汇编:int指令、BIOS与DOS中断、端口

int指令

一、int 指令的核心定义

int(Interrupt,中断指令)是 x86 汇编中的核心指令,用于主动触发软件中断,本质是 “自愿” 调用中断处理程序(中断服务例程,ISR)。它会暂停当前程序执行,切换到操作系统 / BIOS 预设的中断处理逻辑,完成后再返回原程序继续执行。

指令格式
int n   ; n为中断号(0~255,8位立即数)

其中n是中断向量号,对应中断向量表(IVT) 中存储的中断处理程序入口地址(实模式下 IVT 位于内存 0x0000~0x03FF,每个中断占 4 字节:段地址 + 偏移地址)。


二、int 指令的执行过程(实模式为例)

CPU 执行int n时,会自动完成以下步骤(保护模式逻辑类似,但中断表为 IDT,地址结构不同):

  1. 标志寄存器入栈:将 FLAGS 寄存器压栈,随后清除 IF(中断使能)和 TF(单步)标志,避免嵌套中断干扰。
  2. 返回地址入栈:先压栈 CS(代码段寄存器),再压栈 IP(指令指针),保存当前执行位置(中断处理完成后通过iret恢复)。
  3. 查找中断处理程序:计算中断向量表地址:地址 = n * 4,从该地址读取 4 字节(高 2 字节 = CS,低 2 字节 = IP)。
  4. 跳转到中断处理程序:将读取的 CS/IP 加载到寄存器,执行中断服务例程。
  5. 中断返回:处理程序执行iret指令,反向恢复 IP、CS、FLAGS,回到原程序继续执行。

三、常见用途(经典场景)

int指令是 DOS/BIOS 时代与系统交互的核心方式,典型中断号及功能:

中断号功能调用示例(DOS 实模式)
0x00除法错误(除零)被动触发(CPU 自动),非手动调用
0x03断点中断(调试)调试器(如 DEBUG)用int 3设置断点
0x10BIOS 视频服务mov ah,0x0E; mov al,'A'; int 0x10(打印字符)
0x20DOS 程序退出int 0x20(终止当前程序)
0x21DOS 系统调用mov ah,0x01; int 0x21(读取键盘输入)
0x80Linux 系统调用(32 位)mov eax,1; int 0x80(进程退出,对应 exit)

int 21H  DOS中断例程的应用

这段 8086 汇编代码的核心功能是:在 DOS 环境下,将字符串 Welcome to masm! 显示在屏幕的第 5 行、第 12 列位置。整体流程分为三步:设置光标位置 → 输出字符串 → 程序退出。

assume cs: code  ; 声明代码段寄存器cs关联到code段
data segment     ; 定义数据段,存放要显示的字符串
    db ' Welcome to masm!','$'  ; 字符串以'$'结尾(DOS中断int 21h的09h功能要求)
data ends        ; 数据段结束

code segment     ; 定义代码段,存放执行逻辑
start:           ; 程序入口标签

mov ah,2         ; ah=2:功能号,调用int 10h的光标位置设置功能
mov bh,0         ; bh=0:指定显示页为第0页(DOS默认显示页)
mov dh,5         ; dh=5:光标行号(从0开始计数,第5行对应屏幕实际第6行)
mov dl,12        ; dl=12:光标列号(从0开始计数,第12列)
int 10h          ; 调用BIOS中断,完成光标定位

mov ax, data     ; 将数据段段地址送入ax(8086不能直接给ds赋值,需通过ax中转)
mov ds, ax       ; ds=data:设置数据段寄存器,指向字符串所在的段
mov dx,0         ; dx=0:字符串在数据段内的偏移地址(字符串从data段偏移0处开始)
mov ah,9         ; ah=9:调用DOS中断int 21h的字符串输出功能
int 21h          ; 执行中断,输出ds:dx指向的字符串(直到'$'结束)

