8086实模式的寻址范围只有1MB。其中:
系统硬件使用的存储器地址被安排在高端,地址从0xA0000H(684KB)开始的384KB中,其中有用于显示的视频缓冲区;
内存低端安排了中断向量表和BIOS数据区;
剩下的从0x00500H开始的到0xA0000H总共不到640KB的内存是操作系统和应用程序所能够使用的,应用程序不能够使用这600KB以外的内存,这就是著名的“640KB限制”。

从上图可以看出,0xB0000H开始到0xB8000H为单色字符模式视频缓冲区;0xB8000H到C0000为彩色字符模式视频缓冲区。
内存地址空间从B8000H~BFFFFH 共32KB空间([B800:0000]到[B800:8FFF]),称为80x25彩色字符模式显示缓冲区,向这个地址写入的数据会立即出现在显示器上。
窗口大小为160x25个字节(4000个字节),每两个字节为一个字符的参数:
偶数地址存放字符的ascii码,奇数地址存放字符属性。
一行可以存放80个字符一共25行,共可以存放80x25个字符2000个字符。每个字符可以有FF=2^8=256种属性。
第7位:代表闪烁效果
第6、5、4位:代表背景色的rgb=red green blue 红绿蓝
第3位:高亮
第2、1、0 位:字符颜色的rgb
对应颜色设置成1就变成对应颜色
0010 0100 绿底红字 十六进制=0x24
0100 0001 红底蓝字 十六进制=0x41
0001 0010 蓝底绿字 十六进制=0x12
对于昨天的第二扇区的代码,直接把数据放到了0xB8000H开始的彩色字符模式视频缓冲区内。

昨天的运行效果如下。

根据如下代码,可以看出每行用160字节可以显示80个字符。


将把上节中的es由0xB800改为0xB000后,qemu没有进行显示。所以后续还是用es=0xB800H吧。

根据int13/02H调用格式:
AH = 02h AL = number of sectors to read (must be nonzero) CH = low eight bits of cylinder number CL = sector number 1-63 (bits 0-5) high two bits of cylinder (bits 6-7, hard disk only) DH = head number DL = drive number (bit 7 set for hard disk) ES:BX -> data buffer
下述代码将第二扇区复制进了内存es:bx=[new:0]位置
- mov ah, 0x02
- mov al, 1
- mov ch, 0
- mov cl, 2
- mov dh, 0
- mov bx, new
- mov es, bx
- xor bx, bx
- int 0x13
下述代码令cs:ip=[new:0]
jmp new:0
通过上述两部操作,把第二扇区的代码复制到了[new:0]的位置,同时也让指令指针指向了此位置,从而可以继续运行代码。
去掉冗余代码,最精简的可以使用第二扇区代的代码如下。
- [BITS 16]
- org 0x7C00
- start:
- mov ah, 0x02
- mov al, 1
- mov ch, 0
- mov cl, 2
- mov dh, 0
- mov bx, new
- mov es, bx
- xor bx, bx
- int 0x13
- jmp new:0
- data:
- new equ 0x0500
- times 510-($-$$) db 0
- dw 0xaa55
- sect2:
- mov ax, 0xB800
- mov es, ax
- mov byte [es:0x0000], '0'
- mov byte [es:0x0001], 0x48
- mov byte [es:0x00A0], '1'
- mov byte [es:0x00A1], 0x48
- mov byte [es:0x0140], '2'
- mov byte [es:0x0141], 0x48
- mov byte [es:0x00A0], '1'
- mov byte [es:0x00A1], 0x48
- mov byte [es:0x00A0], '1'
- mov byte [es:0x00A1], 0x48
- mov byte [es:420], 'H'
- mov byte [es:421], 0x48
- mov byte [es:422], 'E'
- mov byte [es:423], 0x68
- mov byte [es:424], 'L'
- mov byte [es:425], 0x28
- mov byte [es:426], 'L'
- mov byte [es:427], 0x38
- mov byte [es:428], 'O'
- mov byte [es:429], 0x18
- mov byte [es:430], '!'
- mov byte [es:431], 0x58
- hlt
全局描述符表Global Descriptor Table,表中每个元素8个字节,每个元素表示一个段(代码段,数据段,栈段)的信息,且GDT在进入保护模式之前必须存在,所以它必须位于1MB以下。
GDTR中存放的是GDT在内存中的基地址和其表长界限。

