代码取自《自己动手写操作系统》:
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 字节 ; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pmtest1.asm
; ========================================== ; pmtest1.asm ; 编译方法:nasm pmtest1.asm -o pmtest1.com ; ========================================== %include "pm.inc" ; 常量, 宏, 以及一些说明 org 0100h jmp LABEL_BEGIN [SECTION .gdt] ; GDT ; 段基址, 段界限 , 属性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代码段, 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, 0100h ; 初始化 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] 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 ; END of [SECTION .s32]