日期:2022-09-20
版本号:V1.0
作者:snow
一、基础概念
1.1、段选择符
段寄存器是16位寄存器,(包含代码段CS、数据段DS、栈段SS、附加段ES、分段FS、保留GS)。在实模式环境是指向对应段的基址,在保护模式下,这些寄存器代表的是段选择选择符。段选择符含义如下,每个段选择符即可对应全局描述表GDT或者局部描述符表中的某一个段描述符。
15 2 1 0
INDEX | TI | RPI |
TI代表指向的段描述符表类型(0=GDT,1=LDT)
INDEX=描述符表内偏移
RPI=访问权限,必须高于或等于段描述符中的权限等级,否则不可通过该段选择符访问段描述符所对应的内容
注:段寄存器cs\ds\es\sp等通常指代16位寄存器,ecs\eds\ees\esp等属于32位寄存器,rcs\rds\res\rsp属于64位寄存器。在芯片设计上,16位寄存器是32位寄存器的低16位,同理64位寄存器。
二、重建GDT&IDT
2.1 更新段段选择符
由于系统已经从实模式切换为保护模式,系统的GDT表已经开始使用,故相关的寄存器的功能也需要从段基址变更为相应的段选择符,以实现后续与GDT表的联动过程。
2.1.1 绑定普通段地址
将DS、ES、FS、GS分别置为0x10,即这4个寄存器均指向GDT表的第2个段描述符(从0开始),GDT表的第2个段描述符通常对应为内核数据段描述符。
2.1.2 绑定栈段地址
2.1.2.1 定义栈空间
linux的PAGE_SIZE默认为4KB
在sched.c中声明为
long user_stack[PAGE_SIZE>>2]
user_stack在内核引导阶段会作为内核栈使用,在引导结束后,或作为任务0和任务1的用户栈使用
struc {
long * a;
short b;
} stack_start = { &user_stack[PAGE_SIZE>>2], 0x10 };
即user_stack占用了一个页,stack_start的a指向这个页,同时这个页的段选择符位默认0x10(注:访问user_stack的我第一个字-4B,即对应成员变量a)。
2.1.2.2 加载栈段
栈空间在代码编译阶段好后,接下来就是要将SS寄存器指向该段区域。
lss _stack_start, %esp
(ps:这行代码是AT&T汇编,不是intel汇编,两者不兼容)
对于lds/les/lfs/lgs/lss等段地址加载指令,若目标寄存器为16位,则将mem的低16位送入目标寄存器,高16位送入对应的段寄存器ds/es/fs/gs/ss,若目标寄存器为32位,则将mem的低32位送入目标寄存器,高16位送入对应的段寄存器ds/es/fs/gs/ss。
所以上述指令即是将stack_start的低32位(对应user_stack的地址)送入esp寄存器,stack_start的高16位(对应0x10)送入SS寄存器,即栈段描述符SS更新为0x10,与其他普通段指向相同的段描述符。
2.2、设置IDT
将IDT中的256个门(中断)描述符全部指向一个空函数,该函数无任何实际代码,例如
void ignore_int(void)
{
printf(“hello”);
}
2.3、重建GDT
由于原有GDT表在setup程序内部,而setup程序后续将会被缓冲区覆盖掉,所以需要在其他位置重建一份GDT(小彩蛋,不能在setup程序之后初次建立GDT,否则无法实现setup程序到head程序的跳转)。
2.3.1 定义的新GDT
利用汇编语言gdt_descr中定义如下变量
gdt:
.quad 0x0000 0000 0000 0000 //GDT第0项 保留,空地址
.quad 0x00C0 9A00 0000 0FFF //GDT第1项
.quad 0x00C0 9200 0000 0FFF //GDT第2项
.quad 0x0000 0000 0000 0000 //GDT第3项 保留,空地址
.fill 252.8.0 //给LDT TSS等的空间
(小彩蛋:在编译连接时,新GDT表会定义head.s文件最末尾,并占用2KB存储空间,新GDT位置后面就是system模块的内核代码main)
其中GDT第1项段描述符解析为:段单位4KB,数据/指令宽度32位,段存在,需最高内核权限,程序/代码段,代码段,非一致性,支持读,未被访问,段长度4KB,段基址0(总结就是从0开始的16M空间)
其中第2项段描述符解析为:段单位4KB,数据/指令宽度32位,段存在,需最高内核权限,程序/代码段,数据段,不可向下扩展,支持写,未被访问,段长度4KB,段基址0(总结就是从0开始的16M空间)
2.3.2 加载新GDT
通过lgdt命令将新的gdt表传入到gdtr寄存器中
lgdt gdt_descr
2.3.3 更新段容量
更新过程与与2.1节一致,虽然各个段选择符内容一致,但是需要通过更新寄存器值的方式实现段容量的刷新(由8M变为16M)
三、创建分页机制
3.1、基础准备工作
3.1.1 检查A20
利用实模式下的回滚机制,在1M的位置写入特定数据,若写入后与回滚地址0的内容一致,则当前还处于实模式;若不一致则说明处于保护模式
3.1.2 检查协处理器
检查协处理,即浮点运算器。
3.1.3 压入main函数地址
后续程序将会通过jmp的方式(非call调用)跳转到分页程序,而分页程序执行返回后,会在最后将main函数出栈病跳转到main函数
3.2 创建分页机制
3.2.1 初始化页表
(1)在内存的起始位置定义一个页目录表及4个页表,并将其清0。
(2)将页目录表的第0~3项分别指向上述4个页表(页表0~3)
(3)填写4个页表,如下,使4个页表能指向0~16M的内存空间
3.2.2 激活分页机制
(1)设置页目录基址
将CR3寄存器指向页目录表(也即是0x000000)
(2)激活系统分页功能
将CR0寄存器的第1位(PE)置为1
3.2.3 跳转main
通过ret指令,执行返回
由于main函数入口地址已经压入栈顶,返回后该地址会弹出给CS:EIP