快捷搜索:  汽车  科技

小型操作系统教程(自制操作系统学习4)

小型操作系统教程(自制操作系统学习4)总结:这里段基址需要32位地址表示、段界限用20位表示。段界限的单位可以是字节,也可以是4K。一个段最大值可能是2202^{20}220(1M)或2322^{32}232(4G)由AMD根据速龙64处理器提出的模式,这种模式下允许处理器进入64位的内存空间,并操作64位寄存器。多数的16位和32位处理器并不能工作在长模式下。 x86-64的实模式准确的说是像16位处理器,而x86-64的保护模式像是32位处理器。 要让芯片有64位的处理能力,就要切换到64位模式下。在保护模式下,寻址内存的段值仍是16位的CS,DS等寄存器,但它仅是一个索引,指向了GDT表的一个描述符。段描述符是8字节(64bit),用来描述一个内存段。说明:

小型操作系统教程(自制操作系统学习4)(1)

一、概念1. 为什么要进保护模式

8086最大寻址范围是1M,而超过64K的内存区域访问要靠切换段基址。
当Intel的CPU发展到32位后,寻址空间达到了4G。32位下改变了寻址方式,使用名为GDT的表来管理内存,其实就是查表法,在GDT里记录每个内存段的段基址、段界限、段属性等信息。GDT里每个表项称为描述符Descriptor。
进入32位后,原先的AX,BX,CX,DX,SI,DI,SP,BP从16位扩展(Extend)到了32位,并改名EAX,EBX,ECX,EDX,ESI,EDI,ESP,EBP。
CS,DS,ES,SS这几个16位段寄存器保留,再增加FS,GS两个段寄存器。

CPU在保护模式下使用段描述符采用段描述符缓冲寄存器。在获得一个段描述符之后,后面访问相同段,就会直接访问该寄存器。

2. x86基本运行模式1. 实地址模式

英特尔的x86系列处理器为兼容早期的8086处理器,在上电后处于这个模式。8086有20位地址线,1M的线性地址空间。

2. 保护模式

一个受保护并支持多任务的环境。大部分OS运行在这个模式下。80386有32位地址线,4G线性地址空间。 80386把4G只分为一个段,段基址0x00000000 段长0xFFFFFFFF(4G)

小型操作系统教程(自制操作系统学习4)(2)

3. 64位模式

由AMD根据速龙64处理器提出的模式,这种模式下允许处理器进入64位的内存空间,并操作64位寄存器。
多数的16位和32位处理器并不能工作在长模式下。 x86-64的实模式准确的说是像16位处理器,而x86-64的保护模式像是32位处理器。 要让芯片有64位的处理能力,就要切换到64位模式下。

4. 正在进入保护模式5. 正在进入64位模式3. 段描述符

在保护模式下,寻址内存的段值仍是16位的CS,DS等寄存器,但它仅是一个索引,指向了GDT表的一个描述符。段描述符是8字节(64bit),用来描述一个内存段。

小型操作系统教程(自制操作系统学习4)(3)

说明:

  • G:Granularity,颗粒度标志,1表示段界限单位=4K,0表示段界限单位=1Byte
  • D/B:指明这个段中操作的长度。如果这位被设置,那么这个段是32位段,否则是16位段。
  • L:64位标志,只在IA-32e模式中使用。
  • AVL:Available,可用位,可以为操作系统软件使用。
  • P:Present,存在位,如果为0表示该段不在内存中。
  • S:为1是代码数据段,否则是系统段。
  • DPL:Descriptor Privilege Level,特权级标志。
  • TYPE:最复杂的东西,涉及到段的类型,不过可以读protect.inc里的注释来了解。

这里段基址需要32位地址表示、段界限用20位表示。段界限的单位可以是字节,也可以是4K。一个段最大值可能是2202^{20}220(1M)或2322^{32}232(4G)

小型操作系统教程(自制操作系统学习4)(4)

总结:

  • 描述这些信息的内容放在内存中,称为全局描述表(Global Descriptor Table,GDT)
  • GDT的位置可以由程序员指定,放在1M以下。7c00是512字节引导区,所以可以把GDT放在0x7e00后,GDT的界限最大可以到0x17DFF (64K)。 7c00以下放BIOS数据区、中断向量表等。
  • Intel使用寄存器GDTR来保存GDT信息
  • GDTR是48位专用寄存器,0-15位是GDT边界位置,16-47位放的GDT基地址

小型操作系统教程(自制操作系统学习4)(5)

