0. system程序
MakeFile可以看出system是head.s和main.c编译出来的,head.s就是进入main.c之前的最后一段汇编了
1.重新设置idt & gdt
汇编代码就省略了,简单来说这一步就是
- 重新设置了一下栈顶
- 让idtr和gdtr寄存器重新指向system区域初始化的idt和gdt
- 两者都一样
2.分段和分页
after_page_tables标签就是分页实现
段页式地址转换回顾
- 原来的段式内存地址转换如下
- 有一个逻辑地址
- 通过段寄存器ds找到gdt,其中获取数据段存放的段基址
- 段基址+逻辑地址(偏移量)=物理地址
- 现在页式内存地址转换,基于段式地址基础上,多了一步:
- 段式转换出来的不再是物理地址,而是线性地址
- 通过MMU根据PD(PageDirectory,页目录,一级页表)和线性地址高n位找到PDE(PD-Entry,页目录项)
- 根据PDE找到PT(PageTable,页表,二级页表)和线性地址中m位,然后找到PTE(页表项)
- 最后线性地址低x位即为页偏移
- 页偏移+页起始地址 = 物理地址
- ps:这些内容在操作系统中有讲过
- 原来的段式内存地址转换如下
开启分页机制的开关,其实就是更改 cr0 寄存器中的一位即可(31 位),还记得我们开启保护模式么,也是改这个寄存器中的一位的值。
- 然后,MMU 就可以帮我们进行分页的转换了。此后指令中的内存地址(就是程序员提供的逻辑地址),就统统要先经过分段机制的转换,再通过分页机制的转换,才能最终变成物理地址。
- 所以,从setup_paging到L3两个标签的代码就是在干上面说的事儿,并在内存起始位置生成两级页表
- 这些页目录表和页表放到了整个内存布局中最开头的位置,就是覆盖了开头的 system 代码了,不过被覆盖的 system 代码已经执行过了,所以无所谓。
xor eax,eax
mov cr3,eax
- 上面两个是将页目录表地址放在cr3 寄存器,和idt,gdt一样
- 当前内存状态
- 好了分页搞好,终于可以进入C++代码了
3. head.s的最后
在进入main.c之前,看看head.s的首尾
jmp after_page_tables
...
after_page_tables:
push 0
push 0
push 0
push L6
push _main
jmp setup_paging
L6:
jmp L6
代码中有个 push _main,把 main 函数的地址压栈了,那最终跳转到这个 main.c 里的 main 函数,一定和这个压栈有关。
压栈为什么和跳转到这里还能联系上呢?下一节继续说.