在上文中,我们大体分析了从加电到boot monitor启动及执行的流程。如前文所说,boot monitor通过调用
函数bootminix()真正加载minix的启动映像并启动它。
bootminix()函数定义在文件bootimage.c中,它也作为boot程序的一个模块而链接进boot。本文主要分析这个
模块,通过这个模块了解minix的启动过程,并找出minix内核的入口。
* 几个工具函数
1. pretty_image():该函数会把image的名字、版本以较好看的形式打印到终端;
2. raw_clear():该函数将指定的一段内存清0;
* bootminix()
该函数的主要流程如下:
1. 从环境变量中取出"image"的路径名(boot/image),这可以是包含image的目录,
image本身用版本号做名字,也可以是image的路径名;
2. 调用函数select_image()来选择要启动的image;
3. 如果tty是串口,设置SERVARNAME变量,指向这个串口名;
4. 调用exec_image()启动找到的image;
5. 检查是否出错,清理环境;
* select_image()
该函数用于选择要启动的image,如果参数是一个文件,那么这就是要启动的image,如果参数是一个路径,
就在这个路径下找版本最新的image,如果参数是一个'offset:size'的格式,并且这个名字的文件不存在,
那么就以这个为磁盘的偏移量来读取image。
* exec_image()
该函数加载image,patch它的数据结构并启动它。该函数有300行左右(bad practice)。该函数的主要流程
如下:
1. 检查一下栈和堆是否已经冲突了,如果是,重启;
2. 设置verboseboot的值;
3. 打印Loading信息;
4. 在BootMonitor的起始地址之前分配16*32的空间,用于保存image中的程序头信息;
5. 一次读取各个进程的头信息,并处理:
对于内核:
i. 读取clickshift(click是内存分配单元)和flag,设置全局变量click_shift,click_size
和k_flags;
i. 将加载的开始地址对齐到click大小;
i. 如果k_flags中有K_KHIGH标志,重新设置加载的起始地址和终止地址,将这种(较大的内核)
加载到mem[1]部分的内存(扩展内存)中;
a. 将进程头信息拷贝一份到(4)中保留的对应空间中;
b. 打印该进程的地址信息;
c. 将进程的代码段、数据段等加载到内存;
6. 检查:
1. (5)至少加载了一个进程(内核);
2. 内核magic正确;
7. 调用patch_sizes()方法,将加载的进程的信息打补丁到内核数据空间相应的位置(如果内核设置了
K_HDR标志,表示内核会自己使用之前记录的头信息,这样就不用打补丁了。);从这个方法可以看出,
image中最后一个进程必须是init,它的信息同时也会告诉FS;
9. 准备一个内存sector的内核启动参数数据,调用minix()方法,启动minix内核。
内核启动参数数据来自与boot monitor的环境变量。
10. 如果minix函数返回,代表内核推出了,此时,回到boot monitor,继续接收用户的输入。内核可以通过
传入内核的内存sector来向boot monitor传递内核退出后要执行的boot monitor的命令。
* minix()
这是个在bootheader.s中定义的汇编方法。它的参数是:内核入口地址、内核代码段地址、内核数据段
地址、内核参数、内核参数大小、启动image中image header的副本起始地址。
该函数主要做几件事情:
1. 关闭软硬盘;
2. 如果内核设置了可以返回到boot monitor的标志,准备好返回地址;
3. 将CPU由实模式切换到保护模式;
4. 在保护模式下调用内核的入口点;
由此,控制权交给了minix的内核。
** 几个数据结构说明
*** struct process
该结构体主要用于保存一个进程加载到内存后的几个关键地址。例如,入口地址、代码段、数据段等。
从process数组的大小可以看出,image中最多有16个进程。
*** struct image_header
该结构体是头信息,在minix启动映像中的每个进程(包括内核)都有一个这样的头。在Image中,第一个
部分必须是Kernel,第三个部分必须是FS。
至此,内核启动之前的所有工作就做完了,控制权也交给了内核。
阅读(3838) | 评论(0) | 转发(0) |