linux内核学习之启动程序模块

linux内核学习之启动程序模块

                              linux引导程序解析
    bootsect程序,驻留在磁盘的第一个扇区中(0磁道 0磁头 1 扇区)。在BIOS加点检测之后,该引导程序会自动地加载在内存的0x7c00处。
   bootsect程序在运行时,会首先将自身移动到0x90000处开始执行,并将从第二个扇区开始的共4个扇区大小的setup程序移动到,紧紧挨着该程序的0x90200处。 
 然后会使用BIOS中断int13 取当前引导盘的参数,接着在屏幕上显示Loading System的字符串,最后把磁盘上setup后面的system模块加载到内存0x10000开始的地方。随后确定根文件系统的设备号,若没有指定,则根据所保存的引导盘的每磁道扇区数目,判断出盘的类型和种类,并保存在设备号root_dev中。
   最后长跳转到setup程序的开始处,执行setup程序。

  下面为分析的源代码:
  

! SYSSIZE  =   0x3000
.global begtext , begdata , begbss , endtext , enddata , endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text
     SETUPLEN 
=   4           !  nr of setup - sectors
     BOOTSEG 
=   0x07c0
     INITSEG 
=   0x9000
     SETUPSEG 
=   0x9020
     SYSSEG 
=   0x1000
     ENDSEG 
=  SYSSEG  +  SYSSIZE
ROOT_DEV 
=   0x306
   
 entry start
 start:

! 将bootsect自身移动到0x90000处,并跳转开始执行
       mov ax , #BOOTSEG
       mov ds , ax
       mov ax , #INITSEG
       mov es , ax
       mov cx , #
256
       sub si , si
       sub di , di
       rep
          movw
       jmpi go , INITSEG
               

  
! 跳转过后修改段寄存器
   go:
       mov ax , cs
       mov ds , ax
       mov es , ax 
       mov ss , ax
       mov sp , #
0xFF00

 
! 利用BIOS中断INT 13将 setup模块,从磁盘第2个扇区开始读到0x90200开始处,共读4个扇区

   load_setup:
       mov dx , #
0x0000      !  drive  0   , head  0
       mov cx , #
0x0002      !  sector  2  , track  0
       mov bx , #
0x0200      !  address  =   512  ,  in  INITSEG 
       mov ax , #
0x0200   +  SETUP_LEN  !  service  2  , nr of sectors
       
int   0x13
       jnc ok_load_setup
       mov dx , #
0x0000         ! 出错则重新执行加载程序
       mov ax , #
0x0000
       
int   0x13
       j load_setup

! 利用int13 中断,得到磁盘驱动器的参数,特别是每道磁道的扇区数量
       mov ax , 
0x0800
       mov dl , 
0x00
       
int   0x13
! 重新设置es的值      
       mov sectors , cx
       mov ax , #INITSEG
       mov es , ax
! print some message
       mov ah , #
0x03      ! 读光标位置,返回光标位置在dx中
       xor bh , bh
       
int   0x10

       mov cx , #
24
       mov bx , #
0x0007
       mov bp , #msg1
       mov ax , #
0x1301
       
int   0x10
  
!  ok we have written the message ,现在开始将system模块加载到0x10000开始处
       mov ax , #SYSSEG
       mov es , ax
       call read_it         
! 读磁盘上system模块,es为输入参数
       call kill_motor      
! 关闭马达

! 确定根文件系统所在的设备号
       seg cs
       mov ax , root_dev
       cmp ax , #
0
       jne root_defined
      
       seg cs
       mov bx , sectors
       mov ax , #
0x0208
       cmp bx , #
15
       je root_defined
       mov ax , #
0x021c
       cmp bx , #
18
       je root_defined
  undef_root:
        jmp undef_root
  root_defined:
        seg cs
        mov root_dev , ax
        jmpi 
0  , SETUPSEG       ! 此处跳进setup程序

! 下面是将system模块加载进内存的子函数
      
  sread: .word 
1   +  SETUPLEN    ! sectors read of current track
  head:  .word 
