在上一博文 Linux 内核学习笔记:内核引导程序之“head.s”最后我们提到,执行完 head 程序,指令指针就跳转至 main 函数,即从 main 函数开始执行。
main 函数定义在 init/main.c 文件(在 init 目录下,只有一个文件 main.c),是 Linux 0.11 的初始化程序,负责设备环境初始化并激活进程 0。
本文主要分析 main 函数功能,大部分参考自《Linux 内核设计的艺术》一书,小部分参考自《Linux 内核完全注释》。
设置根设备、硬盘
这部分代码如下:
1 | /* |
程序执行结果如下图所示:
图片来源:《Linux 内核设计的艺术》
注:根设备就是根文件系统所在的设备。
内存规划
除了内核代码、数据所占的内存空间外,其余物理内存主要分为 3 个部分:
- 缓冲区:作为主机与外设进行数据交互的中转站
- 虚拟盘区:可选区域。如果选用,则可以将外设上的数据先复制进虚拟盘区,然后加以使用
- 主内存区:进程代码运行的区域,也包括内核管理进程的数据结构
内存规划代码如下:
1 | /* |
上述代码可用图表示如下:
图片来源:《Linux 内核设计的艺术》
设置虚拟盘空间并初始化
这部分代码如下:
1 | // init/main.c --------------------------------- |
注:在之前博文 Linux 内核学习笔记:Linux 0.11 内核概述中,我们假定了在内存中开辟了 2MB 内存作为虚拟盘。
上述代码可用图表示如下:
图片来源:《Linux 内核设计的艺术》
最终内存规划格局如下:
图片来源:《Linux 内核设计的艺术》
内存管理结构 mem_map 初始化
这部分代码如下:
1 | // init/main.c --------------------------------- |
mem_map[] 对 1MB 以上的内存分页进行管理,记录每一个页面的使用次数。mem_init() 函数首先将所有的内存页面使用次数均设置成 USED,然后再将主内存区的所有页面使用计数清零。系统以后只把使用计数为 0 的页面视为空闲页面。
很明显,Linux 0.11 对内存 01MB 和 1MB16MB 采用的是不同的分页管理方法。按照《Linux 内核设计的艺术》的说法:
- 内核采用分页管理方法,线性地址和物理地址完全一样,是一一映射的,等价于内核可以直接获得物理地址。
- 用户进程则不然,线性地址和物理地址差异很大,之间没有可递推的逻辑关系。
- 操作系统的设计者的目的就是让用户进程无法通过线性地址推算出具体的物理地址,让内核能够访问用户进程,用户进程不能访问其他的用户进程,更不能访问内核。
- 1MB 以内(0~1023KB)是内核代码和只有由内核掌控的大部分数据所在内存空间,是绝对不允许用户进程访问的。1MB 以上,特别是主内存区主要是用户进程的代码、数据所在的内存空间,所以采用专门用来管理用户进程的分页管理方法,这套方法当然不能用在内核上。
注:在 1MB 内区域,内核和缓冲区低端一起只占用了 0~639KB (即 0~0x9FFFF) 的共 640KB 的内存区域
,640KB1023KB (即 0xA00000xFFFFF)的区域由 ROM BIOS & VGA 内存映射 I/O 所占。如下图所示:
图片来源:《Linux 内核设计的艺术》
异常处理类中断服务程序挂接
trap_init()
函数将中断、异常处理的服务程序与 IDT 进行挂接,重建中断服务体系(在 head.s 中,IDT 的 256 均为空)。挂接之后的结果如下图所示:
图片来源:《Linux 内核设计的艺术》
具体执行代码如下:
1 | // init/main.c --------------------------------- |
这些代码的目的就是要拼接
出之前博文 Linux 内核学习笔记:内核引导程序之“head.s” 所提到的中断描述符
:
图片来源:《Linux 内核设计的艺术》
代码的执行效果如下图所示:
图片来源:《Linux 内核设计的艺术》
对于中断描述符拼接
,重要的是上述代码中的汇编代码部分:
图片来源:《Linux 内核设计的艺术》
关于这段代码,《Linux 内核设计的艺术》以除零错误为例子详细分析了这段汇编代码:
图片来源:《Linux 内核设计的艺术》
拼接效果如下图所示:
图片来源:《Linux 内核设计的艺术》