Linux0.11内核--启动引导代码分析setup.s

来源:互联网 发布:java与php哪个好 编辑:程序博客网 时间:2024/05/18 00:18

setup的整体作用:

首先利用BIOS中断读取机器的数据,将其保存在地址0x9000:0x0000处,覆盖了原来bootsect.s代码所在的位置,由于bootsetc模块的代

码运行完毕,已经没有其他的用处了,所以可以将其覆盖掉。

然后关闭中断,setup将system模块整体移动至内存起始处,原来位于0x1000:0x0000,由于movsb和movsw指令的功能是移动一个字节或

者一个字,源地址由寄存器ds:si指定,目的地址由es:di指定。

指令cld指明di和si的方向是递增,而std是递减。

 

由于system模块大小超过64K(实模式一个段的最大偏移),而移动指令需要指明段寄存器,所以需要分段移动,下面是移动代码,代码语法是

AT&T改写的

[cpp] view plain copy
  1. # first we move the system to it's rightful place  
  2.     mov $0x0000, %ax  
  3.     cld         # 'direction'=0, movs moves forward  
  4. do_move:  
  5.     mov %ax, %es    # destination segment  
  6.     add $0x1000, %ax  
  7.     cmp $0x9000, %ax  
  8.     jz  end_move  
  9.     mov %ax, %ds    # source segment  
  10.     sub %di, %di  
  11.     sub %si, %si  
  12.     mov     $0x8000, %cx  
  13.     rep  
  14.     movsw  
  15.     jmp do_move  
  16. # then we load the segment descriptors  
  17. end_move:  
 

然后设置数据段寄存器ds并加载中断描述符和全局描述符

[cpp] view plain copy
  1. end_move:  
  2.     mov $SETUPSEG, %ax  # right, forgot this at first. didn't work :-)  
  3.     mov %ax, %ds  
  4.     lidt    idt_48      # load idt with 0,0  
  5.     lgdt    gdt_48      # load gdt with whatever appropriate  
 

lidt和lgdt的操作数是6个字节

第一第二字节表示描述符表的长度值,第三到第六字节表示的是32位的线性基地址(描述符在内存的中的位置)

GDT格式的详情请见

 

Linux0.11内核--32位保护模式GDT(全局描述符表)

 

 

idt_48和gdt_48的内容如下:

[cpp] view plain copy
  1. idt_48:  
  2.     .word   0           # idt limit=0  
  3.     .word   0,0         # idt base=0L  
  4. gdt_48:  
  5.     .word   0x800           # gdt limit=2048, 256 GDT entries  
  6.     .word   512+gdt, 0x9        # gdt base = 0X9xxxx,   
  7.     # 512+gdt is the real gdt after setup is moved to 0x9020 * 0x10  
 

其中gdt_48中第一个字节表示描述符表中的描述符的个数,由于GDT表在内存中占用2KB的内存空间,一个GDT有64位(8个字节),故有

256项,后面四个字节表示一个32位的线性地址0x0009<<16+0x0200+gdt

实际就是本程序中的gdt的地址。

下面开启A20地址线

操作系统从实模式转换成保护模式时需要开启A20地址线,那什么是A20地址线呢?

在8086/8088CPU中,地址线只有20根,而寄存器都是16位的,为了能够使用20位地址线,它采用段地址:偏移地址的形式,寻址范围

就可以达到1M,但是到了80286地址线达到24根,80386达到32根寻址能力达到4GB。为了向后兼容,IBM采用了一个控制方法:用键盘

控制器上的一个空闲的控制线来控制。即A20控制线。当A20打开时可以使用20-31的地址线,如果A20没有打开,后面的地址线就不可以使

用,全部为0。

 

 

