本节要点:
1、理解 OS 启动过程发生了什么,理解 OS 与 硬件 与 应用 之间的关系
2、本节讲解了setup
模块 和system
模块实现的功能
1、计算机上电时,操作系统在硬盘(磁盘)上,为了“取指执行”,需要把操作系统代码放在内存里。
- bootsect.s:将操作系统读入内存(分段读 bootsect.s、setup.s、system.s …)
2、setup.s 模块:完成 OS 启动前的设置,OS 接管硬件
int 0x15
获得物理内存的大小保存到 ax(#0x88 作为参数)[2]
是间接寻址,默认段寄存器是 9000,所以是 0x90002。即将扩展内存数存入 0x90002,使操作系统对内存大小有一个认知- 把操作系统从 90000 移动到 00000,然后再继续 取指执行
- CS左移4位+IP 只能访问 20位 -> 1M,这种寻址方式无法方位内存 4G 的空间。所以要从16位机(实模式)切换为32位机(保护模式)
- 将值为
#0x0001
赋给寄存器cr0
来启动保护模式,相当于 CPU 要走另外一条解释执行的电路gdt
(不是实模式的左移4位再相加了) - 再通过
jmpi
赋值 cs 寻找表象,赋值 ip 获得偏移
ps: 2^10 = 1KB 2^20 = 1MB 2^30 = 1GB 2^32 = 4GB
gdt
用硬件来实现,相比于软件更快。CS用于选择gdt
表中的表象以生成基址(32位),再和ip偏移(32位)相加- 同样,中断也会根据 int n 去表象中寻找中断处理函数入口地址
- setup 初始化保护模式所用到的 gdt 表
- 前面赋值
cs = 8
,因为一个表象是一个字,即两个字节,所以一行8个字节,则8对应第二行。把四个字填入 设计好的硬件电路,按设计来获得 段基址,再加上 ip 来获得寻址。
setup.s 任务总结:
① 读硬件参数(如内存大小)
② 把 system 模块从 90000 移动到 00000,将OS的核心代码一直放在开头
③ 启动保护模式,应用32位的汇编指令跳到 0 地址处执行
3、 system 模块
- BIOS 读 bootsect.s, bootsect.s 读 setup.s、system模块,再执行 setup.s,执行 system模块等。
- system 模块由许多文件组成,这些文件运行的先后顺序,由 Linux/Makefile (操作系统的控制代码)来控制。
- Makefile 通过一堆源码来产生操作系统镜像,再写入0磁道0扇区。(将一堆 .c 源文件生成 .o 文件,再通过链接指令把文件全部链接在一起就有了 system)
- head.s 建立真正的 idt表(中断) 和 gdt表(寻址),之前的临时 gdt 表只是为了跳转到 system 模块来执行
- 此时 head.s 的32位(保护模式),是用的 32位汇编代码,与之前的不一样,现在前面是源操作数,后面是目标操作数了
- c语言中的函数调用:对应的压栈,把函数参数压入栈中, 跳到调用函数去执行,最后再通过
ret
指令从栈中弹出返回地址,并将控制权转移到该地址。 - 从 head.o 进入到 main.c 去执行(汇编调用c文件),实际上和上述操作一样,通过压栈来执行
- 当 main 函数执行完要返回地址,就会回到 L6,查看代码可以发现 L6 是死循环(电脑死机)
- main 函数中包含了各种外设的初始化程序
- 初始化 mem_map 表格,先将其置为0(右移12位,相当于4k,即每次4k大小的内存进行初始化)
- end_mem 在 setup.s 读入了内存的大小4G(0x90002)
系统启动的全过程:将 OS、GDT、IDT等代码放在内存的起始位置,将应用程序放到内存OS的上端。
boot:将 OS 从磁盘读入内存
setup:获得一些硬件参数,启动保护模式
system:head:初始化 gdt 表 和 idt 表main:初始化一堆设备,如:mem_init