二、进入保护模式的步骤:

386以后的处理器,有CR0-CR4 寄存器,用于控制模式,分页等高级功能。其中CR0寄存器的PE位表示运行状态,0是实模式、1是保护模式。

小型操作系统教程(自制操作系统学习4)(6)

  1. 准备GDT
  2. 创建一个6个字节的伪描述符指向GDT
  3. 用 lgdt 加载 gdtr
  4. 打开 A20
  5. 跳转,进入保护模式

其中CR0中有一位是PE位,保护模式使能位。

三、进入保护的汇编代码

cpu32.s

[SECTION .code] global _start _start: jmp 0:kernel_start [SECTION .gdt] ; 准备GDT 表定义 gdt_start: gdt_null: dd 0x0 dd 0x0 ; 代码段定义 gdt_code: dw 0xffff dw 0x0 db 0x0 db 10011010b db 11001111b db 0x0 ; 数据段定义 gdt_data: dw 0xffff dw 0x0 db 0x0 db 10010010b db 11001111b db 0x0 gdt_end: ; 描述符 gdt_descriptor: dw gdt_end - gdt_start dd gdt_start ; 代码段基址 CODE_SEG equ gdt_code - gdt_start ; 数据段基址 DATA_SEG equ gdt_data - gdt_start ; 通过中段打印字符串子程序 print: pusha mov ah 14 mov bh 0 .loop: lodsb cmp al 0 je .done int 0x10 jmp .loop .done: popa ret ; 定义要打印的两个字符串 mode_16 db 'mode_16' 0 mode_32 db 'mode_32' 0 [SECTION .s16] [bits 16] ; 程序入口 kernel_start: ; 堆栈初始化 mov ax 0 mov ss ax mov sp 0xFFFC ; 段寄存器初始化 mov ax 0 mov ds ax mov es ax mov fs ax mov gs ax ; 打印现在处于16位模式下 mov si mode_16 call print cli ; 用lgdt加载GDTR lgdt[gdt_descriptor] ; 打开A20 mov eax cr0 or eax 0x1 mov cr0 eax ; 跳到保护模式 jmp CODE_SEG:b32 [bits 32] VIDEO_MEMORY equ 0xb8000 WHITE_ON_BLACK equ 0x0f ; 32位模式下 向显存写数据 print32: pusha mov edx VIDEO_MEMORY .loop: mov al [ebx] mov ah WHITE_ON_BLACK cmp al 0 je .done mov [edx] ax add ebx 1 add edx 2 jmp .loop .done: popa ret ; 进入32位模式 b32: mov ax DATA_SEG mov ds ax mov es ax mov fs ax mov gs ax mov ss ax mov ebp 0x2000 mov esp ebp mov ebx mode_32 call print32 jmp $ ; 填充引导区 times 510-($-$$)-0x45 db 0 ; 填充剩下的空间,不知道为什么不能直接引用_start的地址,这里0x45是我临时凑出来的 dw 0xAA55

运行效果:

小型操作系统教程(自制操作系统学习4)(7)

四、定义GDT数据结构

pm.inc