0
  track: .word 
0
 
  
! 保证es在64kb处
  read_it :
     mov  ax , es
     test ax , #
0xfff
  die: jne die
     xor  bx , bx
  rp_read:            
! 接着判断是否已经读入全部的数据,比较当前所读的段是否就是系统数据末端所处的段
     mov ax , es      
! 如果不是,就跳转至下面的ok1标号处继续读数据  
     cmp ax , #ENDSEG
     jb ok1_read
     ret              
! 如果到达了系统末端,就结束此循环
 
  ok1_read:           
! 计算和验证当前磁道上需要读取的扇区数目,放在ax寄存器中,根据当前磁道还未读取的扇区数和 
                      
! 段内数据字节开始偏移的位置,计算如果全部读取这些未读扇区,所读的字节是否会超过64kb的限制
                      
! 若会超过,则根据此次最多能读入的字节数,反算出需要读取的扇区数。
  seg cs
  mov ax , sectors    
! 取每磁道的扇区数
  sub ax , sread      
! 减去当前磁道已读扇区数
  mov cx , ax         
! cx  =  ax 为当前磁道的未读扇区数
  shl cx , #
9           ! 当前未读的字节数
  add cx , bx         
! 此次操作之后,段内偏移地址现在的值
  jnc ok2_read        
! 若没有超过64kb,则跳转至ok2_read
  je  ok2_read

 
! 若加上此次将读取的磁道上所有未读扇区时会超过64kb,则反算出 可以最多加载多少 扇区数目 
  xor ax , ax
  sub ax , bx
  shr ax , #
9            ! 转换成扇区数目
 
ok2_read:
  
! 读当前磁道上指定开始扇区(cl)和需读扇区数(al)的数据到es:bx开始处。然后将磁道上已经读取的扇区数目
  
! 与磁道最大扇区数sectors作比较,如果小于sectors说明当前磁道上还有扇区未读
  call read_track
  mov cx , ax         
! cx等于当前操作以读扇区数目
  add ax , sread      
! 加上当前磁道已读扇区数目
  seg cs           
  cmp ax , sectors    
! 如果当前磁道上还有扇区未读,则跳转到ok3_read
  jne ok3_read
 
  
! 如果该磁道的当前磁头面所有扇区已经读完,则读该磁道的下一磁头面(1号磁头)上的数据,如果已经读完则去读下一磁道
  mov ax , #
1
  sub ax , head                
! 判断当前的磁头号,如果是0磁头,则去读1磁头
  jne ok4_read                 
! 读1号磁头
  inc track                    
! 读下一磁道
ok4_read:
  mov head , ax                
! 保存当前的磁头号
  xor ax , ax                  
! 清除当前磁道的已读扇区数
ok3_read:
  
! 如果当前磁道上还有未读的扇区,则首先保存当前磁道的已读扇区数目,然后调整存放数据的开始位置,若小于64kb边界值
  
! 则跳转到rp_read处,继续读数据
  mov sread , ax            
! 保存当前磁道的已读扇区数
  shl cx   , #
9               ! 上次已读扇区数 * 512字节
  add bx   , cx             
! 调整当前段内数据开始位置
  jnc rp_read     

  
! 否则说明已经读取64kb数据,此时调整当前段,为读下一段数据作准备
   mov ax , es
   add ax , #
0x1000
   mov es , ax
   xor bx , bx
   jmp rp_read

 

 
! read_track 子程序,读当前磁道上指定开始扇区和需读扇区数的数据到es:bx开始处。
 
! int   0x13  , ah  = 2  ,al = 需读扇区数,es:bx 缓冲区开始位置

 read_track:
       push ax
       push bx
       push cx
       push dx

       mov dx , track
       mov cx , sread
       inc cx
   
       mov ch , dl
       mov dx , head
       mov dh , dl 
       mov dl , #
0
       and dx , #
0x0100
       mov ah , #
2
       
int   0x13      
       jc bad_rt

       pop dx
       pop cx
       pop bx
       pop ax
       ret

      bad_rt: mov ax , #
