OS-Lab1
一.内核的物理位置
操作系统最重要的部分是操作系统内核,因为内核需要直接与硬件交互管理各个硬件,从而利用硬件的功能为用户进程提供服务。
启动操作系统,我们就需要将内核代码在计算机结构上运行起来,一个程序要能够运行,其代码必须能够被 CPU 直接访问,所以不能在磁盘上,因为 CPU 无法直接访问磁盘。
CPU可以直接从硬盘里调用数据,然而这样太慢了,而内存则比硬盘快得多,把用有的东西先放入内存里面,CPU调用起来就快得。
所以不可能将内核代码保存在内存中。所以直观上可以认识到:
(1) 磁盘不能直接访问
(2) 内存掉电易失,内核文件有可能放置的位置只能是 CPU 能够直接访问的非易失性存储器——ROM 或 FLASH 中。
将硬件初始化的相关工作从操作系统中抽出放在bootloader中实现,意味着通过这种方式实现了硬件启动和软件启动的分离。 因此需要存储在非易失性存储器中的硬件启动相关指令不需要很多,能够很容易地保存在ROM或FLASH中。
bootloader在硬件初始化完后,需要为软件启动(即操作系统内核的功能)做相应的准备, 比如需要将内核镜像文件从存放它的存储器(比如磁盘)中读到RAM中。既然bootloader需要将内核镜像文件加载到内存中, 那么它就能选择使用哪一个内核镜像进行加载,即实现多重开机的功能。使用bootloader后,我们就能够在一个硬件上运行多个操作系统了
二.Bootloader
而当内存被初始化,bootloader将后续代码载入到内存中后,位于内存中的代码便能完整地使用C语言的各类功能了。 所以说,内存中的代码拥有了一个正常的C环境。
在 stage 1 时,需要初始化硬件设备,包括watchdog timer、中断、时钟、内存等。需要注意的一个细节是,此时内存 RAM 尚未初始化完成, 因而 stage 1 直接运行在存放 bootloader 的存储设备上(比如FLASH)。由于当前阶段不能在内存 RAM 中运行,其自身运行会受诸多限制, 比如有些 flash 程序不可写,即使程序可写的 flash 也有存储空间限制。这就是为什么需要stage 2的原因。 stage 1除了初始化基本的硬件设备以外,会为加载stage 2准备RAM空间,然后将stage 2的代码复制到RAM空间,并且设置堆栈,最后跳转到stage 2的入口函数。
stage 2运行在RAM中,此时有足够的运行环境,所以可以用C语言来实现较为复杂的功能。 这一阶段的工作包括,初始化这一阶段需要使用的硬件设备以及其他功能,然后将内核镜像文件从存储器读到RAM中,并为内核设置启动参数, 最后将CPU指令寄存器的内容设置为内核入口函数的地址,即可将控制权从bootloader转交给操作系统内核。
gxemul支持加载elf格式内核,所以启动流程被简化为加载内核到内存,之后跳转到内核的入口。启动完毕
三.编译和链接
printf的实现是在链接(Link)这一步骤中被插入到最终的可执行文件中的。那么,了解这个细节究竟有什么用呢? 作为一个库函数,printf被大量的程序所使用。因此,每次都将其编译一遍实在太浪费时间了。printf的实现其实早就被编译成了二进制形式。
但此时,printf并未链接到程序中,它的状态与我们利用-c选项产生的hello.o相仿,都还处于未链接的状态。而在编译的最后,链接器(Linker)会将所有的目标文件链接在一起,将之前未填写的地址等信息填上,形成最终的可执行文件,这就是链接的过程。
对于拥有多个c文件的工程来说,编译器会首先将所有的c文件以文件为单位,编译成.o文件。最后再将所有的.o文件以及函数库链接在一起, 形成最终的可执行文件。
链接器通过哪些信息来链接多个目标文件呢?答案就在于在目标文件(也就是我们通过-c选项生成的.o文件)。 在目标文件中,记录了代码各个段的具体信息。链接器通过这些信息来将目标文件链接到一起。而ELF(Executable and Linkable Format)正是Unix上常用的一种目标文件格式。 其实,不仅仅是目标文件,可执行文件也是使用ELF格式记录的。
四.va_list、va_start和va_end三个宏的用法。
1.c语言提供了函数的不定长参数使用,比如 void func(int a, …)。三个省略号,表示了不定长参数。
注意:c标准规定了,函数必须至少有一个明确定义的参数,因此,省略号前面必须有至少一个参数。
2.va_list宏定义了一个指针类型,这个指针类型指向参数列表中的参数。
3.void va_start(va_list ap, last_arg),修改了用va_list申明的指针,比如ap,使这个指针指向了不定长参数列表省略号前的参数。
4.type va_arg(va_list, type),获取参数列表的下一个参数,并以type的类型返回。
5.void va_end(va_list ap), 参数列表访问完以后,参数列表指针与其他指针一样,必须收回,否则出现野指针。一般va_start 和va_end配套使用。
5.函数的参数一般从右至左先后入栈,根据栈的特性,也就是,最左边的参数最先出栈。贴一段代码介绍下va_list、va_start和va_end的使用。
感兴趣的,可以把函数的整型换成char或者int,参数列表判断条件为NUL,还可以为每个参数指定类型。
1 |
|
五.ELF文件的结构
ELF文件的解析
需要输出 ELF ⽂件的所有 section header 的序号和地址信息
1 |
|