; 描述符图示 ; 图示一 ; ; ------ ┏━━┳━━┓高地址 ; ┃ 7 ┃ 段 ┃ ; ┣━━┫ ┃ ; 基 ; 字节 7 ┆ ┆ ┆ ; 址 ; ┣━━┫ ② ┃ ; ┃ 0 ┃ ┃ ; ------ ┣━━╋━━┫ ; ┃ 7 ┃ G ┃ ; ┣━━╉──┨ ; ┃ 6 ┃ D ┃ ; ┣━━╉──┨ ; ┃ 5 ┃ 0 ┃ ; ┣━━╉──┨ ; ┃ 4 ┃ AVL┃ ; 字节 6 ┣━━╉──┨ ; ┃ 3 ┃ ┃ ; ┣━━┫ 段 ┃ ; ┃ 2 ┃ 界 ┃ ; ┣━━┫ 限 ┃ ; ┃ 1 ┃ ┃ ; ┣━━┫ ② ┃ ; ┃ 0 ┃ ┃ ; ------ ┣━━╋━━┫ ; ┃ 7 ┃ P ┃ ; ┣━━╉──┨ ; ┃ 6 ┃ ┃ ; ┣━━┫ DPL┃ ; ┃ 5 ┃ ┃ ; ┣━━╉──┨ ; ┃ 4 ┃ S ┃ ; 字节 5 ┣━━╉──┨ ; ┃ 3 ┃ ┃ ; ┣━━┫ T ┃ ; ┃ 2 ┃ Y ┃ ; ┣━━┫ P ┃ ; ┃ 1 ┃ E ┃ ; ┣━━┫ ┃ ; ┃ 0 ┃ ┃ ; ------ ┣━━╋━━┫ ; ┃ 23 ┃ ┃ ; ┣━━┫ ┃ ; ┃ 22 ┃ ┃ ; ┣━━┫ 段 ┃ ; ; 字节 ┆ ┆ 基 ┆ ; 2 3 4 ; ┣━━┫ 址 ┃ ; ┃ 1 ┃ ① ┃ ; ┣━━┫ ┃ ; ┃ 0 ┃ ┃ ; ------ ┣━━╋━━┫ ; ┃ 15 ┃ ┃ ; ┣━━┫ ┃ ; ┃ 14 ┃ ┃ ; ┣━━┫ 段 ┃ ; ; 字节 0 1┆ ┆ 界 ┆ ; ; ┣━━┫ 限 ┃ ; ┃ 1 ┃ ① ┃ ; ┣━━┫ ┃ ; ┃ 0 ┃ ┃ ; ------ ┗━━┻━━┛低地址 ; ; 图示二 ; 高地址………………………………………………………………………低地址 ; | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ; |7654321076543210765432107654321076543210765432107654321076543210| <- 共 8 字节 ; |--------========--------========--------========--------========| ; ┏━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┓ ; ┃31..24┃ (见下图) ┃ 段基址(23..0) ┃ 段界限(15..0)┃ ; ┃ ┃ ┃ ┃ ┃ ; ┃ 基址2┃③│②│ ①┃基址1b│ 基址1a ┃ 段界限1 ┃ ; ┣━━━╋━━━┳━━━╋━━━━━━━━━━━╋━━━━━━━┫ ; ┃ %6 ┃ %5 ┃ %4 ┃ %3 ┃ %2 ┃ %1 ┃ ; ┗━━━┻━━━┻━━━┻━━━┻━━━━━━━┻━━━━━━━┛ ; │ \_________ ; │ \__________________ ; │ \________________________________________________ ; │ \ ; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓ ; ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ ; ┣━━╋━━╋━━╋━━╋━━┻━━┻━━┻━━╋━━╋━━┻━━╋━━╋━━┻━━┻━━┻━━┫ ; ┃ G ┃ D ┃ 0 ┃ AVL┃ 段界限 2 (19..16) ┃ P ┃ DPL ┃ S ┃ TYPE ┃ ; ┣━━┻━━┻━━┻━━╋━━━━━━━━━━━╋━━┻━━━━━┻━━┻━━━━━━━━━━━┫ ; ┃ ③: 属性 2 ┃ ②: 段界限 2 ┃ ①: 属性1 ┃ ; ┗━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━┛ ; 高地址 低地址 ; ; ; 说明: ; ; (1) P: 存在(Present)位。 ; P=1 表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中; ; P=0 表示描述符对地址转换无效,即该段不存在。使用该描述符进行内存访问时会引起异常。 ; ; (2) DPL: 表示描述符特权级(Descriptor Privilege level),共2位。它规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。 ; ; (3) S: 说明描述符的类型。 ; 对于存储段描述符而言,S=1,以区别与系统段描述符和门描述符(S=0)。 ; ; (4) TYPE: 说明存储段描述符所描述的存储段的具体属性。 ; ; ; 数据段类型 类型值 说明 ; ---------------------------------- ; 0 只读 ; 1 只读、已访问 ; 2 读/写 ; 3 读/写、已访问 ; 4 只读、向下扩展 ; 5 只读、向下扩展、已访问 ; 6 读/写、向下扩展 ; 7 读/写、向下扩展、已访问 ; ; ; 类型值 说明 ; 代码段类型 ---------------------------------- ; 8 只执行 ; 9 只执行、已访问 ; A 执行/读 ; B 执行/读、已访问 ; C 只执行、一致码段 ; D 只执行、一致码段、已访问 ; E 执行/读、一致码段 ; F 执行/读、一致码段、已访问 ; ; ; 系统段类型 类型编码 说明 ; ---------------------------------- ; 0 <未定义> ; 1 可用286TSS ; 2 LDT ; 3 忙的286TSS ; 4 286调用门 ; 5 任务门 ; 6 286中断门 ; 7 286陷阱门 ; 8 未定义 ; 9 可用386TSS ; A <未定义> ; B 忙的386TSS ; C 386调用门 ; D <未定义> ; E 386中断门 ; F 386陷阱门 ; ; (5) G: 段界限粒度(Granularity)位。 ; G=0 表示界限粒度为字节; ; G=1 表示界限粒度为4K 字节。 ; 注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。 ; ; (6) D: D位是一个很特殊的位,在描述可执行段、向下扩展数据段或由SS寄存器寻址的段(通常是堆栈段)的三种描述符中的意义各不相同。 ; ⑴ 在描述可执行段的描述符中,D位决定了指令使用的地址及操作数所默认的大小。 ; ① D=1表示默认情况下指令使用32位地址及32位或8位操作数,这样的代码段也称为32位代码段; ; ② D=0 表示默认情况下,使用16位地址及16位或8位操作数,这样的代码段也称为16位代码段,它与80286兼容。可以使用地址大小前缀和操作数大小前缀分别改变默认的地址或操作数的大小。 ; ⑵ 在向下扩展数据段的描述符中,D位决定段的上部边界。 ; ① D=1表示段的上部界限为4G; ; ② D=0表示段的上部界限为64K,这是为了与80286兼容。 ; ⑶ 在描述由SS寄存器寻址的段描述符中,D位决定隐式的堆栈访问指令(如PUSH和POP指令)使用何种堆栈指针寄存器。 ; ① D=1表示使用32位堆栈指针寄存器ESP; ; ② D=0表示使用16位堆栈指针寄存器SP,这与80286兼容。 ; ; (7) AVL: 软件可利用位。80386对该位的使用未左规定,Intel公司也保证今后开发生产的处理器只要与80386兼容,就不会对该位的使用做任何定义或规定。 ; ;---------------------------------------------------------------------------- ; 描述符类型值说明 ; 其中: ; DA_ : Descriptor Attribute ; D : 数据段 ; C : 代码段 ; S : 系统段 ; R : 只读 ; RW : 读写 ; A : 已访问 ; 其它 : 可按照字面意思理解 ;---------------------------------------------------------------------------- DA_32 EQU 4000h ; 32 位段 DA_DPL0 EQU 00h ; DPL = 0 DA_DPL1 EQU 20h ; DPL = 1 DA_DPL2 EQU 40h ; DPL = 2 DA_DPL3 EQU 60h ; DPL = 3 ;---------------------------------------------------------------------------- ; 存储段描述符类型值说明 ;---------------------------------------------------------------------------- DA_DR EQU 90h ; 存在的只读数据段类型值 DA_DRW EQU 92h ; 存在的可读写数据段属性值 DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值 DA_C EQU 98h ; 存在的只执行代码段属性值 DA_CR EQU 9Ah ; 存在的可执行可读代码段属性值 DA_CCO EQU 9Ch ; 存在的只执行一致代码段属性值 DA_CCOR EQU 9Eh ; 存在的可执行可读一致代码段属性值 ;---------------------------------------------------------------------------- ; 系统段描述符类型值说明 ;---------------------------------------------------------------------------- DA_LDT EQU 82h ; 局部描述符表段类型值 DA_TaskGate EQU 85h ; 任务门类型值 DA_386TSS EQU 89h ; 可用 386 任务状态段类型值 DA_386CGate EQU 8Ch ; 386 调用门类型值 DA_386IGate EQU 8Eh ; 386 中断门类型值 DA_386TGate EQU 8Fh ; 386 陷阱门类型值 ;---------------------------------------------------------------------------- ; 选择子图示: ; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓ ; ┃ 15 ┃ 14 ┃ 13 ┃ 12 ┃ 11 ┃ 10 ┃ 9 ┃ 8 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ ; ┣━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━╋━━╋━━┻━━┫ ; ┃ 描述符索引 ┃ TI ┃ RPL ┃ ; ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━┛ ; ; RPL(Requested Privilege Level): 请求特权级,用于特权检查。 ; ; TI(Table Indicator): 引用描述符表指示位 ; TI=0 指示从全局描述符表GDT中读取描述符; ; TI=1 指示从局部描述符表LDT中读取描述符。 ; ;---------------------------------------------------------------------------- ; 选择子类型值说明 ; 其中: ; SA_ : Selector Attribute SA_RPL0 EQU 0 ; ┓ SA_RPL1 EQU 1 ; ┣ RPL SA_RPL2 EQU 2 ; ┃ SA_RPL3 EQU 3 ; ┛ SA_TIG EQU 0 ; ┓TI SA_TIL EQU 4 ; ┛ ;---------------------------------------------------------------------------- ; 宏 ------------------------------------------------------------------------------------------------------ ; ; 描述符 ; usage: Descriptor Base Limit Attr ; Base: dd ; Limit: dd (low 20 bits available) ; Attr: dw (lower 4 bits of higher byte are always 0) %macro Descriptor 3 dw %2 & 0FFFFh ; 段界限 1 (2 字节) dw %1 & 0FFFFh ; 段基址 1 (2 字节) db (%1 >> 16) & 0FFh ; 段基址 2 (1 字节) dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 段界限 2 属性 2 (2 字节) db (%1 >> 24) & 0FFh ; 段基址 3 (1 字节) %endmacro ; 共 8 字节 ; ; 门 ; usage: Gate Selector Offset DCount Attr ; Selector: dw ; Offset: dd ; DCount: db ; Attr: db %macro Gate 4 dw (%2 & 0FFFFh) ; 偏移 1 (2 字节) dw %1 ; 选择子 (2 字节) dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节) dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节) %endmacro ; 共 8 字节 ; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

