手写操作系统 - CPU段页门

2023-12-29 21:24:22

前言

  1. 为什么要知道CPU的段页门
  2. 如何控制CPU由实模式进入保护模式
  3. CPU是如何找到数据并读写的?CPU是如何找到代码并执行的?

整体流程:

mov eax, [0x100] 为例,其将0x100内存中得数据复制到eax寄存器当中。

在这里插入图片描述
需要了解的知识点:

  • 内存地址中逻辑地址、线性地址和物理地址之间的联系

    • 逻辑地址:看到的所有的内存地址;
    • 线性地址:线性地址 = 段基址 + 逻辑地址
      没有启用分页机制的情况下,线性地址与物理地址相同
      开启后线性地址 -> MMU -> 物理地址;
    • 物理地址:物理内存的地址 ;
  • 运行模式
    从开机到达64位长模式(Long Mode),需要经过实模式(Real Mode)保护模式(Protected Mode),以及在某些情况下的兼容模式(Compatibility Mode)

    • 实时模式(16位):1MB的内存地址空间,并且没有硬件级别的内存保护,20根地址总线,但是寄存器是16位的;
    • 保护模式(32位):内存的访问(高达4GB),硬件级别的内存保护,以及多任务功能。
    • 兼容模式(32位):用于支持旧的32位操作系统和应用程序,它在64位处理器上允许运行未修改的32位代码。
    • 64位长模式 (64位):允许访问远大于4GB的物理和虚拟内存。
  • 虚拟内存

    • 兼容模式和长模式一定要开虚拟内存;
    • 保护模式可以开可以不开。

1、为什么要知道CPU的段页门

  • 什么是段,就是你经常听到的:代码段、数据段
  • 什么是页,就是你经常听到的:虚拟内存,又叫CPU分页机制
  • 什么是门,与你经常听到的用户态切内核态有关
    • 四种门:中断门、调用门、任务门、陷进门
    • 快速调用:sysenter/sysexit、syscall/sysret
  • 想让CPU由实模式进入包含模式,必须构建段

2、如何控制CPU由实模式进入保护模式

  1. 配置GDT表,至少包含一个代码段一个数据段
  2. 开A20总线
  3. 设置控制寄存器CR0
  4. 设置段寄存器

3、CPU是如何找到数据并读写的?CPU是如何找到代码并执行的?

mov eax, [0x100] 为例,其将0x100内存中得数据复制到eax寄存器当中。

