操作系统真象还原-读磁盘与保护模式

操作系统真象还原-读磁盘与保护模式

0x00 代码

​ 头文件boot.inc

;一些宏,预处理指令
;loader的加载地址和磁盘位置
LOADER_BASE_ADDR equ 0x0900
LOADER_START_SECTOR equ 0x02
;gdt描述符属性
DESC_G_4K equ 1_000_0000_0_00_0_0000_00000000b
DESC_D_32 equ 100_0000_0_00_0_0000_00000000b
DESC_L equ 0;不设置L位 不是64位系统
DESC_AVL equ 0;不使用AVL位

DESC_LIMIT_CODE2 equ 1111_0_00_0_0000_00000000b;段界限16-19位
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2;平坦模型 都是全1,最终合成的段界限0xfffff 粒度4K 即(0xfffff+1)*4096= 4GB 
DESC_LIMIT_VIDEO2 equ 0;显存区域不是平坦模型
DESC_P equ 1_00_0_0000_00000000b;表示该段存在内存里,由cpu负责检查,软件设置
DESC_DPL_0 equ 00_0_0000_00000000b;dpl0
DESC_DPL_1 equ 01_0_0000_00000000b;dpl1
DESC_DPL_2 equ 10_0_0000_00000000b;dpl2
DESC_DPL_3 equ 11_0_0000_00000000b;dpl3
DESC_S_CODE equ 1_0000_00000000b;非系统段 S为1 
DESC_S_DATA equ DESC_S_CODE
DESC_S_SYS equ 0_0000_00000000b;系统段

DESC_TYPE_CODE equ 1000_00000000b;可执行 不可读 非依从 ACCESSED 位由cpu访问后置1
DESC_TYPE_DATA equ 0010_00000000b;不可执行 向上扩展 可写 ACCESSED 0