cpu32.s

%include "src/boot/pm.inc" global _start _start: jmp 0:LABEL_BEGIN [SECTION .gdt] ; GDT ; 段基址 段界限 属性 LABEL_GDT: Descriptor 0 0 0 ; 空描述符 LABEL_DESC_CODE32: Descriptor 0 SegCode32Len - 1 DA_C DA_32 ; 非一致代码段 LABEL_DESC_VIDEO: Descriptor 0B8000h 0ffffh DA_DRW ; 显存首地址 ; GDT 结束 GdtLen equ $ - LABEL_GDT ; GDT长度 GdtPtr dw GdtLen - 1 ; GDT界限 dd 0 ; GDT基地址 ; GDT 选择子 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ; END of [SECTION .gdt] [SECTION .s16] [bits 16] ; 程序入口 LABEL_BEGIN: mov ax cs mov ds ax mov es ax mov ss ax mov sp 0xFFFC ; 初始化 32 位代码段描述符 xor eax eax mov ax cs shl eax 4 add eax LABEL_SEG_CODE32 mov word [LABEL_DESC_CODE32 2] ax shr eax 16 mov byte [LABEL_DESC_CODE32 4] al mov byte [LABEL_DESC_CODE32 7] ah ; 为加载 GDTR 作准备 xor eax eax mov ax ds shl eax 4 add eax LABEL_GDT ; eax <- gdt 基地址 mov dword [GdtPtr 2] eax ; [GdtPtr 2] <- gdt 基地址 ; 加载 GDTR lgdt [GdtPtr] ; 关中断 cli ; 打开地址线A20 in al 92h or al 00000010b out 92h al ; 准备切换到保护模式 mov eax cr0 or eax 1 mov cr0 eax ; 真正进入保护模式 jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs 并跳转到 Code32Selector:0 处 ; END of [SECTION .s16] [SECTION .s32]; 32 位代码段. 由实模式跳入. [bits 32] ; 进入32位模式 LABEL_SEG_CODE32: mov ax SelectorVideo mov gs ax ; 视频段选择子(目的) mov edi (80 * 10 0) * 2 ; 屏幕第 10 行 第 0 列。 mov ah 0Ch ; 0000: 黑底 1100: 红字 mov al 'P' mov [gs:edi] ax ; 到此停止 jmp $ SegCode32Len equ $ - LABEL_SEG_CODE32 ; 填充引导区 times 510-($-$$)-0x7e db 0 ; 填充剩下的空间,不知道为什么不能直接引用_start的地址,这里0x7e是我临时凑出来的 dw 0xAA55

Makefile 编译脚本:

# 进入32位保护模式,加载到0x10000h cpu32.o : $(SRC)boot/cpu32.s $(NASM) $(ASFLAGS) -f elf32 -o $(TARGET)$@ $^ cpu32.bin : cpu32.o $(LD) $(LDFLAGS) -Ttext 0x7c00 --oformat binary -o $(TARGET)$@ $(TARGET)$^ install_cpu32 : cpu32.bin $(DD) if=$(TARGET)cpu32.bin of=$(TARGET)os.img conv=notrunc run_cpu32 : install_cpu32 cd $(TOOLPATH) && bochs.bat && cd ..

目录结构:

小型操作系统教程(自制操作系统学习4)(8)

猜您喜欢: