实模式到保护模式 用户态

编译器:nasm
虚拟机:bochs
操作系统:macosx


boot.s是引导程序,负责加载init.s。
开机启动后bios把boot.s加载在0x7c00处,并从这开始运行,boot.s负责用bios中断把init.s加载到0x10000处,然后将init.s移动到0x0处(方便设置gdt,不直接从磁盘加载到这是因为加载用的是bios的中断,bios的中断在0x0开始的地方),然后调入保护模式,从0x0开始运行。

init.s中重新设置了gdt,多了一个内存显示段用来在屏幕上输出字符。并且定义了一个用户任务,这个任务负责不停在屏幕打印字符’A’。

在init.s中定义了一个任务,任务相关的描述是tss,ldt。
ldt中定义了任务的代码段,数据段。
tss中存储了各种寄存器,内核态堆栈,ldt描述符等信息。

在init.s中通过在堆栈中人工建立一个中断返回场景,用iret指令,就可以跳入用户任务中,这样就从内核态,进入了用户态。

引导程序boot.s:

BOOTSEG equ 0x07c0
SYSSEG equ 0x1000
SYSLEN equ 17

section .text vstart=0

jmp BOOTSEG:start

start:
    mov ax, cs
    mov ss, ax
    mov ds, ax

    ;读磁盘
    mov ax, 0x0200+SYSLEN
        mov cx, 0x0002
        mov dx, 0x0000
        mov bx, SYSSEG
        mov es, bx
        xor bx, bx
        int 0x13

        jnc ok_load

ok_load:
    ;移动到内存0x0处(原地址ds:si,目的地址es:di)
    cli
    mov ax, SYSSEG
    mov ds, ax
    xor ax, ax
    mov es, ax
    mov cx, SYSLEN*128
    sub si, si
    sub di, di
    rep movsw

    ;加载idt和gdt基地址寄存器idtr和ldtr
    mov ax, BOOTSEG
    mov ds, ax
    lidt [idt_48]
    lgdt [gdt_48]

    ;设置cr0寄存器到pe位,进入保护模式
    mov ax, 0x0001
    lmsw ax
    jmp 8:0

gdt:
    dw 0,0,0,0

    dw 0x07ff ;代码段
    dw 0x0000
    dw 0x9a00
    dw 0x00c0

    dw 0x07ff ;数据段
    dw 0x0000
    dw 0x9200
    dw 0x00c0

idt_48:
    dw 0
    dw 0,0

gdt_48:
    dw 0x7ff
    dw 0x7c00+gdt,0

times 510-($-$$) db 0
dw 0xaa55

“内核”程序init.s

[BITS 32]

SCRN_SEL equ 0x1f
LDT0_SEL equ 0x28
TSS0_SEL equ 0x20

section .text vstart=0

;现在是保护模式
;
;重新定义gdt
;除了代码段、数据段、显示内存段(0xb8000开始的显存),还定义了一个任务0(TSS0,LDT0)
;
;任务0中执行不断打印字母'A'的程序(运行在用户态)
;
;如何从内核代码跳入用户代码:
;   设置tr,即tss0的段选择符
;   设置ldt,即ldt0的段选择符
;   将原ss,原esp,EFLAGS,cs,eip依次入栈,
;     然后iret,执行中断返回指令,从而切换到特权级3的任务0中执行

start_up:
    mov eax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    lss esp, [init_stack]   

    lgdt [lgdt_opcode]  

    mov eax, 0x10
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        lss esp, [init_stack]

    ;移动到任务0中,在堆栈中人工建立中断返回时到场景
    pushf
    and dword [esp], 0xffffbfff 
    popf
    mov eax, TSS0_SEL
    ltr ax
    mov eax, LDT0_SEL
    lldt ax
    sti ;开启中断
    push dword 0x17
    push init_stack
    pushf
    push dword 0x0f
    push fn
    iret


scr_loc dd 0 ; 如果dd大于2000就复位为0,因为屏幕是25*80=2000,一屏最多显示2000个字符

lgdt_opcode:
    dw (end_gdt-gdt)-1
    dw gdt, 0

;gdt:   dq 0x0000_0000_0000_0000 ;gdt表。第一项不用
;   dq 0x00c0_9a00_0000_07ff ;内核代码段。其选择符是0x08
;   dq 0x00c0_9200_0000_07ff ;内核数据段。其选择符是0x10
;   dq 0x00c0_920b_8000_0002 ;显示内存段。其选择符是0x18

gdt:    dw 0,0,0,0            ;gdt表。第一项不用 
    ;dq 0x0000000000000000;why not work?    

    dw 0x07ff         ;内核代码段。其选择符是0x08
    dw 0x0000
    dw 0x9a00
    dw 0x00c0

    dw 0x07ff         ;内核数据段。其选择符是0x10
    dw 0x0000
    dw 0x9200
    dw 0x00c0

    dd 0x80000002         ;显示内存段。其选择符是0x18
    dd 0x00c0920b   

    dw 0x68, tss0, 0xe900, 0x0 ;tss0段的描述符。其选择符是0x20
    dw 0x40, ldt0, 0xe200, 0x0 ;ldt0段的描述符。其选择符是0x28

end_gdt:
    times 128 dd 0        ;内核堆栈段
init_stack:
    dd init_stack         ; esp
    dw 0x10           ; ss

ldt0:   dd 0x00000000         ;任务0的局部描述符表
    dd 0x00000000         ;第一个描述符,不用

    dd 0x000003ff         ;局部代码段描述符。其选择符是0x0f
    dd 0x00c0fa00         

    dd 0x000003ff         ;局部数据段描述符。其选择符是0x17
        dd 0x00c0f200         

    dd 0x80000002         ;显示内存段。其选择符是0x1f
        dd 0x00c0f20b

tss0:   dd 0
    dd krn_stk0,0x10
    dd 0,0,0,0,0
    dd 0,0,0,0,0
    dd 0,0,0,0,0
    dd 0,0,0,0,0,0
    dd LDT0_SEL, 0x8000000 

    times 128 dd 0        ;任务0的内核栈空间
krn_stk0:

;任务0的代码
fn: mov eax, 0x17
    mov ds, eax
    mov al, 'A'
    call print
    jmp fn

print:
    ;打印字母al中的字母
    mov ebx, SCRN_SEL
    mov gs, bx
    mov ebx, [scr_loc]
    shl ebx, 1
    mov byte [gs:ebx], al
    mov byte [gs:ebx+1], 07
    shr ebx, 1
    inc ebx
    cmp ebx, 2000
    jb l
    mov ebx, 0
l:  mov [scr_loc], ebx
    ret

github real2protect源码

参考:赵炯《Linux内核完全剖析》

这里写图片描述

猜你喜欢

转载自blog.csdn.net/familyshizhouna/article/details/81120455