0
       mov dx , #
0
       
int   0x13
       pop dx
       pop cx
       pop bx
       pop ax
       jmp read_track   

   
! 关闭软驱马达的子程序
 kill_motor:
       push dx
       mov  dx , #
0x3f2
       mov  al , #
0
       outb
       pop dx
       ret
       
 sectors:
      .word 
0          ! 存放当前启动软盘每磁道的扇区数      
 msg1:
      .
byte   13  ,  10
      .ascii 
" Loading system   "
      .
byte   13  ,  10  ,  13  , 10
.org 
508
 root_dev:
      .word ROOT_DEV    
! 这里存放根文件系统的所在设备号
 boot_flag:
      .word 
0xAA55

.text
  endtext:
.data
  enddata:
.bss
  endbss:

         

 

           2     setup.s程序分析

      setup.s是一个操作系统的加载程序,他的主要作用就是利用BIOS的读取机器系统数据,并将这些数据保存到0x90000开始的位置,(覆盖了bootsect程序所在的地方)。这些参数将被内核中相关程序使用。参数诸如光标位置 ,显存等信息。

   然后setup程序将system模块从 0x10000-0x8ffff(任务system模块不会超过512kb) ,整体移动到绝对内存地址为0x0000处。

  接着加载中断描述表寄存器idtr和全局描述表寄存器gdtr, 开启A20地址线,重新设置两个中断控制芯片8259A,将硬件中断号重新设置为0x20---0x2f。最后设置CPU的控制寄存器CR0,从而进入32位保护模式运行,并跳入到system模块最前面部分的head.s程序继续运行。

  为了能让head.s在32位保护模式下运行,在本程序临时设置了中断描述符表(IDT)和全局描述符表(GDT),

  在GDT中设置了当前代码段的描述符和数据段的描述符,在head.s中会重新设置这些描述符表。必须使用lgdt把描述符表的基地址告知CPU,再将机器状态字置位即可进入32位保护模式。

 

 

INITSEG  =   0x9000   ! we move boot here
SYSSEG 
=   0x1000
SETUPSEG 
=   0x9020   ! 本程序所在的段地址

.global begtext , begdata , begbss , endtext , enddata , endbss
.text
    begtext:
.data
    begdata:
.bss
    begbss:
.text
    
    entry start
    start:   
    
! 保存光标位置已备以后需要
    
! 这段代码使用BIOS中断取屏幕当前的光标位置,然后保存在内存0x90000处就可以使用
    mov ax , #INITSEG  
    mov ds , ax
    mov ah , #
0x03
    xor bh , bh
    
int   0x10           ! 利用BIOS中断 将当前光标位置存档到 dx
    mov [
0 ] , dx      ! 将光标位置存放在0x90000处

    
! 得到内存的大小值
    
! 利用BIOS中断0x15功能号 ah = 0x88取系统所含扩展内存大小并保存在内存0x90002处。
    mov ah , #
0x88  
    
int   0x15
    mov [
2 ] , ax
    
    
! 得到显示卡的属性
    
! 调用BIOS中断0x10,功能号ah = 0x0f
    
! 返回:ah = 字符列数;al = 显示模式;bh = 当前显示页
    
! 0x90004存放当前页 ,0x90006存放显示模式,0x90007存放字符列数

    mov ah , #
0x0f
    
int   0x10
    mov [
4 ] , bx   ! bh = display page
    mov [
6 ] , ax   ! al  = video mode , ah  =  window width
    


    
! 检查显示方式并取参数
     mov ah , #
0x12
     mov bl , #
0x10
     
int   0x10
     mov [
8 ] , ax
     mov [
10 ] , bx
     mov [
12 ] , cx
     
 
    
! 取得第一个硬盘的信息
     mov ax , #
0x0000
     mov ds , ax
     lds si , [
4   *   0x41 ]
     mov ax , #INITSEG
     mov es , ax
     mov di , #
0x0080
     mov cx , #
