x86汇编总结实模式篇
基本信息
8086处理器
Intel 8086是由英特尔公司于1976年初开始设计,于1978年年中发布的Intel第一款16位微处理器。
随后于1979年,又推出了Intel 8088处理器,它是Intel以8086为基础设计的微处理器,拥有16比特寄存器和8位外部资料总线。成为8086的一个低成本的简化产品,并用在IBM PC的原始设计中的处理器(包括广为人所知的IBM PC XT)中而被人知晓。
8086是Intel最成功的处理器系列x86架构的开端。此处理器所运行的就是实模式。当然后来的所有x86处理器都兼容实模式。8086的引脚图如下:
PC机
- 第一代PC机,以IBM公司的
IBM PC/XT
机为代表,CPU是8088,诞生于1981年。 - 第二代PC机,IBM公司于1984年推出的
IBM PC/AT
,采用80286为CPU,其数据处理和存储管理能力都大大提高。
实模式下内存布局
起始地址 | 结束地址 | 大小 | 用途 |
---|---|---|---|
0x00000 |
0x003FF |
1KB | 中断向量表 |
0x00400 |
0x004FF |
256B | BIOS数据区 |
0x00500 |
0x07BFF |
30KB-256B | 可用区域 |
0x07C00 |
0x07DFF |
512B | MBR加载区 |
0x07E00 |
0x9FBFF |
607.6KB | 可用区域 |
0x9FC00 |
0x9FFFF |
1KB | 扩展BIOS数据区 |
0xA0000 |
0xAFFFF |
64KB | 用于彩色显示适配器 |
0xB0000 |
0xB7FFF |
32KB | 用于黑白显示适配器 |
0xB8000 |
0xBFFFF |
32KB | 用于文本显示适配器 |
0xC0000 |
0xC7FFF |
32KB | 显示适配器BIOS |
0xC8000 |
0xEFFFF |
160KB | 映射内存 |
0xF0000 |
0xFFFEF |
64KB-16B | 系统BIOS |
0xFFFF0 |
0xFFFFF |
16B | 系统BIOS入口地址区域 |
字符样式
在0xB8000开始,显示器以两个字节显示一个字符。其中,低字节是要显示的字符。高字节是样式,样式的定义如下:
- 高4位表示背景色
| K | R | G | B |
K
: 0 - 背景不闪烁,1 - 背景闪烁
- 低4位表示前景色
| I | R | G | B |
I
: 0 - 正常显示,1 - 高亮显示
中断
中断向量表
中断控制器8259
Intel公司的8259是一系列的可编程中断控制器芯片的总称,此系列的芯片原本包含 8259、8259A、8259B。关于此系列中的芯片,其差异性并不十分明了。据信,NEC开发出 8259A,而8259B只不过是PC/AT上对于8259A的另一种称呼。8259是一个多任务器,它会从多个中断源中挑出一个中断信号,并输出。一开始虽然是一颗独立的芯片,但现代主板上,它成了南桥的一部分。
引脚:
CS
: 片选WR
: 写RD
: 读D
0-D
7: 双向数据总线CAS
0-CAS
2: 级连总线SP/EN
: SLAVE PROGRAM/ENABLE BUFFERINT
: 中断IR
0-IR
7: 中断请求INTA
: 中断确认A
0: 这个引脚与CS
、WR
、RD
、引脚一起使用,8259A使用它来帮助解析CPU写入的各种命令字和CPU希望读取的状态。 一般连在CPU的A0地址线上(在8086、8088上连在了A1地址线),如果真按8259A手册上所说,8086、8088是用地址线A1
连接到8259A的A0
的话,那么凡是需要8259A.A0 == 1
的命令字必然会有8086.A1 == 1
,然而在PC上操作8259A的端口分别是,主片:0x20
,0x21
;从片:0xA0
,0xA1
。这四个端口的A1
都不等于1
。因此,合理猜测,这个手册上说的是另一种使用8086芯片的硬件系统的情况。而在PC/XT机和PC/A机T中,芯片是用地址线A0
连接到8259A的A0
的。(在PC/XT中只有一个8259,到了PC/AT新增加了一个)
我找到了最早的PC机,IBM 5150
(IBM PC)的电路图,证实确实连的是A0地址线。之后的IBM 5160
(IBM XT)和IBM 5170
(IBM AT)也一样。
以及在《IBM 5150 Technical Reference》第40页,也就是2-23页。
https://www.eeeguide.com/interfacing-8259-with-8085/ https://bochs.sourceforge.io/techspec/PORTS.LST
IRQ2和IRQ9
为什么要将IRQ2重定向到IRQ9上?这仍然是由于兼容性问题造成的。早期的IBM PC/XT只有一个8259A,这样就只能处理8种IRQ。但很快就发现这根本不能满足需求。所以到了IBM PC/AT,又以级连的方式增加了一个8259A,这样就可以多处理7种IRQ。原来的8259A被称作Master PIC,新增的被称作Slave PIC。但由于CPU只有1根中断线,Slave PIC不得不级连在Master PIC上,占用了IRQ2,那么在IBM PC/XT上使用IRQ2的设备将无法再使用它;但新的系统又必须和原有系统保持兼容,怎么办?
由于新增加的Slave PIC在原有系统中不存在,所以,设计者从Slave PIC的IRQ中挑出IRQ9,要求软件设计者将原来的IRQ2重定向到IRQ9上,也就是说IRQ9的中断服务程序需要去掉用IRQ2的中断服务程序。 这样,将原来接在IRQ2上的设备现在接在IRQ9上,在软件上只需要增加IRQ9的中断服务程序,由它调用IRQ2的中断服务程序,就可以和原有系统保 持兼容。而在当时,增加的IRQ9中断服务程序是由PC开发商开发的BIOS提供的,所以就从根本上保证了兼容。
寄存器:
IMR
(Interrupt Mask Register) 屏蔽寄存器,指定的位对应的中断会被屏蔽IRR
(Interrupt Request Register) 中断请求寄存器,可以被直接访问的寄存器,指示等待响应的中断请求ISR
(In Sevice Register) 中断服务寄存器,指示被CPU响应,但是还在等待中断结束指令(EOI)。
端口:
Master
:0x20: W ICW1. W OCW2. W OCW3. R IRR. R ISR.
0x21: W ICW2. W ICW3. W ICW4. RW ICW3. RW ICW4. RW IMR
Slave
:0xA0: W ICW1. W OCW2. W OCW3. R IRR. R ISR.
0xA1: W ICW2. W ICW3. W ICW4. RW IMR.
ICW1
端口 0x20 : 0xA0
+---+---+---+---+---+---+---+---+
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---+---+---+---+---+---+---+---+
| | | | | | | |
| | | | | | | +--[1 Need ICW4 : 0 Not]
| | | | | | +------[1 Single PIC : 0 级连]
| | | | | +----------[1 4 Bytes Interrupt Vector : 0 8]
| | | | +--------------[1 Level Triggered Mode : 0 Edge]
| | | +------------------[ICW1强制为1]
+---+---+----------------------[x86不用,直接为0,这是MCS-80/85的中断向量地址(Interrupt Vector Address)]
ICW2
端口 0x21 : 0xA1
+---+---+---+---+---+---+---+---+
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---+---+---+---+---+---+---+---+
| | | | | | | |
| | | | | +---+---+--[x86不用]
| | | | |
| | | | |
+---+---+---+---+--------------[x86中断向量地址]
ICW3 for Master
端口 0x21 : 0xA1
+---+---+---+---+---+---+---+---+
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---+---+---+---+---+---+---+---+
| | | | | | | |
+---+---+---+---+---+---+---+--[1 IR输入引脚有Slave : 0 IR输入引脚没有Slave]
ICW3 for Slaver
端口 0x21 : 0xA1
+---+---+---+---+---+---+---+---+
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---+---+---+---+---+---+---+---+
| | | | | | | |
| | | | | +---+---+--[Slaver连接的Master的IR引脚]
| | | | |
| | | | |
+---+---+---+---+--------------[00000]
ICW4
端口 0x21 : 0xA1
+---+---+---+---+---+---+---+---+
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---+---+---+---+---+---+---+---+
| | | | | | | |
| | | | | | | +--[1 80x86 Mode : 0 MCS 80/85]
| | | | | | +------[1 Auto EOI : 0 Normal EOI]
| | | | +---+----------[0X Non - Buffered Mode :
| | | | 11 Master Buffered Mode : 10 Slave]
| | | |
| | | +------------------[1 Special Fully Nested Mode : 0 Not]
+---+---+----------------------[000 Not Used]
OCW1
端口 0x21 : 0xA1
+---+---+---+---+---+---+---+---+
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---+---+---+---+---+---+---+---+
| | | | | | | |
+---+---+---+---+---+---+---+--[1 屏蔽 : 0 清除屏蔽]
OCW2
端口 0x20 : 0xA0
+---+---+---+---+---+---+---+---+
| 7 | 6 |EOI| 4 | 3 | 2 | 1 | 0 |
+---+---+---+---+---+---+---+---+
| | | | | +---+---+
| | | | | | +--[000 Act On IRQ0]
| | | | | | |--[001 Act On IRQ1]
| | | | | | |--[010 Act On IRQ2]
| | | | | |_____ |--[011 Act On IRQ3]
| | | | | |--[100 Act On IRQ4]
| | | +---+---[00] |--[101 Act On IRQ5]
| | | |--[110 Act On IRQ6]
+---+---+ +--[111 Act On IRQ7]
|
| +--[000 Rotate In Auto EOI Mode (Clear)]
| |--[001 Non Specific EOI]]
| |--[010 Reserved]
|_______|--[011 Specific EOI]
|--[100 Rotate In Auto EOI Mode (Set)]
|--[101 Rotate On Non-Specific EOI]
|--[110 Set Priority Command (Use Bits 2:0)]
+--[111 Rotate On Specific EOI (Use Bits 2:0)]
OCW3
端口 0x20 : 0xA0
+---+---+---+---+---+---+---+---+
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+---+---+---+---+---+---+---+---+
| | | | | | +---+
| | | | | | | +--[00 Reserved]
| | | | | | |____|--[01 Reserved]
| | | | | | |--[10 Next Read Returns IRR]
| | | | | | +--[11 Next Read Returns ISR]
| | | | | +-------------[1 Poll Command : 0 No Poll Command]
| | | | +-----------------[1]
| +---+ +---------------------[0]
| | +--[00 Reserved]
| |_____|--[01 Reserved]
| |--[10 Reset Special Mask]
| +--[11 Set Special Mask]
+--[0]
以下是一个实模式下接受键盘中断的程序,每按一次键盘,屏幕上的字符变化一下。
BITS 16
ORG 0x7C00
IntVect EQU 0x20
_start:
jmp 0x0000:entry
entry:
mov ax, cs
mov ds, ax
mov ss, ax
mov sp, 0x7C00
; 调用BIOS清屏,并设置为80x25的彩色文本模式
; 功能号: 0x00
; 用途: 设置显示模式
; 参数: AL = 显示模式号
; AL=0x00: 40x25黑白文本
; AL=0x01: 40x25彩色文本
; AL=0x02: 80x25黑白文本
; AL=0x03: 80x25彩色文本
; ...
mov ah, 0x00
mov al, 0x03
int 0x10
; 将es设为显存段地址
mov ax, 0xB800
mov es, ax
; 在中断向量表中设置键盘中断程序入口地址
mov bx, (IntVect+1)*4
mov ax, ds
mov cx, kbd_isr
mov [bx + 0x00], cx
mov [bx + 0x02], ax
; 准备初始化8259
cli
; 先初始化主片
; ICW1: 需要ICW4,级连状态
mov al, 0001_0001b
out 0x20, al
; ICW2: 中断向量地址为0x20
mov al, IntVect
out 0x21, al
; ICW3: 主片2号IR输入引脚连着从片
mov al, 0000_0100b
out 0x21, al
; ICW4: 非缓冲,手动发送EOI的80x86模式
mov al, 0000_0001b
out 0x21, al
; 再初始化从片
; ICW1: 需要ICW4,级连状态
mov al, 0001_0001b
out 0xA0, al
; ICW2: 中断向量地址为0x28
mov al, IntVect+0x08
out 0xA1, al
; ICW3: 从片连在主片的2号IR输入引脚
mov al, 0000_0010b
out 0xA1, al
; ICW4: 非缓冲,手动发送EOI的80x86模式
mov al, 0000_0001b
out 0xA1, al
; 主片OCW1: 解除主片上键盘的中断屏蔽
mov al, 1111_1101b
out 0x21, al
; 从片OCW1: 屏蔽从片上的所有中断
mov al, 1111_1111b
out 0xA1, al
; 打开CPU中断允许标志
sti
run:
hlt
jmp run
kbd_isr:
push ax
push bx
; 从键盘缓冲区中读取输入
in al, 0x60
test al, 0x80
jnz .ignore ; 如果是break code就跳过
; 变化显示字符
mov bl, [cnt]
cmp bl, 26
jbe .letter
mov bl, 0
.letter:
mov al, bl
add al, 'A'
inc bl
mov [cnt], bl
mov byte [es:0], al
mov byte [es:1], 0x0C
.ignore:
; 给主片发送EOI
; OCW2
mov al, 0110_0001b
out 0x20, al
pop bx
pop ax
iret
cnt db 0
times 510 - ($ - $$) db 0
dw 0xAA55
在MBR结尾为什么使用0xAA55
在内存中的字节序是: 0x55
, 0xAA
。
0x55
二进制为01010101
0xAA
二进制为10101010
变成串行电平的话就是一个占空比为50%的方波, 这种方波在电路中最容易被分辨是否受干扰或者畸变,在实际波形的观察中也最容易看出毛病所在。
历史包袱
0x7C00的由来
1981年8月12日,IBM推出了第一款PC机IBM PC 5150。1982年IBM与美国司法部达成垄断官司的和解,条件是IBM允许竞争对手的发展,因此IBM公开了IBM PC上除了BIOS之外的全部技术资料,从而形成了PC的开放标准。而最先引入0x7C00
的这个机器就是IBM PC 5150,因此之后所有的PC机就沿用了0x7C00
这个地址。
而IBM PC 5150为什么要用0x7C00
这个地址呢?虽然5150要求的最小内存是16KB,但是要运行DOS 1.0的话,最小的内存要求是32KB。32KB 对应 0x8000,那么引导程序512B,和为引导程序预留的栈空间512B,加起来就是1KB,因此在当时为了把尽可能多的内存留给用户,选择把引导程序加载到(32KB-1KB) = 31KB = 0x7C00
,然后默认引导程序把栈空间选为[0x7E00, 0x8000)
,就是最好的选择,这就是0x7C00的由来。
A20地址线问题的由来
A20地址线就是第21根地址线。
8086处理器有20根地址线: A00-A19
,所以可以寻址220=1MB的内存。又已知8080的寻址方式是: 段地址:偏移地址=段地址x16+偏移地址
,那么在8086机器上这种寻址方式不仅能表示1MB以内([0x00000, 0xFFFFF])的地址,也能表示一些1MB(0x100000)以上的地址。比如: 0xFFFF:0x00FF=0xFFFF0+0x00FF=0x1000EF
,然而在只有20位地址线的机器上,这个地址最高位不能保存,从而回绕成0x000EF
也就是1MB以内的地址。在那个时候一定有程序员利用这个特性来写程序,这就导致后来的兼容性问题。
之后Intel发布80286处理器,该处理器的地址线已经达到24根,理论可寻址范围能到224=16MB。如果直接把这颗CPU装到PC机上,那么当初的0x1000EF
这种地址就不会回绕成0x000EF
,而是真的会寻址到0x1000EF
这个地址。这就会造成不兼容那些已经利用8086地址回绕特性的程序,这在当时是不能接受的。
因此IBM为解决这个问题,就找了一个键盘控制器的空闲输出线(控制端口为0x60
),并加了一个与门
,将这个输出线与处理器的A20地址线作为该与门的输入,与门的输出作为实际使用的A20。如果键盘控制器输出线输出0,则A20相当于被强制置0;如果键盘控制器输出线输出1,则相当于直接启用A20。如果不手动打开这个控制器,那么默认就输出0,就实现了和8086的兼容。
但这种方式非常繁琐,需要等键盘不忙,还要不断地判断状态。
直到80486发布,该处理本身自带A20M#
引脚,意思是A20屏蔽(A20 Mask),配合上ICH(I/O controller hub,又名”输入输出控制器集中芯片”或”输入输出控制中心”,负责连接PCI总线,IDE设备,I/O设备等,是英特尔的南桥芯片系列名称。)芯片可以解决这个问题。它有一个用于兼容老式设备的端口0x92
。8个bit中,使用了第0、1两位,第2~7位保留未用。
- 第0位为
INIT_NOW
- 第1位为
ALT_A20_GATE
INIT_NOW
意思是”现在初始化“,用于初始化处理器,当它从0过滤到1时,ICH会使处理的INIT#
引脚的电平变低(有效),并保持至少16个PCI时钟周期。简单来说,向端口0x92
写1,将会复位处理器,导致计算机重启。
当INIT_NOW
从0过滤到1时,ALT_A20_GATE
将被置1。
ALT_A20_GATE
和键盘控制器一起通过一个或门连接到处理器的A20M#
引脚。
也就是说,计算机启动时,A20地址线是自动启用的。
因此现在为保险起见常用的打开A20的方式为:
in al, 0x92
or al, 0000_0010b
out 0x92, al