学习操作系统:进入保护模式,理解GDT(1):TEXT

直接上代码,以及追加了自己对GDT的理解,为了理解GDT,翻遍了各种文章,但没有代码的支撑,凭空的理解很浪费时间。

下面的代码,稍微简化了原先的代码(于老师的代码^^)。把pm.asm需要的代码从pm.inc挪出来,取消了pm.inc的引入。

; ==========================================
; 32mode_TEXT.asm
; 编译方法:nasm 32mode_TEXT.asm -o 32mode_TEXT.bin
; ==========================================

;=========================宏定义:  GDT的定义 开始=====================================
;
; 描述符 类型       入参1(%1), 入参2(%2), 入参3(%3)
; 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(双字节:字节1~字节2)
	dw	%1 & 0FFFFh				; 段基址1(双字节:字节3~字节4)
	db	(%1 >> 16) & 0FFh			; 段基址2(单字节:字节5)
	dw	((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)	; 属性1 + 段界限2 + 属性2(双字节:字节6~字节7)
	db	(%1 >> 24) & 0FFh			; 段基址3(单字节:字节8)
%endmacro ; 共 8 字节
;
; 门     类型   入参1,  入参2,  入参3,  入参4
; usage: Gate Selector, Offset, DCount, Attr
;        Selector:  dw
;        Offset:    dd
;        DCount:    db
;        Attr:      db
%macro Gate 4    ; 4为参数个数
	dw	(%2 & 0FFFFh)				; 偏移1 (双字节)
	dw	%1					; 选择子(双字节)
	dw	(%3 & 1Fh) | ((%4 << 8) & 0FF00h)	; 属性  (双字节)
	dw	((%2 >> 16) & 0FFFFh)			; 偏移2 (双字节)
%endmacro ; 共 8 字节
;=========================GDT的定义 结束=====================================
org 07c00h
    jmp LABEL_BEGIN
 
[SECTION .gdt]
; GDT
; Descriptor在pm.inc中定义,三个入参
; LABEL_GDT为(0#)描述符,创建0#描述符,它是空描述符,这是处理器的要求
;                              	段基址,      段界限    	  , 属性
LABEL_GDT:	   Descriptor     0,      0, 	    	     0          ; 空描述符 
LABEL_DESC_CODE32: Descriptor     0, 	  SegCode32Len - 1,  98h + 4000h; 非一致代码段
LABEL_DESC_VIDEO:  Descriptor 	0B8000h,  0ffffh, 	     92h	; Text显存首地址:0B8000h,VGA显存首地址:0x000a0000
; GDT 结束
 
GdtLen      equ $ - LABEL_GDT   ; GDT长度
GdtPtr      dw  GdtLen - 1  	; GDT界限
            dd  0       	; GDT基地址
 
; GDT 选择子
; LABEL_GDT的地址为GDT表起始地址
; 后面的GDT的地址,都是以LABEL_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 eax,0一样,由于它比mov eax,0效率高,所以一般用它!
    ; 两者的作用没有区别,都是让eax的值为0,但是xor eax,eax 指令为2字节,mov eax,0 指令为5个字节。相比而言,前面指令更能节省空间。
    ; 这个虽然结果是一样的,都是变成0,但是xor会影响到状态标志位,使cf of 变成0。
    xor eax, eax
    mov ax, cs
    shl eax, 4 ; shl:逻辑左移指令 shr:逻辑右移指令
    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; “XOR EAX,EAX”语句已经将EAX清零了。
    mov ax, ds  ; 然后“MOV AX,DS”将16位的段值从DS复制到AX。
    shl eax, 4  ; “SHL EAX,4”是计算数据段的起始物理地址,
                ; 所以用了EAX来表示20位的物理地址(没有使用AX表示物理地址是因为AX只有16位)。 
                ; (注:实模式下的寻址的方式固然还是段:偏移,而且段:偏移还都是16位,
                ; 但在实模式下却可以使用32位寄存器,如EAX……等,
                ; 而这里的EAX既不是段值,也不是偏移,而是人工计算出的物理地址,
                ; 相当于用EAX保存的数据,
                ; 最后再通过“MOV DWORD[GdtPtr+2],EAX”将EAX中的物理地址送到DS:(OFFSET GdtPtr)+2表示的逻辑地址处。)
    add eax, LABEL_GDT      ; eax <- gdt 基地址
    mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
 
    ; 加载 GDTR
    lgdt    [GdtPtr]
 
    ; 关中断
    cli
 
    ; 打开地址线A20
    ; 打开A20的其他方法:https://blog.csdn.net/longintchar/article/details/79365928
    ; 下面写法是其中之一
    in  al, 92h  ;南桥芯片内的端口 
    or  al, 00000010b
    out 92h, al
 
    ; 准备切换到保护模式--除了cr0还有其他控制寄存器(CR0,CR1,CR2,CR3,CR4)
    mov eax, cr0; cr0是80386CPU中的控制寄存器
    or  eax, 1  ; 第0位,即PE位,用来控制是否进入保护模式(是/否:1/0)
    mov cr0, eax; 设置PE位
 
    ; 真正进入保护模式
    jmp dword SelectorCode32:0  ; 执行这一句会把 SelectorCode32 装入 cs,
                    ; 并跳转到 SelectorCode32:0  处
                    ; 【jmp dword SelectorCode32:0】的理解
                    ; http://tieba.baidu.com/p/4302738682
                    ; SelectorCode32:0的意思就是第二个描述符的第0行代码开始,这里的0是不是可以换成32位模式下的任何函数?
; END of [SECTION .s16]
 
 
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS   32]
 
LABEL_SEG_CODE32:
    mov ax, SelectorVideo
    mov gs, ax          ; 视频段选择子(目的)
 
    mov ecx, 28         ; .show的循环次数
    mov edi, 0
    mov bx, BootMessage
.show:
    mov ah, 0Ch         ; 0000: 黑底    1100: 红字
    mov al, [bx]
    mov [gs:edi], ax
    inc edi
    inc edi
    inc bx
    loop .show
 
    ; 到此停止
    jmp $
 
BootMessage:        db  "Hello CPU, I'm in protected mode!"
SegCode32Len    equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]

 

用GDB查看GDT的内容(64位GCC在Windows下模拟)

GDT和段的关系

代码:
mov ax, SelectorVideo ; SelectorVideo 为LABEL_DESC_CODE32的选择子
mov gs, ax                   ; gs指向GDT(LABEL_DESC_CODE32)的段基址
这时gs指向0x000b8000, 即段描述符的段基址。界限值:0xffff
剩下就和正常的段寄存器操作一样了。
gs:edi等 

猜你喜欢

转载自blog.csdn.net/jinold/article/details/86561133