0. system程序

MakeFile可以看出system是head.s和main.c编译出来的,head.s就是进入main.c之前的最后一段汇编了

1.重新设置idt & gdt

汇编代码就省略了,简单来说这一步就是

  • 重新设置了一下栈顶
  • 让idtr和gdtr寄存器重新指向system区域初始化的idt和gdt
  • 两者都一样

image.png

2.分段和分页

  • after_page_tables标签就是分页实现

    段页式地址转换回顾

    • 原来的段式内存地址转换如下
      • 有一个逻辑地址
      • 通过段寄存器ds找到gdt,其中获取数据段存放的段基址
      • 段基址+逻辑地址(偏移量)=物理地址
      • image.png
    • 现在页式内存地址转换,基于段式地址基础上,多了一步:
      • 段式转换出来的不再是物理地址,而是线性地址
      • 通过MMU根据PD(PageDirectory,页目录,一级页表)和线性地址高n位找到PDE(PD-Entry,页目录项)
      • 根据PDE找到PT(PageTable,页表,二级页表)和线性地址中m位,然后找到PTE(页表项)
      • 最后线性地址低x位即为页偏移
      • 页偏移+页起始地址 = 物理地址
      • ps:这些内容在操作系统中有讲过
  • 开启分页机制的开关,其实就是更改 cr0 寄存器中的一位即可(31 位),还记得我们开启保护模式么,也是改这个寄存器中的一位的值。

  • image.png
  • 然后,MMU 就可以帮我们进行分页的转换了。此后指令中的内存地址(就是程序员提供的逻辑地址),就统统要先经过分段机制的转换,再通过分页机制的转换,才能最终变成物理地址。
  • 所以,从setup_paging到L3两个标签的代码就是在干上面说的事儿,并在内存起始位置生成两级页表
  • image.png这些页目录表和页表放到了整个内存布局中最开头的位置,就是覆盖了开头的 system 代码了,不过被覆盖的 system 代码已经执行过了,所以无所谓。
  • xor eax,eax
  • mov cr3,eax
  • 上面两个是将页目录表地址放在cr3 寄存器,和idt,gdt一样
  • 当前内存状态
  • image.png
  • 好了分页搞好,终于可以进入C++代码了

3. head.s的最后

  • 在进入main.c之前,看看head.s的首尾

    1. jmp after_page_tables
    2. ...
    3. after_page_tables:
    4. push 0
    5. push 0
    6. push 0
    7. push L6
    8. push _main
    9. jmp setup_paging
    10. L6:
    11. jmp L6
  • 代码中有个 push _main,把 main 函数的地址压栈了,那最终跳转到这个 main.c 里的 main 函数,一定和这个压栈有关。


压栈为什么和跳转到这里还能联系上呢?下一节继续说.