kernel v0.1.1

机器上电启动,BIOS完成硬件自检、中断初始化等,按照BIOS配置读取第一个设备、第一柱面、第0扇区的前 512B 数据到内存中,然后检测这512B数据的最后两个字节是否是0xAA55,是的话就说明是可引导的操作系统,于是开始执行这段代码,这段代码默认被读到内存的 0x07C0的位置。

boot/bootsect.s 就是生成这段代码的,其大小一定小于512B且最后两个字节是 0xAA55

1. boot/bootsect.s

在内存中的起始地址是: 0x07C0

  1. 把自己(bootsect.s编译后的二进制)从 0x7C00 复制到 0x9000

  2. 然后跳转到 0x9000 开始执行,此处需要注意,跳转后使用了 jmpi go,INITSEG 并不是从 0x9000 处开始执行,而是以 0x9000 位基址调到相对的 go 位置,也就是继续执行代码

  3. 调整寄存器:DS、ES、SS、SP

  4. 读取磁盘接下来的4个扇区(1个扇区512B,4个是2KB),读取到 0x90200 开始的位置,也就是 setup.s 编译后的二进制

  5. 输出字符串 “Loading system …”,继续开始读取操作系统内核剩余部分(head.s编译后的二进制加内核部分)

  6. 开始读取操作系统 ES = 0x10000 (这块代码量比较大)

2. boot/setup.s

  1. 跳转到 setup.s 处开始执行。

  2. 读取硬件信息

  3. 关中断

  4. 将 system 移动到 0x00000 处(原来位于0x10000位置,此操作将BIOS在0x00000位置保存的中断向量表以及BIOS数据区完全覆盖)

  5. 加载IDT

  6. 加载GDT

  7. 设置8259A中断控制器

  8. 开启保护模式(设置PE位)

  9. 跳转到head.s 中

3.boot/head.s+内核

  1. 在header.s 中将寄存器从实模式转为保护模式(设置DS、ES、FS、GS的GDT表中的某一个全局描述符,这个全局描述符里保存着段的基址、段长、特权级等信息)

  2. 设置IDT

  3. 设置GDT

  4. 重新加载所有段寄存器

  5. 设置SS段选择符、ESP

  6. 确定A20是否打开

  7. 检测协处理器是否存在(存在则进行设置)

  8. 把要传递的参数进行压栈、把main函数的地址进行压栈

  9. 打开分页机制(设置成功并返回后开始执行 main 函数),将页目录表和4个页表放在内存的起始位置(这是操作系统能够掌控全局、掌控进程在内存中安全运行的基石),将CR3指向页目录表,设置CR0的PG位,启动分页寻址模式

  10. 开始执行内核里的main函数

4. init/main.c

  1. 规划内存布局

  2. 内存初始化,每个内存页都置为 空闲状态

  3. 中断初始化,给每个中断设置中断处理程序

  4. 块设备初始化,块设备请求队列(32个),struct request 用于表示块设备的输入/输出请求,它包含了描述和跟踪设备操作的相关信息。将请求按顺序链接成队列,内核可以有效处理多个并发的设备操作。

  5. 字符设备初始化

  6. tty 初始化,串口设置、显示器接口设置

  7. 时间初始化,读取 CMOS 时间

  8. 进程控制初始化,在GDT中挂接 TSS 和 IDT,设置定时器每隔1/100S产生一次0x80中断,执行 system_call,reschedule、ret_from_sys_call

  9. 缓存区初始化(主要是块设备缓存区)

  10. 硬盘初始化,初始化硬盘中断,将与硬盘的请求交互工作由 do_hd_request 函数来处理(struct request 和 一个回调函数)

  11. 软盘初始化,同硬盘初始化流程一样,只是交互函数不一样

  12. 打开中断

  13. 切换到用户模式

  14. 执行 fork(),并在子进程中执行 init(),父进程等待子进程退出

  15. init(), 打开 /dev/tty0,输出内存适用范围,再次fork,父进程等待子进程退出,子进程关机 stdin,打开 /etc/rc 执行 /bin/sh,退出子进程,父进程继续执行,再次 fork,父进程关闭标准输入、标准输出、标准错误,执行setsid,打开 /dev/tty0,执行 /bin/sh argv envp,父进程一致等待子进程退出。此时子进程打开了一个 shell,等待与用户交互。