0x10
     rep 
      movsb

    
! 取得第二个硬盘
     mov ax , #
0x0000
     mov ds , ax
     lds si , [
4   *   0x46 ]   ! 取中断向量0x46的值,即hd1的参数值  ------>  ds:si
     mov ax , #INITSEG   
     mov es , ax
     mov di , #
0x0090      ! 传输目的地址  0x9000 : 0x0090
     mov cx , #
0x10
     rep 
        movsb
     
    
! 检查系统是否有第二个硬盘
     mov ax , #
0x01500
     mov dl , #
0x81
     
int   0x13
     jc no_disk1
     cmp ah , #
3
     je is_disk1
     
  no_disk1:              
! 第二块硬盘不存在,所以清空参数表
     mov ax , #INITSEG
     mov es , ax
     mov di , #
0x0090
     mov cx , #
0x10
     mov ax , #
0x00
     rep
       stosb
 is_disk1:
    
! 从此开始进入了保护模式
     cli
    
! 首先把system模块移动到正确的位置
     
    mov ax , #
0x0000
    cld
 do_move:
    mov es , ax 
    add ax , #
0x1000
    cmp ax , #
0x9000
    jz end_move
    mov ds , ax
    sub di , di
    sub si , si
    mov cx , #
0x8000
    rep
       movsw
    jmp do_move
  
 end_move:
    
  
! 在此处加载段描述符表,这里需要设置全局描述符表和中断描述符表
  mov ax , #SETUPSEG
  mov ds , ax
  lidt idt_48
  lgdt gdt_48
  
  
! 打开A20地址线
  call empty_8042
  mov  al , #
0xD1
  
out   # 0x64  , al 
  

  call empty_8042
  mov al , #
0xDF
  
out  # 0x60  , al
  call empty_8042

  
! 8259芯片主片端口是0x20  -   0x29  ,从片的端口是0xA0  -   0xA9  。
  mov al , #
0x11
  
out  # 0x20 , al
  .word 
0x00eb  ,  0x00eb
  
out  # 0xA0  , al
  .word 
0x00eb  ,  0x00eb
  
  
! 8259芯片设置中断号从0x20开始
  mov al , #
0x20
  
out  # 0x21  , al
  .word 
0x00eb  ,  0x00eb
  mov al , #
0x28
  
out  # 0xA1  , al
  .word 
0x00eb  ,  0x00eb
  mov al , #
0x04
  
out  # 0x21  , al 



  .word 
0x00eb 0x00eb
  mov al , #
0x02
  
out  # 0xA1  , al
  

  .word 
0x00eb 0x00eb
  mov al , #
0x01
  
out  # 0x21  , al


  .word 
0x00eb  ,  0x00eb
  
out  # 0xA1  , al
  .word 
0x00eb  ,  0x00eb
  mov al , #
0xFF
  
out  # 0x21  , al
  .word 
0x00eb  ,  0x00eb
  
out  # 0xA1  , al


  
! 下面设置并进入32位保护模式运行,首先加载机器状态字,也称控制寄存器cr0 
  
! 在设置该bit之后,随后的一条指令必须是一条段间跳转指令,一用于刷新当前指令队列
  
! 因为CPU在执行一条指令之前就已经从内存读取该指令并对其进行解码。
  
  mov ax , #
0x0001
  lmsw ax 
  jmpi 
0 , 8
  

  
! 下面这个子程序检查键盘命令队列是否为空
 empty_8042:
   .word 
0x00eb  ,  0x00eb
   
in  al , # 0x64
   test al , #
2
   jnz empty_8042
   ret
  
  
! 全局描述符表开始处
  gdt:
     .word 
0 , 0 , 0 , 0
     
  
! 代码段选择符的值  
     .word 
0x07FF
     .word 
0x0000
     .word 
0x9A00
     .word 
0x00C0
    
  
! 数据段选择符的值
     .word 
0x07FF
     .word 
0x0000
     .word 
0x9200
     .word 
0x00C0
  
  idt_48:
     .word 