mov ax,4c00h     ; ax=4c00h:ah=4Ch(退出功能号),al=00h(返回码,通常设为0)
int 21h          ; 调用DOS中断,终止程序并返回DOS系统

code ends        ; 代码段结束
end start        ; 汇编结束,指定程序入口为start标签

关键:int 21h的09h功能 要求字符串必须以'$'结尾,否则会输出乱码。

运行效果:

关键知识点总结

中断 / 功能号功能寄存器参数要求
int 10h (ah=2)设置光标位置bh = 显示页,dh = 行号,dl = 列号
int 21h (ah=9)输出字符串ds:dx 指向字符串首地址,字符串以 '$' 结尾
int 21h (ah=4Ch)程序退出al = 返回码(通常为 0)

I/O端口的读写

在 8086 CPU 中,I/O 端口具有独立的地址空间,这被称为 “独立 I/O 编址”(也称为 “端口映射 I/O” 或 “Port-Mapped I/O, PMIO”)。
具体说明:

    8086 CPU 使用 16 位地址线 来寻址 I/O 端口,因此最多可以寻址 2^16 = 65536(即 64KB)个 I/O 端口地址,地址范围为 0x0000 到 0xFFFF
    这个 I/O 地址空间 与内存地址空间是分开的。也就是说,I/O 端口地址和内存地址互不冲突,各自独立编址。
    访问 I/O 端口使用专门的指令:
        IN 指令:从 I/O 端口读取数据(如 IN AL, DX)。
        OUT 指令:向 I/O 端口写入数据(如 OUT DX, AL)。

对比:统一编址(Memory-Mapped I/O)

在某些其他体系结构(如 ARM、现代 x86 的部分外设)中,I/O 设备被映射到内存地址空间中,称为 统一编址 或 内存映射 I/O(MMIO)。这种情况下,访问 I/O 就像访问普通内存一样,使用 MOV 等内存访问指令。

但在 8086 架构中采用的是独立 I/O 编址方式,这是其设计特点之一。

✅ 总结:

8086 CPU 中,I/O 端口确实拥有 独立于内存的地址空间,使用专用的 IN/OUT 指令进行访问,地址范围为 0x0000 ~ 0xFFFF(共 64KB)。


8086CPU的CMOS端口

8086CPU 通过70H(地址端口)71H(数据端口)两个专用 I/O 端口与 CMOS RAM 芯片交互,这两个端口采用地址 - 数据分离的访问方式,是操作 CMOS 实时钟和系统配置信息的核心通道。

一、CMOS 端口的核心结构与功能

CMOS RAM 芯片内置 128 字节 RAM(早期为 64 字节),包含实时钟、系统配置信息等,靠电池供电保证断电后数据不丢失。8086CPU 通过 70H 和 71H 端口实现对 CMOS 的读写,具体分工如下:

端口地址类型权限功能
70H地址端口只写(8bit)写入要访问的 CMOS RAM 单元地址(低 6 位为有效地址,对应 CMOS 的 00H~3FH 单元)
71H数据端口可读可写(8bit)读取或写入 70H 端口指定的 CMOS RAM 单元数据

二、8086CPU 访问 CMOS 端口的流程

访问 CMOS RAM 需分两步执行OUTIN指令,先指定地址,再读写数据:

  1. 写地址到 70H 端口:用OUT 70H, AL指令将要读取的目标CMOS 单元地址送入地址端口;
  2. 读写 71H 数据端口
    • 读操作:用IN AL, 71H读取指定单元的数据;
    • 写操作:用OUT 71H, AL将数据写入指定单元。
示例:读取 CMOS 的 “分钟” 信息(CMOS 02H 单元存储分钟)
; 读取CMOS 02H单元的分钟数据
MOV AL, 02H       ; 将CMOS单元地址02H送入AL
OUT 70H, AL       ; 输出到地址端口70H
IN AL, 71H        ; 从数据端口71H读取分钟数据(BCD码格式)

