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

来源:互联网 发布:大数据思维与决策心得 编辑:程序博客网 时间:2024/05/21 20:26

作者:朱克锋

转载请注明出处:http://blog.csdn.net/linux_zkf

 

 

    在分析head之前先看一下这个head程序,前面讲过加载分三步进行,1,加载bootsec0x07C00后移到0x90000位置,2,加载setup0x90200位置,这两部分是分别加载和执行的,然而head于此是不同的,head程序在被编译成目标代码后会和内核的其他程序一起被链接成目标程序,head位于system最前端的位置在面的setup程序已将system程序移到了0x00000的位置,有因为head在最前面,所以head位置在0x00000位置简单的可以表成下图:

0x00000Head

Main

 

    从head程序开始,程序都是在保护模式下执行,这里注意head汇编与前面的bootsecsetup是不同的。

    在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程序的本身数据,这段代码执行完之后内存分布图如下:

Main0x64b8

全局描述符表(2KB0x05cb8

中断描述符表(2KB0x054b8

。。。0x05000

页表34KB0x04000

页表24KB 0x03000

页表14KB 0x02000

页表04KB 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函数执行。

原创粉丝点击