好的,OK,今天终于来到了高级语言的世界,操作系统的旅途又看到新风景了,日日新,我们废话不多说,现在开始!
开启分页机制
上次我们在head.s中重新设置了gdt和idt。
然后开启分页机制并跳转到main函数
jmp after_page_tables
...
after_page_tables:
push 0
push 0
push 0
push L6
push _main
jmp setup_paging
L6:
jmp L6
如何开启分页机制
setup_paging:
mov ecx,1024*5
xor eax,eax
xor edi,edi
pushf
cld
rep stosd
mov eax,_pg_dir
mov [eax],pg0+7
mov [eax+4],pg1+7
mov [eax+8],pg2+7
mov [eax+12],pg3+7
mov edi,pg3+4092
mov eax,00fff007h
std
L3: stosd
sub eax,00001000h
jge L3
popf
xor eax,eax
mov cr3,eax
mov eax,cr0
or eax,80000000h
mov cr0,eax
ret
我们之前已经知道在保护模式下我们需要进行地址转换,那里段寄存器中存放的是段选择子,需要根绝分段机制的转换成物理地址,换言之,保护模式下一定是开启分段机制的,也就是将内存分为代码段、数据段和栈段这样的。
开启了分页机制后,我们的还是需要先经过分段机制的转换,但是转换后的地址仍然不是物理地址,而是线性地址,我们需要通过线性地址经过分页机制的转化才能得到物理地址。
段选择子+偏移地址 -> 线性地址 -> 物理地址
如何从线性地址到物理地址
线性地址:15M
二进制:0000000011_0100000000_000000000000
我们先将线性地址分为三部分
- 高10位从页目录项找到对应页目录项
- 页目录项值+中间10位找到页表项
- 页表项值+后12位得到物理地址
上面的这一系列操作由计算机硬件MMU(内存管理单元)来进行转换。
操作系统作为一个系统软件,只需要提供页目录项和页表项就可以了~
页目录项PDE
可以看做是页表的一个索引表,起到一个目录的作用
页表项PTE
存放每个页的物理地址
开启分页机制就像之前开启保护模式一样改变cr0寄存器的一个标志位
好的,现在我们再来看设置也页表的代码,实际上是把页目录项和页表项在内存中写好了,linux-0.11认为总共可以使用的内存不超过16M也就是4个页表项就可以搞定了
每个页表项可以包含1024个页表,每个页表的是4KB,因为有12位的偏移地址。
然后将页目录表放在内存的最开头,随后放置4个页表项,随后开启分页机制。
此处告诉cr3寄存器,0地址处是页目录表,通过页目录表可以找到所有的页表。xor eax,eax mov cr3,eax
现在内存中的一个样貌就是这样的了
setup_paging:
...
mov eax,_pg_dir
mov [eax],pg0+7
mov [eax+4],pg1+7
mov [eax+8],pg2+7
mov [eax+12],pg3+7
mov edi,pg3+4092
mov eax,00fff007h
std
L3: stosd
sub eax, 1000h
jpe L3
...
通过上面这段代码,将页目录项和页表项都填充完毕,映射到16M的内存地址空间 ,之后我们就可以顺利找到对应的内存地址了。
跳转到main函数
after_page_tables:
push 0
push 0
push 0
push L6
push _main
jmp setup_paging
...
setup_paging:
...
ret
我们看到上述代码将0 0 0 L6压栈,这里栈是向下发展的,然后将_main压栈。
0 |
---|
0 |
0 |
L6 |
main |
我们看到自setup_paging之后是ret,也就是返回指令,到底返回到哪里呢,CPU直接将栈顶的元素当做返回地址,于是就跳转到main函数中执行了。
将esp(栈顶地址)赋值给eip,cs:eip就是CPU下一条执行指令的地址。
对于Intel而言,访问内存就分为三类:代码、数据、栈。
cs:eip:执行哪里的代码
ds:xxx:访问哪里的数据
ss:esp:栈顶地址在哪里
好的,OK,今天就说到这里吧。有问题欢迎留言讨论!