最近正在阅读《orange's,一个操作系统的实现》,真是相见恨晚啊,那么多年就没有一本真正深入浅出弄点代码让我们入门和实践一下的书,感谢作者于渊。
下面是第3章pmtest1.asm的全注释
总结执行过程如下:
- 初始化32位代码段的段基址,并存储到GDT中对应的描述符中
- 准备GDT的基地址,并通过LGDT指令加载到GDTR寄存器中
- cli关中断
- 打开地址线A20
- 将CR0寄存器的PE位置1,进入保护模式
- 已经进入保护模式,跳转到32位代码段继续执行
- 写显存
- game over
- ; ==========================================
- ; pmtest1.asm
- ; 编译方法:nasm pmtest1.asm -o pmtest1.bin
- ; ==========================================
- %include "pm.inc" ; 常量, 宏, 以及一些说明
- org 07c00h
- jmp 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 选择子
- ; 选择子其实就是GDT中的偏移,因为GDT的每项都是8字节,因此选择子的低3位肯定为0
- SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
- SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
- ; END of [SECTION .gdt]
- [SECTION .s16]
- [BITS 16]
- LABEL_BEGIN:
- ; 下面这几行代码貌似没有实际意义的说,尤其是那个0100h,不是DOS PSP的偏移嘛。
- ; 估计就是保护现场框架代码,哪位知道可以告诉我。
- mov ax, cs
- mov ds, ax
- mov es, ax
- mov ss, ax
- mov sp, 0100h
- ; 初始化 32 位代码段描述符
- ; 将32位代码段的基地址存到对应的描述符
- xor eax, eax
- ; 代码段左移4位 + 32位代码段的偏移 = 32位代码段的段起始物理地址(段基址)
- mov ax, cs
- shl eax, 4
- add eax, LABEL_SEG_CODE32
- ; 如果使用纯段式内存管理,段描述符中的地址就是段的物理地址
- ; 描述符低32位
- ; 31 16 15 0
- ; | 段基址 | | 段界限 |
- ; 存储32位段基址的低16位
- mov word [LABEL_DESC_CODE32 + 2], ax
- ; 存储32位段基址的高16位
- shr eax, 16
- mov byte [LABEL_DESC_CODE32 + 4], al
- mov byte [LABEL_DESC_CODE32 + 7], ah
- ; 为加载 GDTR 作准备
- ; GDTR寄存器(48bit)中存储的是:GDT的线性基地址和GDT的界限
- ; 将GDT的地址存入GdtPtr
- ; GdtPtr对应48bit的GDTR寄存器,
- ; 47 16 15 0
- ; | GDT32位线性基地址 | | GDT界限 |
- xor eax, eax
- ; GDT在数据段中
- mov ax, ds
- shl eax, 4
- add eax, LABEL_GDT ; eax <- gdt 基地址
- mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
- ; 加载 GDTR
- ; 将GdtPtr指向的6个字节拷贝到GDTR寄存器中
- lgdt [GdtPtr]
- ; 关中断
- cli
- ; 打开地址线A20
- in al, 92h
- or al, 00000010b
- out 92h, al
- ; 准备切换到保护模式
- ; cr0的第0位PE,PE=0为实模式,PE=1为保护模式
- mov eax, cr0
- or eax, 1
- ; 一旦将cr0中的PE位设置为1,就进入了保护模式!
- ; 也就是说,执行完下面这句,我们就已经进入保护模式了
- mov cr0, eax
- ; 真正进入保护模式
- ; 虽然已经进入保护模式,但我们现在还在16位代码段中,因为CS仍然是16位代码段
- ; 的基地址(如果我们此时执行不带段前缀的JMP指令,CS不会被解释为段基址,
- ; 而是被解释为GDT的选择子,不要忘记我们已经在保护模式中了),
- ; 因此我们必须跳入32位代码段,跳入以后,CS中保存的就是GDT的选择子了.
- ; 必须加上dword,因为我们还在16位代码段中,偏移也是16位的。
- ; 如果偏移不是0,而是0x12345678,则高位的0x1234会被截断
- 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 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。
- mov ah, 0Ch ; 0000: 黑底 1100: 红字
- mov al, 'P'
- mov [gs:edi], ax
- ; 到此停止
- jmp $
- SegCode32Len equ $ - LABEL_SEG_CODE32
- ; END of [SECTION .s32]