您的位置:首页 > 运维架构 > Linux

从linux0.11学习linux内核设计之模式转换:实模式-保护模式(2)

2012-05-13 16:16 453 查看
作者:朱克锋
转载请注明出处: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函数执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