手写操作系统 - CPU段页门

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 手写操作系统 - CPU段页门

前言

  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
  1. 拿到数据段寄存器中的值 ,代码中ds = 0x10
  2. 解析段寄存器中的值,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    |  -> 数据段描述符
  1. 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      |   
  1. 取到数据段的描述符

    base + 2 * 8

  gdt
  |    0    |  -> 段描述符(unused 固定的,CPU硬性要求)<----------base
  |    1    |  -> 代码段描述符
  |    2    |  -> 数据段描述符
  1. 解析段描述符

    下图为段描述符:

下图为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
  • 返回线性地址
相关文章
|
6月前
|
缓存 Linux 调度
操作系统-CPU粘合
操作系统-CPU粘合
67 0
|
4月前
|
Linux 调度
部署02-我们一般接触的是Mos和Wimdows这两款操作系统,很少接触到Linux,操作系统的概述,硬件是由计算机系统中由电子和机械,光电元件所组成的,CPU,内存,硬盘,软件是用户与计算机接口之间
部署02-我们一般接触的是Mos和Wimdows这两款操作系统,很少接触到Linux,操作系统的概述,硬件是由计算机系统中由电子和机械,光电元件所组成的,CPU,内存,硬盘,软件是用户与计算机接口之间
|
5月前
|
虚拟化 iOS开发 MacOS
客户机操作系统已禁用 CPU。请关闭或重置虚拟机。解决方法
客户机操作系统已禁用 CPU。请关闭或重置虚拟机。解决方法
2622 0
|
6月前
|
存储 缓存 PHP
阿里云服务器实例、CPU内存、带宽、操作系统选择参考
对于使用阿里云服务器的用户来说,云服务器的选择和使用非常重要,如果实例、内存、CPU、带宽等配置选择错误,可能会影响到自己业务在云服务器上的计算性能及后期运营状况,本文为大家介绍一下阿里云服务器实例、CPU内存、带宽、操作系统的选择注意事项,以供参考。
阿里云服务器实例、CPU内存、带宽、操作系统选择参考
|
6月前
|
存储 算法 Linux
【计算机操作系统】深入探究CPU,PCB和进程工作原理
【计算机操作系统】深入探究CPU,PCB和进程工作原理
180 1
|
6月前
|
存储 SQL 缓存
手写操作系统(5)——CPU工作模式与虚拟地址(下)
手写操作系统(5)——CPU工作模式与虚拟地址
58 0
|
6月前
|
存储 缓存 Linux
手写操作系统(5)——CPU工作模式与虚拟地址(上)
手写操作系统(5)——CPU工作模式与虚拟地址
73 0
|
6月前
|
Linux
Linux操作系统调优相关工具(一)查看CPU负载相关工具 找出系统中使用CPU最多的进程?
Linux操作系统调优相关工具(一)查看CPU负载相关工具 找出系统中使用CPU最多的进程?
60 0
|
6月前
|
算法 网络协议 调度
操作系统 -- CPU调度
操作系统 -- CPU调度
65 0
|
6月前
|
资源调度 调度 UED
CPU执行系统调用时发生中断,操作系统还能切回中断前的系统调用继续执行吗?
系统调用服务例程在执行过程中,通常不会被中断。系统调用服务例程的执行是一个原子操作,即在执行期间不会被中断。这是为了确保在系统调用服务例程执行期间对内核数据结构的一致性和完整性。