更新数据段寄存器与堆栈段寄存器
首先,保护模式下把0x10加载到ds es fs gs段寄存器中,就是将setup.s中设置的第2项数据段描述符加载到各段寄存器的隐藏区域。然后,看下kernel/sched.c第69行的内容:
说明:lss指令指定的是32位通用寄存器,所以该指令的作用是将存储操作数中低位双字送该32位通用寄存器,高位字送指令指定的SS段寄存器。所以esp=&user_stack[PAGE_SIZE>>2],ss=0x10;用户空间是1K*4=4K空间。
重新设置idt及gdt
第一条指令lea(load effective address to register),取ignore_int标号的偏移地址给edx寄存器。想要理解这段在干什么需要先清楚idt表项的结构,参考下图或者中断和异常
我们以中断门描述符为例,代码79-85行就是在构造这样一个8字节门描述符。将_idt表基地址放入edi寄存器,将8字节的eax和edx分别放入edi及其后的4个字节处。最后循环操作256次建立中断描述符表。0x8E00的含义就是DPL=0,P=1,其他位为0.然后这里的段选择符被设置为8.
小结:lidt指令将idt表的基地址和长度加载,然后rp_sidt设置idt表的每一项描述符,而每一项描述符都有一个额外的偏移地址ignore_int,这个偏移地址是实际处理中断的入口,并且使用段选择符0x08去获取这个中断入口的段基地址。这样整个中断描述符的工作机理就清楚了。
下面我们再进一步看看这个中断处理程序都做了些什么:
这里涉及到通过堆栈传递函数参数int_msg指针,因为使用了eax、ds、es、fs寄存器所以也提前入栈保存。下面继续分析setup_gdt:
这里的gdt表已经手工填充好了所以直接加载到gdtr寄存器中就可以了。上面有gdt_descr的代码,我们重点看下_gdt的内容:
关于局部描述符和任务状态段TSS的描述符,后面继续学习遇到了再分析。
设置页目录项和页表项
head.s检测完A20地址线和数学协处理器以后,会跳转到135行的jmp after_page_tables
这里首先把main的三个函数参数入栈,然后入栈main返回后执行的指令L6的地址。根据之前的经验,此处应该调用call指令调用main函数,但是这里需要先设置页表,开启分页机制,所以先把main地址入栈,作为setup_paging返回后执行的下一条指令。
说明几点:
1)setup.s程序中曾经学习了stosb指令,这里是stosl是双字操作,将EAX的值传送到ES:EDI内存地址处,结合ECX及rep指令来对0x00000地址开始的5页内存页共5*4K做初始化操作。
2)页目录表项格式参见80386内存寻址机制
因此页目录表的前4项就是$pg0+7,$pg1+7,$pg2+7,$pg3+7;
3)页目录表项填好以后,开始填写页表项,这里从$pg3+4092项开始往$pg0方向填写0xfff007,并且每填好一项,对应的物理地址减去4K。STOSL会同时操作EDI寄存器每次减去4个字节。jge(great equal)是当结果大于等于0的时候就继续循环。
4)页目录基地址在0地址处,赋值CR3寄存器。
5)取出CR0寄存器的值,PG位置1,写回CR0,开启分页。
6)ret返回的时候根据之前的分析,main函数地址从栈中弹出,并跳转到main函数执行。
到这里,实模式到保护模式切换,段转换,页转换以及中断描述符表,全局描述符表的设置和意义就全部介绍一遍了。整个过程也比较清晰。这个需要反复很多遍才能真正理解成为自己的东西,请不要放弃。CPU进入了保护模式,我也可以顺利过渡到下个阶段了。