在这里插入图片描述

  1. 需了解的知识:
    0x100是逻辑地址,数据是存储在物理内存上的,得有物理地址,如何通过逻辑地址0x100找到它的物理地址?
    A. 段寄存器:

    • 常用的:CS(code segment)代码段 , SS(stack segment)栈段,DS (data segment)数据段;

    • 不常用:ES、FS(用于kpcr)、GS

    B. 段选择子:需要注意CPLDPLRPL

    C. gdt表、ldt表以及对应的gdtr寄存器ldtr寄存器

    D.段描述符

    setup.asm基础框架:
    会在下面基础框架基础上进行一系列操作进入保护模式。

    [ORG 0x500]
    
    [SECTION .text]
    [BITS 16]
    global setup_start
    setup_start:
        mov ax, 0
        mov ss, ax
        mov ds, ax
        mov es, ax
        mov fs, ax
        mov gs, ax
        mov si, ax
    
        mov si, prepare_enter_protected_mode_msg
        call   print
    
    print:
        mov ah, 0x0e
        mov bh, 0
        mov bl, 0x01
    .loop:
        mov al, [si]
        cmp al, 0
        jz .done
        int 0x10
    
        inc si
        jmp .loop
    .done:
        ret
    
    
    prepare_enter_protected_mode_msg:
        db "Prepare to go into protected mode...", 10, 13, 0
    
  2. 拿到数据段寄存器中的值 ,代码中ds = 0x10

  3. 解析段寄存器中的值,0x10->段选择子
    在这里插入图片描述
    GDT定义:
    分别定义了段描述符和段选择子

    [SECTION .gdt]
    SEG_BASE equ 0
    SEG_LIMIT equ 0xfffff
    
    CODE_SELECTOR equ (1 << 3)		;0000_0001 -> 0000_1000 即 0x08
    DATA_SELECTOR equ (2 << 3)		;0000_0010 -> 0001_0000 即 0x10
    
    gdt_base:
        dd 0, 0
    gdt_code:
        dw SEG_LIMIT & 0xffff
        dw SEG_BASE & 0xffff
        db SEG_BASE >> 16 & 0xff
        ;P_DPL_S_TYPE
        db 0b1_00_1_1000
        ;G_DB_0_AVL_LIMIT
        db 0b0_1_0_0_0000 | (SEG_LIMIT >> 16 & 0xf)
        db SEG_BASE >> 24 & 0xf
    
    gdt_data:
        dw SEG_LIMIT & 0xffff
        dw SEG_BASE & 0xffff
        db SEG_BASE >> 16 & 0xff
        ;P_DPL_S_TYPE
        db 0b1_00_1_1000
        ;G_DB_0_AVL_LIMIT
        db 0b0_1_0_0_0000 | (SEG_LIMIT >> 16 & 0xf)
        db SEG_BASE >> 24 & 0xf
        
    gdt_ptr:
        dw $ - gdt_base
        dd gdt_base
    

    分析: 0x10中段选择子RPL为0,只能内核态访问;TI为0,即GDT表;Index为2,CPU支持的段数为2 需要注意本文章代码中:

    gdt
    |    0    |  -> 段描述符(unused 固定的,CPU硬性要求)<----------base
    |    1    |  -> 代码段描述符
    |    2    |  -> 数据段描述符
    
  4. CPU读自己的gdtr寄存器
    通过特权指令,r0可以执行
    lgdt 写
    sgdt 读

    x86模式下,GDTR总共是48位(16位界限 + 32位基址)
    x64模式下,GDTR总共是80位(16位界限 + 64位基址)

    x86模式下
    gdt_ptr:
        dw $ - gdt_base
        dd gdt_base
        
        	2            4
    	|  limit  |    base      |   
    
    
  5. 取到数据段的描述符
    base + 2 * 8

    	gdt
    	|    0    |  -> 段描述符(unused 固定的,CPU硬性要求)<----------base
    	|    1    |  -> 代码段描述符
    	|    2    |  -> 数据段描述符
    
  6. 解析段描述符
    下图为段描述符:
    在这里插入图片描述
    下图为S = 1 代码段或数据段的描述符:
    在这里插入图片描述

    A. 检查p位 有效位 1 0

    B. 检查dpl位 0x10 rpl = 0

    	rpl  请求特权级  
    		段选择子的低2位
    	cpl  当前请求特权级
    		CPU的内部
    		cpl = rpl = 0
    	dpl  访问段的最多要求特权级
    		dpl = 0
    		
    	dpl <= cpl
    

    C. s位、type域
    一般s位和type域连在一起看

    	s = 1 代码段、数据段
    	s = 0 系统段
    

    D. type域 数据段

    E. 取base、limit

    	G位控制limit的单位
    	0: 字节  	1M
    	1: 4K		1M个4K   4G
    

    F. 判断base + offset <= limit

    G. 返回线性地址

setup.asm完整代码:

[ORG 0x500]

[SECTION .data]
KERNEL_ADDR equ 0x1200

[SECTION .gdt]
SEG_BASE equ 0
SEG_LIMIT equ 0xfffff

CODE_SELECTOR equ (1 << 3)
DATA_SELECTOR equ (2 << 3)

gdt_base:
    dd 0, 0
gdt_code:
    dw SEG_LIMIT & 0xffff
    dw SEG_BASE & 0xffff
    db SEG_BASE >> 16 & 0xff
    ;P_DPL_S_TYPE
    db 0b1_00_1_1000
    ;G_DB_0_AVL_LIMIT
    db 0b0_1_0_0_0000 | (SEG_LIMIT >> 16 & 0xf)
    db SEG_BASE >> 24 & 0xf