0
     .word 
0  ,  0
  gdt_48:
     .word 
0x800
     .word 
512   +  gdt ,  0x9
  
  .text
   endtext:
  .data
   enddata:
  .bss
   endbss:



   三 head.s程序
       功能描述:
      head.s程序在被编译生成目标文件之后会与内核其他程序一起被链接成system模块,位于system模块最前面,所以称之为head程序的原因。system模块将被放置在磁盘上setup模块之后开始的扇区中,即从磁盘上第6个扇区开始位置。 linux内核一般大约有120KB ,在磁盘上大概占用240个扇区。
 
   之后我们将在保护模式下编程,head.s使用 as 和ld 编译器和连接器。 这段程序实际上处于绝对地址0处开始的地方,首先是加载各个数据段寄存器,重新设置中断描述符表idt,共256项,并使各个表项指向一个只报错误的哑中断子程序 ignore_int。 

   在设置了中断描述符表之后,本程序又重新设置了全局段描述符表gdt,主要是把gdt表设置在比较合理的地方,接着设置管理内存的分页处理机制,将页目录表放在绝对物理地址0开始处,紧随后边将放置可以寻址16MB内存的4个页表,并设置它们的表项。

  最后,head.s 程序利用返回指令将预先放置在堆栈中的 main.c程序的入口地址弹出,去执行main()程序。



  以下是部分代码分析
   (1)首先是建立IDT和GDT表
   
#建立IDT表

setup_idt:
  lea ignore_int   , 
% edx
  movl $
0x00080000  ,  % eax  
  movw 
% dx ,  % ax
  movw $
0x8E00  ,  % dx
  lea _idt , 
% edi
  mov $
256  ,  % ecx 
 
rp_sidt:  
  movl 
% eax , ( % edi)  #eax的高16位是选择符 ,低16位是段内偏移的低16位
  movl 
% edx ,  4 ( % edi) #edx的高16位是段内偏移地址的高16位,低16位是权限位 
  addl $
8  ,  % edi
  dec 
% ecx            #重复设置总共256个中断描述符
  jne rp_sidt
  lidt idt_descr      #加载中断描述符表寄存器
  ret

setup_gdt:  
  lgdt gdt_descr     #加载全局描述符表寄存器
  ret 

 由代码可知, 256个idt均指向了一个哑中断ignore_int,加载gdt的过程更简单,只是将gdt描述符表的基地址加载进gdtr寄存器。

(2) 页目录表和页表之间的映射
    在linux1.1中 , 在绝对内存地址的0x000000处是一个大小为4k的页目录表,然后在内存0x1000,0x2000,0x3000,0x4000处分别是4个页表的首地址,也就是说linux0.11仅仅能访问16M的         内存空间,内存映射的算法如下:
 首先在内存0x00000即页目录表设置4个页表首地址,注意添加权限属性。 然后从最后一个页表的最后一个表项,倒序的将物理地址添加进页表中,最后一个页表项的内容是 64M - 4096 + 7 (7表示页面在内存,且用户可读可访问)。
  .align  2
setup_paging:      #首先为5页内存进行清空处理
                   #1个页目录表,4个页表 
    movl $
1024   *   5  ,  % ecx
    xorl 
% eax ,  % eax
    xorl 
% edi ,  % edi
    
    cld ; rep ; stosl
   
    #页目录中只需要4个页目录, 7是属性,表示该页存在内存中,且用户可以访问
    movl $pg0 
+   7  , _pg_dir
    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 #16M  -   4096   + 7  
    std
    stosl
    subl $
0x1000  ,  % eax
    jge 1b


    #设置页目录表基址寄存器cr3的值,指向页目录表。cr3中保存的是页目录表的物理地址
    xorl 
% eax ,  % eax
    movl 
% eax ,  % cr3
    #设置启动分页处理
    movl 
% cr0 ,  % eax
    orl 
% 0x80000000  ,  % eax 
    movl 
