保护模式
保护模式是从80386时代引入的,之前的8086只有实模式,实模式有1MB的寻址空间(2 ^ 20),而保护模式有4GB的寻址空间,且支持分页等操作
保护模式对内存段的访问做了限制,对于内存段的性质和允许的操作给出了定义,所以每一个段有一个段描述符,占8个字节,段描述符有很多,只能放在内存里,其中最重要的全局段描述符表(GDT),GDT的相关信息存在cpu的寄存器GDTR中
GDTR有48位,其中0 - 15位表示GDT的边界,也就是GDT段的大小,16 - 47位这32位存的是GDT的基地址,所以GDT段的大小最多为65536字节,也就是可以存8192个段描述符
也就是我们通过GDTR寄存器获取GDT的位置和大小,然后GDT中存储了所有段描述符
cup加电后工作在实模式,而进入保护模式后就需要GDT和GDTR,所以我们需要在进入保护模式之前就设置好GDT和GDTR,但是实模式只有1MB的寻址空间,所以一开始的GDT只能放在这里,进入保护模式后还可以更改
从实模式进入保护模式只需要修改CR0寄存器的第0位,为0表示实模式,为1表示保护模式
具体的分段策略
分段是cpu为了兼容以前的程序所保留的,所以我们一般绕过分段直接用分页技术,而绕过分段的方法就是把4GB的空间分成一段,整个虚拟地址的其实位置是0,长度为4G,这样偏移地址和段机制处理后的地址是同一个数
当然并不是给所有的段相同的描述符,数据段和代码段是不同的描述符
段描述符的结构
参考: https://blog.csdn.net/longintchar/article/details/50489889
看这个,就不搬运了
代码实现
include/gdt.h
1 |
|
gdt/gdt.c
1 |
|
一点一点来看
首先定义了GDT的长度,这个不是GDT的大小,而是GDT数组的长度,因为GDT就可以看作是一个数组,其中存放的是一个个8字节的段描述符
然后定义了GDT数组gdt_entries和GDTR gdt_ptr
然后是初始化GDT的函数,首先设定该gdt的大小也就是数组长度*元素大小 -1,然后gdt的基地址,也就是数组的起始地址
后面是构造全局段描述符表,首先规定第一个段是全0,段的基地址拆成低16位,中8位和高8位,然后段限长是20位的,其中低16位存在limit_low中,还有4位存在granularity的低4位中,剩下4位是访问标志
设置的描述符,除了后面的标志不同前面的都是基地址为0,限长为4GB
然后是把GDT的描述信息写入GDTR
gdt/gdt_s.s
1 | [GLOBAL gdt_flush] |
最后更改init / entry.c测试一下
1 |
|
如果正常输出就ok了
现在的目录结构