关于head.s,它是在/boot目录下三个汇编程序中唯一一个使用AT&T指令的,对于我这个x86汇编看多的人有很多不适应的地方。
head.s主要实现的功能:
- 重新设置IDT和GDT;(至于为什么要重新设置IDT和GDT,在setup.s中设置的中断向量表和GDT,由于是在实模式下设置的,但是现在运行的程序是在保护模式下运行的,采用实模式的话,包含的信息较少,比如没有包含每个段的r,w,x等信息,因此要重新设置);
- 检测A20是否真的开启了;
- 检测PC机是否含有数字协处理器芯片;
- 设置页表;
- 将堆栈中的main的入口地址弹出,然后执行;
head.s部分代码分析:
-
AT&T中的立即数
mov $0x10,%eax mov %ax,%ds mov %ax,%es mov %a,%fs mov %ax,%gs
上面第一行中mov $0x10,%eax,如果没有$,则表示地址,即0x10表示地址,$0x10则表示段描述符表的选择项。这里也许有人会问如果要表示立即数怎么办,在第46行和第89行中$2,$8表示2和8这两个立即数。
-
A20地址线的开启
这段代码我也花了很多的时间来理解,这里我解释一下如果A20线关闭,则当程序员给出100000H-10FFEFH之间的地址的时候,系统仍然使用8086/8088的方式,即访问模100000H之后的地址,在第35行中如果A20关闭,则访问ox100000时,系统访问的地址会模100000h的地址,即0x00000的地址,所以相等则A20是关闭的。如果知道了这个道理,我们也可以比较ox000001和0x100001这两个地址的内容。xorl %eax,%eax 1:incl %eax, movl %eax,0x000000 cmpl %eax,0x100000 je 1b
-
关于IDT和GDT
IDT和GDT的设置和setup.s中的基本一样,将一个线性地址存到IDTR和GDTR中,这里的差别主要在与在保护模式下,各个段除了段地址,段长之外还用一些标志位,使用权限。因此,实模式下的中断向量表是不能满足要求的。
关于这段代码,我要提示一下这里的中断描述表项所表示的中断处理地址是0~1,6~7字节。根据对代码的理解,0~1表示的是低地址,6~7表示的是高地址,我的理解是0~1是偏移量,6~7表示的是段基址。setup_idt: lea ignore_int ,%edx movl $0x00080000,%eax movw %dx,%ax mov w $0x8e00,%dx lea _idt ,%edx mov $256,%ecx rp_sidt: movl %eax,(%edi) movl %edx,4(%edi) addl $8,%edi dec %ecx jne rp_sidt lidt idt_descr ret
-
main入栈操作
关于这段代码,我觉得要联系后面的第218行代码看 ret。当程序执行到218行时,head.s的代码基本上已经到了最后了,调用ret,将栈中4个字节调出来,正好是_main的地址。pushl $0 pushl $0 pushl $0 pushl $L6 pushl $_main jmp setup_pageing L6: jmp L6
-
设置页表
-
清零
<span style="font-size:18px;">movl $1024*5,%ecx xorl %eax,%eax xorl %edi,%edi cld;rep;stosl</span>
-
设置页目录项
这里已经开始覆盖了head.s的代码了,页目录在物理地址0开始。页目录项指向一个页表的首地址<span style="font-size:18px;">movl $pg0+7,_pg_dir movl $pg1+7,_pg_dir+4 movl $pg2+7,_pg_dir+8 movl $pg3+7,_pg_dir+12 </span>
-
设置页表项
页表项指向一个页的首地址(注意和页目录项的区别)。一共有4个页表,每个页表有1024项,每一项指向了一个4KB的页,从而可以指向4*1024*4KB=16MB的内存。<span style="font-size:18px;">movl $pg3+4.92,%edi movl $0xfff007,%eax std 1: stosl subl $0x1000,%eax jge 1b</span>