ZYB ARTICLES REPOS

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机

实模式下内存布局

起始地址 结束地址 大小 用途
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开始,显示器以两个字节显示一个字符。其中,低字节是要显示的字符。高字节是样式,样式的定义如下:

中断

中断向量表

中断控制器8259

Intel公司的8259是一系列的可编程中断控制器芯片的总称,此系列的芯片原本包含 8259、8259A、8259B。关于此系列中的芯片,其差异性并不十分明了。据信,NEC开发出 8259A,而8259B只不过是PC/AT上对于8259A的另一种称呼。8259是一个多任务器,它会从多个中断源中挑出一个中断信号,并输出。一开始虽然是一颗独立的芯片,但现代主板上,它成了南桥的一部分。

《Intel 8259A》

引脚:

我找到了最早的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提供的,所以就从根本上保证了兼容。

寄存器:

端口:

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

变成串行电平的话就是一个占空比为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位保留未用。

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