三、CMOS 端口的典型应用场景

CMOS 的 00H~0DH 单元用于存储实时钟信息,其余单元保存系统配置(如硬盘类型、内存容量等),通过 70H/71H 端口可实现以下操作:

  • 读取系统时间:访问 00H(秒)、02H(分)、04H(时)等单元;
  • 修改系统配置:向 10H(软盘类型)、12H(硬盘类型)等单元写入配置数据;
  • 控制实时钟工作模式:通过 0AH(状态寄存器 A)、0BH(状态寄存器 B)设置时钟频率、中断使能等。

四、BCD码

在 x86 架构的 PC 系统中,CMOS RAM(通常由 RTC 芯片如 Motorola MC146818 或其兼容芯片实现)使用一组固定的 I/O 端口(0x70 为索引端口,0x71 为数据端口)来访问内部寄存器。时间信息以 BCD 格式(默认,除非设置为二进制模式)存储在特定地址(偏移)中。

以下是 当前时间(2025年12月10日,星期三 21:30) 在 CMOS RAM 中的存放形式,包括 寄存器地址、字段含义、十进制值、BCD 编码(十六进制表示) 的完整表格:

注:年、月、日、秒、分、时,每个信息的长度为1个字节。

问题:从CMOS RAM的8号单元中读出当前月份的BCD码,以十进制形式显示到屏幕上

assume cs:code
code segment
start:
    ; 步骤1:读取CMOS 8号寄存器(小时数,BCD码)
    mov al,8           ; CMOS地址寄存器端口70H写入要读取的寄存器号8
    out 70h,al         ; 将寄存器号写入70H端口
    in al,71h          ; 从71H数据端口读取8号寄存器的值到AL

    ; 步骤2:拆分高4位(十位)和低4位(个位)
    mov ah,al          ; 暂存AL的值到AH
    mov cl,4           
    shr ah,cl          ; AH右移4位,得到十位(高4位)
    and al,00001111B   ; AL与运算,得到个位(低4位)

    ; 步骤3:BCD码转ASCII码(+30H是数字0-9的ASCII偏移)
    add ah,30h         ; 十位转ASCII
    add al,30h         ; 个位转ASCII

    ; 步骤4:写入显存(B800H是文本模式显存段地址)
    mov bx,0B800H      
    mov es,bx          ; 修正:原ex是笔误,应为es(段寄存器)
    ; 屏幕第12行40列:显存偏移=160*行 + 2*列(每个字符占2字节:ASCII+属性)
    mov byte ptr es:[160*12 + 40*2], ah   ; 十位ASCII写入40列
    
    mov byte ptr es:[160*12 + 40*2 + 2], al   ; 个位ASCII写入41列
    

    ; 步骤5:程序退出
    mov ax,4c00h       
    int 21h            ; DOS中断,程序正常退出

code ends
end start

关键逻辑解释
步骤详细说明
CMOS 读取CMOS 时钟的端口:70H 是地址端口(写入要读取的寄存器号),71H 是数据端口(读取对应值)。8 号寄存器存储小时数(BCD 格式,如 15 点存储为 0x15)。
拆分高低 4 位shr ah,4 将 AH(原 AL)右移 4 位,提取高 4 位(十位),即:0001 0010->0000 0001,12月中的1;and al,0x0F 保留低 4 位(个位),即:0001 0010 -> 0000 0010,12月中的2
BCD 转 ASCII数字 0-9 的 ASCII 码是 0x30-0x39,因此加 0x30 即可完成转换。
显存写入文本模式显存段地址为 0xB800,每个字符占 2 字节:第 1 字节是 ASCII 码。第 12 行 40 列的偏移计算:160*12 + 2*40(每行 80 字符,共 160 字节)。

posted @ 2025-12-10 22:36  chenlight  阅读(8)  评论(0)    收藏  举报  来源