第4章 进入保护模式(设置GDT全局描述符表)

进入保护模式的步骤

  1. 关中断;
  2. 打开A20地址线,使之能访问1M以上的内存;
  3. 设置GDT; GDT知识可查看《Intel微处理器 第8版》p541 也可以查看 Intel开发手册
  4. 加载GDT;
  5. 进入保护模式,跳转到setup.s

boot.s的源代码

.text
    .global  start
    .include "kernel.inc"
    .code16
start:
    jmp $0x0, $code
msg:
    .string "1kos booting......\x0"
code:
    movw    $0xb800,%ax        #显存地址在0xb8000
    movw    %ax,    %es        
    xorw    %di,    %di        #es:di = 0xb8000
    xorw    %ax,    %ax
    movw    %ax,    %ds        #ds:si = msg
    movw    $msg,   %si
    cld                #si和di自增
    movb    $0x07,  %ah        #字符属性:黑底白字

    #打印msg信息
print_c:
    cmp        $0x0,   (%si)
    je        load
    lodsb            #ds:[si] -> al, ++si
    stosw            #ax -> es:[di], di += 2  #al=字符 ah=属性
    jmp        print_c


    #读取kernel到0x7e00上
load:
    #设置临时栈
    xorw    %ax,    %ax
    movw    %ax,    %ds        # ds = 0x0
    movw    %ax,    %ss        # ss = 0x0,栈基址
    movw    $KERNEL_STACK_BOT,%sp        # sp = 0x7c00,栈地址    
    
    #设置rd_kern的参数
    #设置ES:BX参数
    movw    $KERNEL_START_SEGMENT, %ax
    movw    %ax,    %es        # es = 0x7e0
    xorw    %bx,    %bx        # bx = 0x0,  es:bx = 0x7e00

    #读取的起始扇区号(逻辑扇区起始扇区号是0)
    movw    $0x1,   %si        # si = 0x1 ,是rd_sect的参数
    
    #读取1400个扇区,共700K
    movw    $KERNEL_SECT_NUMBER,  %cx

    #调用rd_kern函数
    #读取700K的kernel到0x7e00上
    call    rd_kern    

    #关中断
    cli

    #探测内存情况,并把内存探测结果写入特定位置中
    call    check_mem

    #打开A20地址线
enable_a20:
    inb        $0x64,  %al
    testb   $0x2,   %al
    jnz        enable_a20
    movb    $0xdf,  %al
    outb    %al,    $0x64

    #设置GDT
    lgdt    gdt_48

    #进入保护模式
    movl    %cr0,   %eax
    orl        $0x1,   %eax
    movl    %eax,   %cr0

    ljmp    $CODE_SEL,  $0x7e00
    





#==============================================================
#函数名:rd_kern
#功能:读取软驱A上连续多个扇区
#参数1:  ES:BX    读取数据存放的目的地址
#参数2:  SI        指定起始逻辑扇区号    
#参数3:  CX        欲读取的扇区数目
rd_kern:
    pushw   %ax
rd_k1:
    call    rd_sect
    movw    %es,    %ax        #
    addw    $0x20,  %ax        #
    movw    %ax,    %es        # es:bx += 512
    incw    %si            # ++si
    loop    rd_k1        # if(cx != 0) goto rd_k1;
    popw    %ax
    ret

#==============================================================

#==============================================================
#函数名:rd_sect
#功能:读取软驱A上的1个扇区
#参数1:  ES:BX    读取数据存放的目的地址
#参数2:  SI        指定逻辑扇区号    

rd_sect:
    pushw   %ax
    pushw   %dx
    pushw   %cx
    pushw   %bx

    movw    %si,    %ax
    movb    $18,    %bl
    divb    %bl            # al <-- (ax)/(bl) 的商
                # ah <-- (ax)/(bl)的余数
    incb    %ah        # 因为软驱的物理起始扇区号从1开始,而逻辑扇区号从0开始,所以要增1
    movb    %ah,    %cl        # 起始扇区号,(int 13H ah=02h 中断的参数)
    xorb    %ah,    %ah
    movb    $2,        %bl
    divb    %bl
    movb    %ah,    %dh        # 磁头号,(int 13H ah=02h 中断的参数)
    movb    %al,    %ch        # 磁道号,(int 13H ah=02h 中断的参数)    
    popw    %bx

re_rd: 
    movb    $0x2,   %ah        # 读磁盘中断
    movb    $1,        %al        # 读取1个扇区
    movb    $0x0,   %dl        # 读取软驱A
    int $0x13
    jc re_rd

    popw    %cx
    popw    %dx
    popw    %ax

    ret

#==============================================================

#==============================================================
#函数名:check_mem
#功能:使用E820方法探测内存大小
#参数1: eax
#参数2: ebx
#参数3: ecx
#参数4: edx
#参数5: es:di

check_mem:
    pushl   %eax
    pushl   %ebx
    pushl   %ecx
    pushl   %edx
    pushw   %es
    pushw   %di

    xorw    %ax,    %ax
    movw    %ax,    %es
    movl    $0x0,   %ebx
    movw    $E820MAP, %di
    
1:
    movl    $0x0E820,    %eax
    movl    $20,    %ecx
    movl    $0x534D4150,%edx
    int        $0x15
    jc        ck_fail
    addw    $20,    %di
    incl    E820NR(,1)
    cmpl    $0,        %ebx
    jne        1b
    jmp        ck_ok
ck_fail:
    movl    $0,        E820NR(,1)
ck_ok:
    popw    %di
    popw    %es
    popl    %edx
    popl    %ecx
    popl    %ebx
    popl    %eax

    ret



#==============================================================
.org 0x100, 0xff
#==============================================================
#gdt在内存中的地址是0x7c00 + 0x100 = 0x7d00
gdt:
    .quad   0x0000000000000000    #GDT表第一项为空
    
    .word   0x0ffff        #边界限制为4GB
    .word   0x0000        #基地址 = 0x00000000
    .word   0x9a00        #代码段,可 读/执行
    .word   0x00cf        # 4K边界粒度;386


    .word   0x0ffff        #边界限制为4GB
    .word   0x0000        #基地址 = 0x00000000
    .word   0x9200        #数据段,可 读/写
    .word   0x00cf        # 4K边界粒度;386


    .quad   0x0000000000000000    #GDT表第四项备用
    .quad   0x0000000000000000    #GDT表第五项备用
gdt_48:
    .word   .-gdt-1
    .long   gdt

#==============================================================
.org    0x1fe, 0x90
.word    0xaa55

kernel.inc的源代码

.set CODE_SEL,    0x08    #内核代码段选择符
.set DATA_SEL,    0x10    #内核数据段选择符
.set KERNEL_SECT_NUMBER,    1400  #内核长度,以扇区为单位
.set KERNEL_STACK_BOT,        0x7c00  #内核堆栈
.set KERNEL_START_SEGMENT,  0x7e0   #内核起始段,也就是内核会被拷贝到0x7e00上
.set GDT_ADDR,            0x7d00  #GDT在内存中的地址 0x7d00 = 0x7c00 + 0x100
.set NR_SYSTEM_CALLS,        11        #现在系统调用的数目
.set USER_CODE_SEL,        0x07    #用户代码段选择符
.set USER_DATA_SEL,        0x0f    #用户数据段选择符


.set E820NR ,            0x0FFC  #内存映射项个数,存放地址
.set E820MAP,            0x1000  #内存探测结果项,存放地址

Leave a Reply

Your email address will not be published. Required fields are marked *