% eax ,  % cr0


    #该返回指令执行先前压入堆栈的main函数的入口地址
    ret


 (3)head中还需要为程序跳转进main函数作准备,当完成了页面设置的时候,上面代码的最后一句ret,即
     完成了跳入main函数中继续执行。 设置main函数的代码如下:
   
  
 .org  0x5000  #定义下面的内存数据块从偏移0x5000处开始

_tmp_floppy_area:
  .fill 
1024  ,  1  , 0  #共保留1024项,每项1字节,

#下面这些代码为跳转到main函数中,做准备

after_page_tables:
 pushl $
0           #这些是main函数的参数
 pushl $
0
 pushl $
0
 pushl $L6         #main函数的返回地址
 pushl $_main      #_main 是编译程序对main的内部表示法
 jmp setup_paging  #跳转到建立页表映射

L6:
     jmp L6 

  可以看出执行完setup_paging之后的ret指令,将把_main 加载进 指令寄存器,进行执行。


(4)完整的代码如下


.text
  .global _idt , _gdt , _pg_dir , _tmp_floppy_area
_pg_dir:  #页目录将会设置在这里,所以该程序会被覆盖掉
 
startup_32:
  movl $
0x10  ,  % eax #0x10已经是全局描述符的在描述符表中的偏移值
  mov 
% ax ,  % ds    
  mov 
% ax ,  % es
  mov 
% ax ,  % fs
  mov 
% ax ,  % gs

  lss _stack_start , 
% esp #设置_stack_start -----> ss:esp

  call setup_idt    #调用设置中断描述符表的子程序
  call setup_gdt    #调用设置全局描述符表的子程序

  movl $
0x10  ,  % eax  #重新加载所有的段寄存器
  mov 
% ax ,  % ds
  mov 
% ax ,  % es
  mov 
% ax ,  % fs
  mov 
% ax ,  % gs
  

  lss _stack_start , 
% esp
  
  #以下代码用来测试A20地址线是否已经打开,采用的方法是向内存0x000000处写入任意的一个数值
  #然后看内存地址0x100000是否也是这个数值,如果一样的话,就说明A20地址线没有打开
  
    xorl 
% eax ,  % eax
1 :  incl  % eax
    movl 
% eax ,  0x000000   #地址就不需要加$
    cmpl 
% eax ,  0x100000
    je 1b
  

   movl 
% cr0 ,  % eax       #
   andl $
0x80000011  ,  % eax
   orl $
2  ,  % eax
   movl 
% eax ,  % cr0
   call check_x87
   jmp after_page_tables
 
 check_x87:
   fninit   #向协处理器发送初始化命令
   fstsw 
% ax
   cmpb $
0  ,  % al 
   je 1f 
   movl 
% cr0 ,  % eax
   xorl $
6  ,  % eax
   movl 
% eax ,  % cr0
   ret
  
 .align 
2
  
1 : . byte   0xDB  ,  0xE4
      
#建立IDT表

setup_idt:
  lea ignore_int   , 
% edx
  movl $
0x00080000  ,  % eax  
  movw 
% dx ,  % ax
  movw $
0x8E00  ,  % dx
  lea _idt , 
% edi
  mov $
256  ,  % ecx 
 
rp_sidt:  
  movl 
% eax , ( % edi)  #eax的高16位是选择符 ,低16位是段内偏移的低16位
  movl 
% edx ,  4 ( % edi) #edx的高16位是段内偏移地址的高16位,低16位是权限位 
  addl $
8  ,  % edi
  dec 
% ecx            #重复设置总共256个中断描述符
  jne rp_sidt
  lidt idt_descr      #加载中断描述符表寄存器
  ret

setup_gdt:  
  lgdt gdt_descr     #加载全局描述符表寄存器
  ret 

 #这里设置四张页表,可以用来方位16M的内存空间
 #每个页表大小为4k,每项为4字节,一张页表可以映射1024 
*  4kb的内存空间 ,即4M
 .org 
0x1000
  pg0:

 .org 
0x2000
  pg1:

 .org 
0x3000
  pg2:
 
 .org 