所以,激活A20地址线的流程为:

    1.关闭中断;

    2.等待8042 Input buffer为空;

    3.发送禁止键盘操作命令;

    4.等待8042 Input buffer为空;

    5.发送读取8042 Output Port命令;

    6.等待8042 Output buffer有数据;

    7.读取8042 Output buffer,并保存得到的字节;

    8.等待8042 Input buffer为空;

    9.发送Write 8042 Output Port命令到8042 Input buffer;

    10.等待8042 Input buffer为空;

    11.将从8042 Output Port得到的字节的第2位置1(或清0),然后写入8042 Input buffer;

    12.等待,直到8042 Input buffer为空为止;

    13.发送允许键盘操作命令到8042 Input buffer;

    14. 打开中断。

下面是as86的汇编代码(linux0.11内核中)

[cpp] view plain copy
  1. call    empty_8042  
  2. mov al,#0xD1        ! command write  
  3. out #0x64,al  
  4. call    empty_8042  
  5. mov al,#0xDF        ! A20 on  
  6. out #0x60,al  
  7. call    empty_8042  
  8.   
  9. mpty_8042:  
  10. .word   0x00eb,0x00eb  
  11. in  al,#0x64    ! 8042 status port  
  12. test    al,#2       ! is input buffer full?  
  13. jnz empty_8042  ! yes - loop  
  14. ret  
 

然后对中断进行编程

实际上就是设置8259中断控制器的ICW字

[c-sharp] view plain copy
  1. mov $0x11, %al      # initialization sequence(ICW1)  
  2.                 # ICW4 needed(1),CASCADE mode,Level-triggered  
  3. out %al, $0x20      # send it to 8259A-1  
  4. .word   0x00eb,0x00eb       # jmp $+2, jmp $+2  
  5. out %al, $0xA0      # and to 8259A-2  
  6. .word   0x00eb,0x00eb  
  7. mov $0x20, %al      # start of hardware int's (0x20)(ICW2)  
  8. out %al, $0x21      # from 0x20-0x27  
  9. .word   0x00eb,0x00eb  
  10. mov $0x28, %al      # start of hardware int's 2 (0x28)  
  11. out %al, $0xA1      # from 0x28-0x2F  
  12. .word   0x00eb,0x00eb       #               IR 7654 3210  
  13. mov $0x04, %al      # 8259-1 is master(0000 0100) --/  
  14. out %al, $0x21      #               |  
  15. .word   0x00eb,0x00eb       #            INT    /  
  16. mov $0x02, %al      # 8259-2 is slave(       010 --> 2)  
  17. out %al, $0xA1  
  18. .word   0x00eb,0x00eb  
  19. mov $0x01, %al      # 8086 mode for both  
  20. out %al, $0x21  
  21. .word   0x00eb,0x00eb  
  22. out %al, $0xA1  
  23. .word   0x00eb,0x00eb  
  24. mov $0xFF, %al      # mask off all interrupts for now  
  25. out %al, $0x21  
  26. .word   0x00eb,0x00eb  
  27. out %al, $0xA1  
 

其中.word 0x00eb是直接进跳转指令的操作码,带一个字节的相对移位值,移动范围是-127--+127CPU通过将这个移位值加到EIP寄

存器,形成新的地址。实际上就是起到延迟的作用。

最后,进入32位的保护模式运行,首先加载机器状态字,CR0,其比特位0置1表示CPU工作在保护模式下。

下面分别是AS86格式和AT&T汇编格式

[cpp] view plain copy
  1. mov ax,#0x0001  ! protected mode (PE) bit  
  2. lmsw    ax      ! This is it!  
  3. jmpi    0,8     ! jmp offset 0 of segment 8 (cs)  
 

 

[cpp] view plain copy
  1. mov %cr0, %eax  # get machine status(cr0|MSW)     
  2. bts $0, %eax    # turn on the PE-bit   
  3. mov %eax, %cr0  # protection enabled  
  4.               
  5.             # segment-descriptor        (INDEX:TI:RPL)  
  6. .equ    sel_cs0, 0x0008 # select for code segment 0 (  001:0 :00)   
  7. ljmp    $sel_cs0, $0    # jmp offset 0 of code segment 0 in gdt  
 


0 0
原创粉丝点击