DESC_CODE_HIGH4 equ (0x00<<24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0X00  ;合成高32位中的中间16位 基地址全0
DESC_DATA_HIGH4 equ (0x00<<24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00   ;平坦模型 4GB 基地址全0
DESC_VIDEO_HIGH4 equ (0x00<<24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + DESC_S_DATA +DESC_TYPE_DATA + 0X0b ;

;---选择自 SELECTOR
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b

​ mbr.s 端口读磁盘

%include "boot.inc"
;引入头文件,和c中.h没区别 直接加进来,主要写一些宏
SECTION MBR vstart=0x7c00
    mov ax,0
    mov ds,ax
    mov ss,ax
    mov sp,0x7c00

    call .clear
    mov ax,0
    mov ds,ax
    mov bx,message
    mov dh,1
    mov dl,0
    call .print_string

    mov ax,0
    mov ds,ax
    mov eax,LOADER_START_SECTOR
    mov si,LOADER_BASE_ADDR
    mov cx,4;读4个扇区到内存
    call .rd_disk
    ;以下将print_string的入口地址保存在0x7c00+506,4字节 低2字节段地址 高2字节偏移地址
    mov bx,addr
    mov word [ds:bx],.print_string;不需要再加0x7c00 因为vstart 0x7c00了
    mov word [ds:bx+2],0
    jmp 0:LOADER_BASE_ADDR

.clear:
    push si
    push es
    push cx

    mov si,0xb800
    mov es,si
    mov si,0
    mov cx,2000
.clear_loop:
    mov byte [es:si],0
    add si,2
    loop .clear_loop
    pop cx
    pop es
    pop si
    ret
.print_string:; ds:bx 字符串地址 dh 行 dl 列
    push ax
    push bx
    push cx
    push dx
    push es
    push si

    mov ax,0xb800
    mov es,ax
    mov al,160
    mul dh
    add dl,dl;
    mov dh,0
    add ax,dx
    mov si,ax;si即显存地址

.print_loop:
    mov al,[ds:bx]
    cmp al,0
    je .print_end
    mov [es:si],al
    inc bx
    add si,2
    jmp .print_loop

.print_end:
    pop si
    pop es
    pop dx
    pop cx
    pop bx
    pop ax
    ret

.rd_disk:; eax LBA扇区号 ds:si 读入内存的地址 cx 读入的扇区数
    push cx
    push ds
    push si
    push di
    mov ebp,eax;//实模式下还是可以使用32位寄存器
    mov di,cx;//对eax cx备份

    mov dx,0x1f2
    mov al,cl
    out dx,al;写入数据

    mov eax,ebp

    mov dx,0x1f3;从 eax写入0-27位地址到对应端口
    out dx,al

    mov cl,8
    shr eax,cl
    mov dx,0x1f4
    out dx,al

    shr eax,cl
    mov dx,0x1f5
    out dx,al

    shr eax,cl
    and al,0x0f
    or al,0xe0;将4-7位设置成0xe0 使用lba模式
    mov dx,0x1f6
    out dx,al

    mov dx,0x1f7
    mov al,0x20
    out dx,al;读命令

.not_ready:;阻塞式读取
    nop
    in al,dx;0x1f7既用来写命令 又用来查询命令后的状态
    and al,0x88
    cmp al,0x08;如果第3位为1 就准备就绪了 并不需要第7位也为1
    jnz .not_ready;不是0x88就循环等

    mov ax,di
    mov dx,256
    mul dx
    mov cx,ax;//16位*16位 结果 dx:ax ax是低16位 保存着需要读取的次数
    ;一个扇区512字节 需要读 256次(1次16位)
    mov dx,0x1f0
.go_on_read:
    in ax,dx;这里是x一次读16位,16位的端口而不是8位
    mov [ds:si],ax
    add si,2
    loop .go_on_read
    pop di
    pop si
    pop ds
    pop cx
    ret


message db "mbr program is running!",0

times 506-($-$$) db 0
addr  times 4 db 0;用来保存print的地址
db 0x55,0xaa

​ loader.s 设置GDT进入保护模式

%include "boot.inc"
LOADER_STACK_TOP equ LOADER_BASE_ADDR
SECTION loaders vstart=LOADER_BASE_ADDR
jmp near loader_start

;---GDT
GDT_BASE dd 0x00000000;0号描述符
         dd 0x00000000;
CODE_DESC dd 0x0000FFFF;段描述符低32位
        dd DESC_CODE_HIGH4 ;段描述符高32位 基地址0 段界限 0xfffff 4k粒度 因此范围0-4GB
DATA_STACK_DESC dd 0x0000FFFF;
        dd  DESC_DATA_HIGH4;同样是0-4GB 向上扩展(段界限作为上界检查)的数据段 但是没关系啦,向下扩展的栈段也可以使用
VIDEO_DESC dd 0x80000007; 基地址0xb8000 低32位的高16位是基地址的低16位 为 0x8000   范围 是0xb8000-0xbffff ,因此段界限是0x8000
;使用4K粒度是 段界限填0x07即可 (0x07+1)*0x1000-1
        dd DESC_VIDEO_HIGH4
GDT_SIZE equ $-GDT_BASE
GDT_LIMIT equ  GDT_SIZE-1

times 120 dd 0;预留60个段描述符

SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0
SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0

gdt_ptr dw GDT_LIMIT
        dd GDT_BASE

loader_msg db "2 loader in real.",0

loader_start:

mov sp,LOADER_STACK_TOP
mov bp,loader_msg
mov cx,17
mov ax,0x1301
mov bx,0x001f
mov dx,0x1800
int 0x10 ;调用int 10  13h子功能显示字符串

;---进入保护模式  打开a20 (第21条地址线)
in al,0x92
or al,0000_0010b
out 0x92,al

;--加载gdt
lgdt [gdt_ptr]

;---cr0 开启pe(protection enable)
mov eax,cr0
or eax,0x01
mov cr0,eax
jmp dword SELECTOR_CODE:p_mode_start;刷新流水线,使用dword 0x66反转 使用32位数据

;使用code选择子,那么会d位被置1,因此会使用32位模式工作,我们要对应编码32位
[bits 32]
p_mode_start:
mov ax,SELECTOR_DATA
mov ds,ax
mov es,ax
mov ss,ax
mov esp,LOADER_STACK_TOP
mov ax,SELECTOR_VIDEO
mov gs,ax

mov byte [gs:160],'P' 
jmp $

​ 上述代码编译通过运行正确。