8086内存安排
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中断
根据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
GDT
全局描述符表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示例代码如下所示
; 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中的描述符