LDT和GDT从本质上说是相同的,只是LDT嵌套在GDT之中。
LDTR记录局部描述符表的起始位置,与GDTR不同,LDTR的内容是一个段选择子。
由于LDT本身同样是一段内存,也是一个段,所以它也有个描述符描述它,这个描述符就存储在GDT中,对应这个表述符也会有一个选择子,LDTR装载的就是这样一个选择子。
LDTR可以在程序中随时改变,通过使用lldt指令。如上图,如果装载的是Selector 2则LDTR指向的是表LDT2。
由于每个进程都有自己的一套程序段、数据段、堆栈段,有了局部描述符表LDT则可以将每个进程的程序段CS、数据段DS、堆栈段SS封装在一起,只要改变LDTR就可以实现对不同进程的段进行访问。
如下代码为建立一个GDT的示例,首先定义一段连续内存。本示例构建了3个元素CODE_DESC、DATA_DESC、VIDEO_DESC,并预留了60个元素。在lgdt命令中将GDT_LIMIT和GDT_BASE赋给了寄存器GDTR。
其中:DD 的意思是define double(4-bytes),与之相似的还有db(byte)dw(word,2-bytes)
- ; gdt
- GDT_BASE:
- dd 0x00000000
- dd 0x00000000
-
- CODE_DESC:
- dd DESC_CODE_LOW_32
- dd DESC_CODE_HIGH_32
-
- DATA_DESC:
- dd DESC_DATA_LOW_32
- dd DESC_DATA_HIGH_32
-
- VIDEO_DESC:
- dd DESC_VIDEO_LOW_32
- dd DESC_VIDEO_HIGH_32
-
- ; reserve 60 gdt entries space
- times 60 dq 0
-
- GDT_SIZE equ $ - GDT_BASE
- GDT_LIMIT equ GDT_SIZE - 1
-
- gdt_ptr:
- dw GDT_LIMIT
- dd GDT_BASE
- ; enable A20
- in al, 0x92
- or al, 0000_0010b
- out 0x92, al
-
- ; load GDT
- lgdt [gdt_ptr]
-
- ; open protection mode - set cr0 bit 0
- mov eax, cr0
- or eax, 0x00000001
- mov cr0, eax
-
- ; refresh pipeline
- jmp dword SELECTOR_CODE:protection_mode_entry
其中,SELECTOR_CODE等的定义如下:
- ;*************************** segment selector *********************************;
- RPL0 equ 00b
- RPL1 equ 01b
- RPL2 equ 10b
- RPL3 equ 11b
-
- TI_GDT equ 000b
- TI_LDT equ 100b
-
- SELECTOR_CODE equ (0x0001 << 3) + TI_GDT + RPL0
- SELECTOR_DATA equ (0x0002 << 3) + TI_GDT + RPL0
- SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
jmp dword SELECTOR_CODE:protection_mode_entry
执行这一句会把SELECTOR_CODE装入CS,并跳转到SELECTOR_CODE:protection_mode_entry处。
LDT示例代码如下所示
- ; LDT
- [SECTION .ldt]
- ALIGN 32
- LABEL_LDT:
- ; 段基址 段界限 属性
- LABEL_LDT_DESC_CODEA: Descriptor 0, CodeALen - 1, DA_C + DA_32 ; Code, 32 位
- LABEL_LDT_DESC_CODEB: Descriptor 0, CodeBLen - 1, DA_C + DA_32 ; Code, 32 位
- LDTLen equ $ - LABEL_LDT ;LDT表长度
-
- ; LDT 选择子
- SelectorLDTCodeA equ LABEL_LDT_DESC_CODEA - LABEL_LDT + SA_TIL
- SelectorLDTCodeB equ LABEL_LDT_DESC_CODEB - LABEL_LDT + SA_TIL
- ; END of [SECTION .ldt]
-
-
- ; Load LDT
- mov ax, SelectorLDT
- lldt ax ;加载LDT在GDT中的描述符