XV6

kernel 将从 entry.S 转入到 main.c 的 C 代码 main() 函数,完成若干项初始化工作,在 userinit() 创建第一个用户进程后进入 mpmain() 函数进行 scheduler() 调度。

下面对 main() 中的所调用的各种初始化函数按调用次序做一个简单的介绍,并且指出它们在哪里继续深入讨论。

物理内存管理使用了链表将空闲页帧进行组织,kinit2() 用于初始化时将所有空闲页帧并构成链表。

  1. kinit1() 将现有的 4 MB 物理页帧中未使用的部分,按照 4 KB 页帧的尺寸构成链表。
  2. kvmalloc() 用于创建内核空间的页表,此时使用的 4 KB 页而不是使用原来的 4 MB 大页模式的页表。
  3. mpinit() 用于检测其他 CPU 核,并根据收集的信息设置 cpus[ncpu] 数组和 ismp 多核标 志。
  4. lapicinit() 对本地 apic 中断控制器进行初始化。
  5. seginit() 初始化段表中的段描述符,在原来的两个内核段的基础上增加了用户态段。
  6. picinit()8259A 中断控制器初始化代码(适用于单核系统)。
  7. ioapicinit()I/O APIC 中断控制器初始化(适用于多核 SMP 系统)。
  8. consoleinit() 完成控制台初始化。
  9. uartinit() 完成串口初始化。
  10. pinit() 通过 initlock() 完成进程表 ptable 的互斥锁的初始化(开锁状态)。
  11. tvinit() 对中断向量初始化,而真正将 IDT 装入到 IDTR 要等待 mpmain()->idtinit() 的时候。
  12. binit() 初始化磁盘块缓存(buffer cache),完成互斥锁初始化(开锁状态),并将缓冲区构成链表。
  13. fileinit() 通过 initlock() 完成对 ftable 的互斥锁进行初始化(开锁状态),table 记录系统所打开的文件,总数不超过 NFILE
  14. ideinit() 对 IDE 控制器进行初始化,包括互斥锁的初始化、相应的中断使能以及检测是否有 driver 1
  15. startohters() 用于启动其他处理器核。
  16. kinit2() 函数将 [4MB, 240MB) 地址范围的空闲页帧(4 KB 大小的页帧)构成链表。
  17. userinit() 中定义,用于创建第一个用户态进程 init
  18. mpmain() 将 IDT 表的起始地址装入到 IDTR 寄存器中,然后开始执行 scheduler() 内核执行流。

除了比较简单的初始化函数在下面讨论外。其他复杂的初始化函数将会在各自的子系统分析中加以讨论,例如 kvmalloc() 在内存管理子系统中讨论。

1. seginit()

每个处理器都要执行这个初始化,最终各个处理器核 上的段表内容几乎相同:在每 CPU 变量 cpus[].gdt[] 中设置 4 项,分别对应内核代码段 SEG_KCODE、内核数据段 SEG_KDATA、用户代码段 SEG_UCODE、用户数据段 SEG_UDATA

各处理器不同的地方是 SEG_KCPU 段,由段寄存器 GS 使用,对应于 “每 CPU” 变量。

2. mpmain()

当主处理器或 AP 处理器启动到 mpmain(),说明已经是启动代码的最后阶段,随后将进入 scheduler() 调度器中的无限循环(不再返回)。因此,mpmain() 的第一件事情就是打印 cpuX: starting 的信息,然用 idtinit() 装入中断描述符 IDT 表以响应时钟中断(及其他中断), 接着将 cpu->started 设置为 1,让其他处理器知道本处理器已经完成启动。最后进入到调度器的 scheduler() 中的无限循环中,每隔一个 tick 时钟中断就选取下一个就绪进程来执行。

3. mpenter()

其他 AP 处理器经历 entryother.S 启动代码后,进入到 mpenter()mpenter()switchkvm() 切换到本处理器内存空间;再用 seginit() 设置段表(各处理器段表几乎完全相同),除了 GS 使用的 SEG_KCPU 段用于访问 “每 CPU” 变量;在完成本地中断控制电路 local APIC 的初始化 lapicinit(),然后就可以进入到 mpmain() 执行最后阶段的代码完成启动并进入到调度器循环中。

4. startothers()

startothers() 先将 entryother.S 代码从现在的位置拷贝到 0x7000 物理地址,然后逐个 CPU 启动。每启动一个 CPU 核,需要为它传递独立的堆栈指针、mpenter() 地址和系统共享的页表地址 entrypgdir