作者:朱克锋
转载请注明出处:http://blog.csdn.net/linux_zkf
在分析head之前先看一下这个head程序,前面讲过加载分三步进行,1,加载bootsec到0x07C00后移到0x90000位置,2,加载setup到0x90200位置,这两部分是分别加载和执行的,然而head于此是不同的,head程序在被编译成目标代码后会和内核的其他程序一起被链接成目标程序,head位于system最前端的位置在面的setup程序已将system程序移到了0x00000的位置,有因为head在最前面,所以head位置在0x00000位置简单的可以表成下图:
(0x00000)Head |
Main |
从head程序开始,程序都是在保护模式下执行,这里注意head汇编与前面的bootsec和setup是不同的。
在head程序中有一个变量和重要:_pg_dir,用于表示内核分页机制完成以后的内核起始位置,即0x00000,紧接着head要在此处建立页目录表。代码如下:
.text
.globl _idt,_gdt,_pg_dir,_tmp_floppy_area
_pg_dir:
startup_32:
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
lss _stack_start,%esp
call setup_idt
call setup_gdt
head程序的目的是为了适应保护模式做准备的,可以从上面这几行代码看出在设置几个寄存器之后就会调用
call setup_idt
call setup_gdt
这两个方法,代码如下所示:
setup_idt:
lea ignore_int,%edx
movl $0x00080000,%eax
movw %dx,%ax /* selector = 0x0008 = cs */
movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
lea _idt,%edi
mov $256,%ecx
rp_sidt:
movl %eax,(%edi)
movl %edx,4(%edi)
addl $8,%edi
dec %ecx
jne rp_sidt
lidt idt_descr
ret
这段代码的作用是对中断描述符表进行设置。
接下来head还要重新设置全局描述符表并设置相关寄存器,代码如下:
gdt_descr:
.word 256*8-1 # so does gdt (not that that's any
.long _gdt # magic number, but it works for me :^)
.align 3
_idt: .fill 256,8,0 # idt is uninitialized
然后开始设置一些寄存器,检测A20地址线是否打开,检测数学协处理器这里就不再具体介绍,相关可以查看相关代码。
再往后head就要为执行main函数做最后的准备了:jmp after_page_tables
可以看到在after_page_tables中:
after_page_tables:
pushl $0 # These are the parameters to main :-)
pushl $0
pushl $0
pushl $L6 # return address for main, if it decides to.
pushl $_main
jmp setup_paging
L6:
jmp L6
这是一些压栈操作,之后head程序跳转到setup_paging中去执行分页机制的创建,代码如下:
setup_paging:
movl $1024*5,%ecx /* 5 pages - pg_dir+4 page tables */
xorl %eax,%eax
xorl %edi,%edi /* pg_dir is at 0x000 */
cld;rep;stosl
movl $pg0+7,_pg_dir /* set present bit/user r/w */
movl $pg1+7,_pg_dir+4 /* --------- " " --------- */
movl $pg2+7,_pg_dir+8 /* --------- " " --------- */
movl $pg3+7,_pg_dir+12 /* --------- " " --------- */
movl $pg3+4092,%edi
movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */
std
1: stosl /* fill pages backwards - more efficient :-) */
subl $0x1000,%eax
jge 1b
xorl %eax,%eax /* pg_dir is at 0x0000 */
movl %eax,%cr3 /* cr3 - page directory start */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* set paging (PG) bit */
ret
从代码可以看出,head程序建立分页机制的同时覆盖了head程序的本身数据,这段代码执行完之后内存分布图如下:
Main:0x64b8 |
全局描述符表(2KB)0x05cb8 |
中断描述符表(2KB)0x054b8 |
。。。0x05000 |
页表3(4KB)0x04000 |
页表2(4KB 0x03000 |
页表1(4KB 0x02000 |
页表0(4KB 0x01000 |
页目录表(4KB 0x00000 |
Head程序最后一步:ret,跳到main函数执行。
下面来分析一下head是如何跳到main函数的,比较特殊。
在前面我们看到在分页机制执行之前的压栈操作即:
pushl $0 # These are the parameters to main :-)
pushl $0
pushl $0
pushl $L6 # return address for main, if it decides to.
pushl $_main
main函数的地址在最后一行被压入栈中,head最后执行ret的时候正好将最后压入的main函数的执行地址弹出实现了用返回指令调用main函数的特殊用法。
OK,到此linux0.11汇编部分就分析完了,接下来就会进入main函数执行。