手写操作系统 - CPU段页门
前言
- 为什么要知道CPU的段页门
- 如何控制CPU由实模式进入保护模式
- 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由实模式进入保护模式
- 配置GDT表,至少包含一个代码段一个数据段
- 开A20总线
- 设置控制寄存器CR0
- 设置段寄存器
3、CPU是如何找到数据并读写的?CPU是如何找到代码并执行的?
以
mov eax, [0x100]
为例,其将0x100内存
中得数据复制到eax
寄存器当中。
-
需了解的知识:
0x100是逻辑地址,数据是存储在物理内存上的,得有物理地址,如何通过逻辑地址0x100找到它的物理地址?
A. 段寄存器:-
常用的:
CS(code segment)
代码段 ,SS(stack segment)
栈段,DS (data segment)
数据段; -
不常用:ES、FS(用于kpcr)、GS
B. 段选择子:需要注意
CPL
、DPL
和RPL
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
-
-
拿到数据段寄存器中的值 ,代码中ds = 0x10
-
解析段寄存器中的值,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 | -> 数据段描述符
-
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 |
-
取到数据段的描述符
base + 2 * 8gdt | 0 | -> 段描述符(unused 固定的,CPU硬性要求)<----------base | 1 | -> 代码段描述符 | 2 | -> 数据段描述符
-
解析段描述符
下图为段描述符:
下图为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是如何找到数据并读写的?
- 拿到数据段寄存器中的值 ds = 0x10
- 解析段寄存器中的值,0x10->段选择子
- CPU读自己的gdtr寄存器
- 取到数据段的描述符
- 解析段描述符
- 检查p位 有效位
- 检查dpl位
- s位、type域
- type域 数据段
- 取base、limit
- 判断base + offset <= limit
- 返回线性地址
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!