gdt_data:
    dw SEG_LIMIT & 0xffff
    dw SEG_BASE & 0xffff
    db SEG_BASE >> 16 & 0xff
    ;P_DPL_S_TYPE
    db 0b1_00_1_0010
    ;G_DB_0_AVL_LIMIT
    db 0b0_1_0_0_0010 | (SEG_LIMIT >> 16 & 0xf)
    db SEG_BASE >> 24 & 0xf

gdt_ptr:
    dw $ - gdt_base
    dd gdt_base

[SECTION .text]
[BITS 16]
global setup_start
setup_start:
    xchg bx, bx
    mov ax, 0
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov si, ax

    mov si, prepare_enter_protected_mode_msg
    call   print

enter_protected_mode:
    ; 关中断
    cli

    ; 加载gdt表
    lgdt [gdt_ptr]

    ; 开A20
    in    al,  92h
    or    al,  00000010b
    out   92h, al

    ; 设置保护模式
    mov eax, cr0
    or eax, 1
    mov cr0, eax

    jmp CODE_SELECTOR:protected_mode	;长跳,cs:ip 刷新cs eip寄存器
print:
    mov ah, 0x0e
    mov bh, 0
    mov bl, 0x01
.loop:
    mov al, [si]
    cmp al, 0
    jz .done
    int 0x10

    inc si
    jmp .loop
.done:
    ret

[BITS 32]
protected_mode:
    xchg bx, bx
    mov ax, DATA_SELECTOR
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov esp, 0x9fbff

    ; 将内核读入内存
    mov edi, KERNEL_ADDR
    mov ecx, 3  ;从哪个山区开始读
    mov bl, 60  ;指定从硬盘读取的扇区数
    call read_hd

    jmp CODE_SELECTOR:KERNEL_ADDR    ;修改esi寄存器

read_hd:
    ; 0x1f2 8bit 指定读取或写入的扇区数
    mov dx, 0x1f2
    mov al, bl
    out dx, al

     ; 0x1f3 8bit iba地址的第八位 0-7
     inc dx
     mov al, cl
     out dx, al

     ; 0x1f4 8bit iba地址的中八位 8-15
     inc dx
     mov al, ch
     out dx, al

     ; 0x1f5 8bit iba地址的高八位 16-23
     inc dx
     shr ecx, 16
     mov al, cl
     out dx, al

     ; 0x1f6 8bit
     ; 0-3 位iba地址的24-27
     ; 4 0表示主盘 1表示从盘
     ; 5、7位固定为1
     ; 6 0表示CHS模式,1表示LAB模式
     inc dx
     mov al, ch
     add al, 0b1110_1111
     out dx, al

     ; 0x1f7 8bit  命令或状态端口
     inc dx
     mov al, 0x20
     out dx, al

     ; 设置loop次数,读多少个扇区要loop多少次
     mov cl, bl

.start_read:
    push cx     ; 保存loop次数,防止被下面的代码修改破坏

    call .wait_hd_prepare
    call read_hd_data

    pop cx      ; 恢复loop次数

    loop .start_read

.return:
    ret

; 一直等待,直到硬盘的状态是:不繁忙,数据已准备好
; 即第7位为0,第3位为1,第0位为0
.wait_hd_prepare:
     mov dx, 0x1f7
.check:
    in al, dx
    and al, 0b1000_1000
    cmp al, 0b0000_1000

    jnz .check

    ret


; 读硬盘,一次读两个字节,读256次,刚好读一个扇区
read_hd_data:
    mov dx, 0x1f0
    mov cx, 256

.read_word:
    in ax, dx
    mov [edi], ax
    add edi, 2
    loop .read_word

    ret

prepare_enter_protected_mode_msg:
    db "Prepare to go into protected mode...", 10, 13, 0

总结

  • CPU是如何找到数据并读写的?
    1. 拿到数据段寄存器中的值 ds = 0x10
    2. 解析段寄存器中的值,0x10->段选择子
    3. CPU读自己的gdtr寄存器
    4. 取到数据段的描述符
    5. 解析段描述符
      • 检查p位 有效位
      • 检查dpl位
      • s位、type域
      • type域 数据段
      • 取base、limit
      • 判断base + offset <= limit
      • 返回线性地址

文章来源:https://blog.csdn.net/weixin_53492721/article/details/134705105
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。