进入内核前的苦力活[linux源码趣读]
总体
加载代码
- pc指针初始指向0xFFFF0(ROM) 代表BIOS的地址
- 加载硬盘第一扇区代码(bootsetct)到0x7c00
- 复制到0x90000
- 跳转到go代码,设置好cs ds ss sp
- 把全部os代码搬入内存(setup.s 2~5; head.s 240扇区),至此bootsect.s使命完成
- setup.s 使用
int
指令初始化光标、内存、显卡、磁盘等信息放到bootsect.s的位置,并把system代码复制到0位置
进入保护模式
setup.s
设置初始化IDT(键盘、时钟、串口、鼠标),GDT(包含代码段、数据段描述符)并设置寄存器指向他们的地址
将cr0中PE置为1,切换到保护模式(地址转换也响应变换)
原来实模式下地址转换的方法
保护模式下转换方法:需要转换一下段基址(段寄存器(比如 ds、ss、cs)里存储的是段选择子,段选择子去全局描述符表中寻找段描述符,从中取出段基址)
跳转到cs:ip 0:0位置,从现在开始进入system代码
重新设置idt、gdt,指向新的空间
开启分页
地址转换
cr0中的PG设置为1
没有开启分页机制的时候,只需要经过这一步转换即可得到最终的物理地址了,但是在开启了分页机制后,又会多一步转换。
二级页表线性地址转物理地址(第一级叫页目录表 PDE,第二级叫页表 PTE),MMU负责转换
0000000011_0100000000_000000000000
当时 linux-0.11 认为,总共可以使用的内存不会超过 16M,也即最大地址空间为 0xFFFFFF。
而按照当前的页目录表和页表这种机制,1 个页目录表最多包含 1024 个页目录项(也就是 1024 个页表),1 个页表最多包含 1024 个页表项(也就是 1024 个页),1 页为 4KB(因为有 12 位偏移地址),因此,16M 的地址空间可以用 1 个页目录表 + 4 个页表搞定。
4(页表数)* 1024(页表项数) * 4KB(一页大小)= 16MB
全局结构
CR3寄存器是虚拟内存管理的核心部分,与操作系统的内存管理紧密相关。当操作系统需要切换当前的内存映射时(如进程切换时),它会更新CR3寄存器的值来指向新的页目录表。
小结
Intel 体系结构的内存管理可以分成两大部分,也就是标题中的两板斧,分段和分页。
分段机制在之前几回已经讨论过多次了,其目的是为了为每个程序或任务提供单独的代码段(cs)、数据段(ds)、栈段(ss),使其不会相互干扰。(保护模式必须开启)
分页机制是本回讲的内容,开机后分页机制默认是关闭状态,需要我们手动开启,并且设置好页目录表(PDE)和页表(PTE)。其目的在于可以按需使用物理内存,同时也可以在多任务时起到隔离的作用,这个在后面将多任务时将会有所体会。
- 逻辑地址:我们程序员写代码时给出的地址叫逻辑地址,其中包含段选择子和偏移地址两部分。
- 线性地址:通过分段机制,将逻辑地址转换后的地址,叫做线性地址。而这个线性地址是有个范围的,这个范围就叫做线性地址空间,32 位模式下,线性地址空间就是 4G。
- 物理地址:就是真正在内存中的地址,它也是有范围的,叫做物理地址空间。那这个范围的大小,就取决于你的内存有多大了。
最后进入main函数是利用ret指令,ret会将栈顶作为下一条指令的地址,所以只需要将main函数提前入栈即可
总结
前五节:载入代码
之后包含进入保护模式(分段)以及开启分页,初始化了IDT、GDT、页表,并且设置响应寄存器指向他们:idtr 寄存器指向了 idt,这个就是中断的设置;gdtr 寄存器指向了 gdt,这个就是全局描述符表的设置,可以简单理解为分段机制的设置;cr3 寄存器指向了页目录表的位置
Intel 本身对于访问内存就分成三类:
- 代码
- 数据
- 栈
而 Intel 也提供了三个段寄存器来分别对应着三类内存:
- 代码段寄存器(cs)
- 数据段寄存器(ds)
- 栈段寄存器(ss)
具体来说:
- cs:eip 表示了我们要执行哪里的代码。
- ds:xxx 表示了我们要访问哪里的数据。
- ss:esp 表示了我们的栈顶地址在哪里。
而第一部分的代码,也做了如下工作:
- 将 ds 设置为了 0x10,表示指向了索引值为 2 的全局描述符,即数据段描述符。
- 将 cs 通过一次长跳转指令设置为了 8,表示指向了索引值为 1 的全局描述符,即代码段描述符。
- 将 ss:esp 这个栈顶地址设置为 user_stack 数组的末端。
你看,分段和分页,以及这几个寄存器的设置,其实本质上就是安排我们今后访问内存的方式,做了一个初步规划,包括去哪找代码、去哪找数据、去哪找栈,以及如何通过分段和分页机制将逻辑地址转换为最终的物理地址。
而所有上面说的这一切,和 Intel CPU 这个硬件打交道比较多,设置了一些最最最最基础的环境和内存布局,为之后进入 main 函数做了充分的准备,因为 c 语言虽然很底层了,但也有其不擅长的事情,就交给第一部分的汇编语言来做,所以我称第一部分为进入内核前的苦力活。
1 |
|