0x4000
  pg3:
 
 .org 
0x5000  #定义下面的内存数据块从偏移0x5000处开始

_tmp_floppy_area:
  .fill 
1024  ,  1  , 0  #共保留1024项,每项1字节,

#下面这些代码为跳转到main函数中,做准备

after_page_tables:
 pushl $
0           #这些是main函数的参数
 pushl $
0
 pushl $
0
 pushl $L6         #main函数的返回地址
 pushl $_main      #_main 是编译程序对main的内部表示法
 jmp setup_paging  #跳转到建立页表映射

L6:
     jmp L6 

 #下面是默认的中断向量句柄
int_msg:
  .asciz 
" Unknown interrupt\n\r "
  .align 
2  
 ignore_int:
   pushl 
% eax
   pushl 
% ecx 
   pushl 
% edx

   push  
% ds   #入栈占4个字节
   push  
% es
   push  
% fs
  
   movl $
0x10  ,  % eax
   mov  
% ax  ,  % ds
   mov  
% ax  ,  % es
   mov  
% ax  ,  % fs
   pushl $int_msg   #向printk函数传递参数 
   call _printk     #该函数在
/ kernel / printk.c中
   

   popl 
% eax        #返回值
   pop 
% fs
   pop 
% es
   pop 
% ds 
   popl 
% edx
   popl 
% ecx
   popl 
% eax
   iret




   .align 
2
setup_paging:      #首先为5页内存进行清空处理
                   #1个页目录表,4个页表 
    movl $
1024   *   5  ,  % ecx
    xorl 
% eax ,  % eax
    xorl 
% edi ,  % edi
    
    cld ; rep ; stosl
   
    #页目录中只需要4个页目录, 7是属性,表示该页存在内存中,且用户可以访问
    movl $pg0 
+   7  , _pg_dir
    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 #16M  -   4096   + 7  
    std
    stosl
    subl $
0x1000  ,  % eax
    jge 1b


    #设置页目录表基址寄存器cr3的值,指向页目录表。cr3中保存的是页目录表的物理地址
    xorl 
% eax ,  % eax
    movl 
% eax ,  % cr3
    #设置启动分页处理
    movl 
% cr0 ,  % eax
    orl 
% 0x80000000  ,  % eax 
    movl 
% eax ,  % cr0


    #该返回指令执行先前压入堆栈的main函数的入口地址
    ret

    #140行将压入堆栈的main指令弹出,并跳到main函数中去
 .align 
2
   .word 
0  
    idt_descr:
       .word 
256   *   8   -   1
       .
long  _idt
 .align 
2
    .word 
0
  gdt_descr:
     .word 
256   *   8   -   1
     .
long  _gdt

 .align 
3
 _idt: .fill 
256  ,  8  ,  0   #共256项,每项8字节,初始化为0

 _gdt: .quad 
0x0000000000000000
       .quad 
0x00c09a0000000fff
       .quad 
0x00c0920000000fff
       .quad 
0x0000000000000000
       .fill 
252  ,  8  ,  0


 
 当CPU运行在保护模式下,某一时刻GDT和LDT分别只能有一个,分别有寄存器GDTR和IDTR指定它们的表基址。在某一时刻当前LDT表的基址由LDTR寄存器的内容指定并且使用GDT中的某个描述符来加载,即LDT也是由GDT中的描述符来决定。但是在某一时刻同样也只是由其中的一个被视为活动的。一般对于每个任务使用一个LDT,在运行时,程序可以使用GDT中的描述符以及当前任务的LDT中的描述符
















 

 

 


 

 

 

  

 

     

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

 

 

    

 

 

 

 

 

 

 

 

 

 

 


 

 


 

 

   


 
   

 

 

 

 

 

 

   
  

 


 

 


             

 

 

 

      

 

 

 

 

 

 

      
      

 

 

 

 

 

 

 
       
     

 

 

 

 

 

 

 

 

 

 

 

    
      



















你可能感兴趣的:(linux内核学习之启动程序模块)