简介:这份课后习题答案对应《16/32位微机原理、汇编语言及接口技术教程》,面向计算机、电子与自动化等专业的学生,以及备考微机原理、汇编语言与接口技术课程的学习者,用于核对课后练习、梳理章节重点和查漏补缺。压缩包内仅含1个PDF文件,约153KB,体量轻便,适合在电脑或移动端随时查阅。内容围绕微型计算机系统概述、8088微处理器指令系统、总线与存储器、逻辑地址与物理地址转换、补码与BCD码、ASCII码以及汇编子程序设计等核心考点展开,并给出多道习题的参考解答与推导过程,如求数据块最大值、内存区域填充、标志位差异、地址计算等。已有303人学习下载,可作为期末复习、习题订正和考前知识串联的辅助资料,帮助读者对照题目理解解题思路与关键概念。
1. 把课后答案当验算器,而不是背诵材料
把《16、32位微机原理、汇编语言及接口技术教程》的课后习题答案当成背诵材料,是这门课最亏的用法。更省时间的做法是把答案当验算器:先在 EMU8086 或 DOSBox 加 MASM 的环境里把题面代码跑出结果,再回头对答案,对不上的地方才是真正要补的知识点。
这门课三块内容性质完全不同。16 位实模式的习题大多靠“算”——物理地址、标志位、寻址方式、指令执行后的寄存器快照;32 位部分偏向“读”——保护模式下的段描述符、分页与寄存器宽度差异;接口技术的习题必须“连”起来看——8255 的方式控制字、8253 的计数初值、8259 的中断向量,写错一位整个结果都变形。
所以答案文件适合两类人:正在准备上机和期末的在校生,以及临时接手老工控汇编代码、需要快速回忆 8086 细节的工程师。下面按“算得清、跑得动、验得了”三步走,重点放在怎么自己把答案推出来、把代码跑起来。
2. 16 位到 32 位:物理地址、寻址方式与标志位怎么算准
课后习题里最容易“看着会、一算就错”的,是物理地址换算、寻址方式判断和标志位推演这三类。它们占了 16 位部分的多数篇幅,而答案通常只给一个数字,错了也看不出错在哪一步。把计算过程固化成可复现的步骤,比多刷十道题管用。
2.1 段地址与偏移量的换算类习题
8086 在实模式下只有 20 根地址线,物理地址等于段基址乘以 16 再加偏移量,也就是段基址左移 4 位后与偏移量相加。段基址来自 CS、DS、ES、SS 中的一个,偏移量由寻址方式给出。习题一般给“逻辑地址 1234H:5678H,求物理地址”,或者反过来给物理地址,问有几种合法的段基址方案。
默认段寄存器的配对是失分重灾区,判断寻址方式时必须先确定用哪个段:
| 寻址中出现的寄存器 | 默认段寄存器 | 能否加段超越前缀 |
|---|---|---|
| BX、SI、DI | DS | 可以 |
| BP、SP | SS | 可以 |
| 取指令(CS:IP) | CS | 不可以 |
| 串操作源操作数 | DS | 可以 |
| 串操作目的操作数 | ES | 不可以 |
直接寻址如[1234H]默认也是 DS。只有 BP 参与的寻址默认走 SS,这是栈帧访问的基础,也是选择题里最爱考的一条。
把换算固化成一段 MASM 代码,用它验算所有同类题:
; seg_off.asm 把 段:偏移 换算成 20 位物理地址 ; 输入: AX=段基址, BX=偏移量 ; 输出: DX 存 bit16~bit19, AX 存低 16 位 mov ax, 1234h mov bx, 5678h mov cl, 4 shl ax, cl ; 段基址左移 4 位, 等价于乘 16 add ax, bx ; 得到物理地址低 16 位 adc dx, 0 ; 若相加有进位, CF 落到 DX 高位参数说明:8086 的移位指令用 CL 指定移位次数,直接写shl ax,4在 MASM 6.x 下会报错,这是新手第一个坑。adc dx,0的作用是把相加产生的进位接进高位,20 位地址不能只看 AX。反过来从物理地址推段基址时,段基址必须是 16 的倍数,所以 179B8H 既能由 1234H:5678H 得到,也能由 1000H:79B8H 得到,习题问“几种方案”时要把所有合法段值列全,别只写一种。
2.2 32 位保护模式习题与实模式的三个差异
做 32 位部分的习题,先把差异表记住,否则会习惯性套用实模式的公式。
| 对比项 | 16 位实模式 | 32 位保护模式 |
|---|---|---|
| 通用寄存器宽度 | AX、BX 等 16 位 | EAX、EBX 等 32 位 |
| 段寄存器的含义 | 段基址的高 16 位 | 选择子,指向 GDT 或 LDT 中的描述符 |
| 物理地址计算 | 段基址左移 4 位加偏移 | 描述符中的基址直接加偏移 |
| 偏移量范围 | 64KB | 由段限长限制,最大 4GB |
| 段寄存器装载 | mov ds, ax | mov ax, sel后mov ds, ax |
段描述符里的基址虽是 32 位,却被拆成三块存放;限长是 20 位,G 位为 1 时单位是 4KB,为 0 时单位是字节。习题常问“基址 00010000H、限长 000FFH、G=0,可寻址范围是多少”,答案是 00010000H 到 000100FFH,共 256 字节;G=1 时要用限长乘以 4KB。判断越界时,访问的偏移量必须小于等于限长,等于限长是合法的,这条细节经常被漏掉。
2.3 标志位判定题的推演方法
加法类题目看两个标志:CF 是有没有从最高位进位,OF 是两个同号数相加是否变成异号。减法类题目里 CF 表示借位,OF 的判定逻辑不变。逻辑运算会清零 CF 和 OF,移位指令把最后移出的那一位送进 CF。
手算容易漏掉 OF,用下面这段代码把标志位直接读出来对照:
; flags.asm 执行一条指令后取 FLAGS 快照 mov ax, 7fffh add ax, 0001h ; 7FFFH+1 = 8000H, 符号位翻转 pushf ; FLAGS 压栈 pop ax ; AX 低 8 位含 SF ZF AF PF CF 等 lahf ; 另一种做法, AH = SF:ZF:0:AF:0:PF:1:CF分析:7FFFH 加 0001H 结果是 8000H,最高位没有进位所以 CF=0;两个正数相加得到负数,所以 OF=1;结果最高位为 1 所以 SF=1;结果非零所以 ZF=0。pushf和pop ax的组合能把 16 位标志寄存器完整取到 AX,lahf只取低 8 位但更省事。注意lahf取不到 OF,涉及溢出的题目必须用 pushf 的快照,否则对不上答案。
3. MASM 加 io.lib 的上机链路:把习题代码跑成 exe 并验证端口读写
上机题通常要求用 MASM 编译、用 io.lib 做端口读写,这条链路本身不长,但每一步都有固定的坑。先把“编译、链接、调试”三步跑通,再谈端口是否真的读到值。
3.1 最小可运行流程:masm、link、td 三步
masm hello.asm; # 生成 hello.obj, 分号表示后续提示一律回车接受默认值 link hello.obj; # 生成 hello.exe, 需要库时写成 link hello.obj,hello,,io.lib; td hello.exe # Turbo Debugger 单步; 没有 td 就用 debug hello.exe分号是关键。MASM 和 LINK 会连续询问目标文件名、列表文件名、库文件名、段对齐方式,逐项回车容易漏项,加一个分号等于一次性接受全部默认值。需要链接 io.lib 时,LINK 的库文件参数是第三个字段,写法容易记错,直接把它写在对象文件后面用逗号占位更稳。
反复改代码时写一个批处理更省事:
rem build.bat 用法: build hello @echo off if "%1"=="" goto usage masm %1.asm; if errorlevel 1 goto err link %1.obj; if errorlevel 1 goto err echo BUILD OK goto end :err echo BUILD FAILED goto end :usage echo usage: build filename_without_ext :end参数说明:%1是传入的文件名,不带扩展名;errorlevel 1判断上一步是否失败,MASM 或 LINK 只要有错误就会返回非零值。注意批处理里判断错误必须紧跟在命令后面,中间插入别的命令会把 errorlevel 覆盖掉,这是批处理最常见的低级错误。
3.2 io.lib 的链接方式与端口读写参数
io.lib 是一组端口读写函数的集合,常用的是_inp、_outp、_inpw、_outpw四个。在 MASM 里调用它们要先声明为外部符号,并按 C 调用约定传参:参数从右往左压栈,由调用方负责清栈。
| 函数 | 原型 | 参数入栈顺序 | 调用后清栈 |
|---|---|---|---|
_inp | int _inp(unsigned port) | port | add sp, 2 |
_inpw | unsigned _inpw(unsigned port) | port | add sp, 2 |
_outp | int _outp(unsigned port, int value) | value,port | add sp, 4 |
_outpw | unsigned _outpw(unsigned port, unsigned value) | value,port | add sp, 4 |
; port_io.asm 读 8255 的 C 口状态, 再写到 B 口 .model small .stack 100h extern _inp:near, _outp:near ; C 库函数, 参数从右往左压栈 .code start: mov ax, 0062h ; 8255 的 C 口地址 push ax call _inp ; AL 返回 C 口内容 add sp, 2 ; 调用方清栈, 保持栈平衡 mov ah, 0 push ax ; value 参数先入栈 mov ax, 0061h ; B 口地址 push ax ; port 参数后入栈 call _outp add sp, 4 ; 两个 16 位参数, 清 4 字节 mov ax, 4c00h int 21h end start逻辑说明:读函数只有一个参数,压一次栈、清 2 字节;写函数有两个参数,先压 value 再压 port,清 4 字节。如果符号名对不上,比如报_inp未定义,常见原因是不同版本的库把符号做成了__inp或inp_,用link /map生成映射文件看一眼导出名就能确定,比猜快得多。非 C 约定的库(例如 pascal 约定)不需要调用方清栈,这时多写一句add sp反而会把栈搞乱,表现为子程序返回后跳到错误地址。
3.3 端口实验跑不通时的排错顺序
按下面顺序排查,能覆盖大部分卡壳场景:
| 现象 | 常见原因 | 处理 |
|---|---|---|
| A2006 undefined symbol | 没写 extern 声明 | 补extern _inp:near |
| L2029 unresolved external | 链接时没带库 | link x.obj,x,,io.lib; |
| 运行直接异常退出 | 在受保护的操作系统用户态执行端口指令 | 换纯实模式环境或仿真器 |
| 读回恒定值 | 端口地址算错 | 基址加偏移重新核对 |
| 输出无变化 | 控制字没先写或写错端口 | 先写控制口再写数据口 |
注意:端口读写指令属于特权操作,在普通用户态进程里执行会触发异常。上机时优先在实模式或专门的仿真环境里跑,别在这上面浪费时间。
4. 接口芯片习题的工程化验算:8255、8253、8259 三件套
接口部分的习题几乎都是“给一个应用场景,写出初始化程序”。这类题的答案对不对,取决于控制字每一位拼得对不对,以及初始化顺序对不对。把三个芯片的控制字格式背熟之后,用代码验算比逐题对答案高效得多。
4.1 8255 方式控制字的位拼装
8255 的控制字分两种:方式选择控制字(D7=1)和 C 口按位置位复位控制字(D7=0)。方式字的位定义如下:
| 位 | 名称 | 含义 |
|---|---|---|
| D7 | 标志位 | 恒为 1 |
| D6 D5 | A 组方式 | 00 方式0,01 方式1,1x 方式2 |
| D4 | A 口方向 | 1 输入,0 输出 |
| D3 | C 口高 4 位 | 1 输入,0 输出 |
| D2 | B 组方式 | 0 方式0,1 方式1 |
| D1 | B 口方向 | 1 输入,0 输出 |
| D0 | C 口低 4 位 | 1 输入,0 输出 |
例如要求 A 口方式 0 输出、B 口方式 0 输入、C 口高 4 位输出、低 4 位输入,拼出来是 D7=1、D6D5=00、D4=0、D3=0、D2=0、D1=1、D0=0,即 10000010B 等于 82H。
; ppi8255.asm 按上面的要求初始化 8255 PPI_BASE equ 060h ; A口 060H, B口 061H, C口 062H, 控制口 063H mov dx, PPI_BASE+3 mov al, 10000010b ; 82H out dx, al ; 写控制字 mov dx, PPI_BASE mov al, 0ffh out dx, al ; A 口全部输出 1参数说明:控制口地址是基址加 3,A、B、C 三口依次加 0、1、2。写控制字必须在写数据之前,顺序反了输出会保持上次的配置。C 口按位置位用out 控制口, 位号*2+1置 1,位号*2复位,位号从 0 到 7 对应 PC0 到 PC7。
4.2 8253 计数初值与输出频率的计算
8253 的计数初值等于输入时钟频率除以目标输出频率,取值范围 1 到 65536,写 0000H 表示 65536。二进制计数时写 16 位,BCD 计数时写 4 位十进制数,最大 9999。
工作方式共 6 种,习题里最常考的是方式 2 和方式 3:
| 方式 | 名称 | 输出特征 |
|---|---|---|
| 0 | 计数结束中断 | 计到 0 时输出变高并保持 |
| 1 | 可编程单稳 | 触发后输出一个负脉冲 |
| 2 | 频率发生器 | 周期性输出,自动重装 |
| 3 | 方波发生器 | 周期为计数值的对称方波 |
| 4 | 软件触发选通 | 计到 0 输出一个负脉冲 |
| 5 | 硬件触发选通 | 由外部触发启动 |
例:输入时钟 2MHz,要得到 1kHz 方波,选方式 3,初值为 2000000 除以 1000 等于 2000。
; timer8253.asm 计数器0 工作在方式3, 初值 2000 TMR_BASE equ 040h ; 计数器0 040H, 1 041H, 2 042H, 控制口 043H mov dx, TMR_BASE+3 mov al, 00110110b ; 00 计数器0, 11 先低后高, 011 方式3, 0 二进制 => 36H out dx, al mov dx, TMR_BASE mov ax, 2000 out dx, al ; 先写低字节 mov al, ah out dx, al ; 再写高字节参数说明:控制字高两位选计数器,中间两位选读写格式(01 只读低 8 位,10 只读高 8 位,11 先低后高),再三位选工作方式,最低位选计数制式。读当前计数值要用锁存命令,把控制字 D7D6 置 11、D5 置 0,再按同样顺序读,否则读到的两个字节可能不是同一时刻的值。
4.3 8259 中断向量表填写与 ISR 收尾
中断类型号 N 对应的向量表位置是内存 0000H 段的 N 乘以 4 处,连续 4 个字节依次存放偏移低 8 位、偏移高 8 位、段低 8 位、段高 8 位。类型号 8 对应 0000H:0020H,这一点很多题会直接问。
; pic8259.asm 安装类型号 8 的中断服务程序 cli xor ax, ax mov es, ax mov bx, 08h*4 ; 类型号 8 的向量在 0000H:0020H mov word ptr es:[bx], offset myisr mov ax, cs mov word ptr es:[bx+2], ax sti ; ... 主程序 myisr proc far push ax push dx ; 保护现场 ; 具体中断处理 mov dx, 020h mov al, 20h out dx, al ; 发非特殊 EOI pop dx pop ax iret myisr endp要点说明:中断服务程序必须是 far 过程,末尾用iret而不是ret,用到的寄存器要自己保护,编译器不会代劳。EOI 必须在处理结束时发出,否则同级中断不会再次被响应,表现为“只中断一次就再也没反应”。cli和sti包住向量表改写过程,避免改到一半发生中断。
4.4 没有实验箱时的替代验证
| 验证手段 | 能验证什么 | 局限 |
|---|---|---|
| EMU8086 虚拟外设 | 指令执行、寄存器与标志位 | 外设模型较简化 |
| Proteus 挂芯片模型 | 8255、8253、8259 的时序与波形 | 建模型耗时 |
| 纯手算加对照表 | 控制字、初值、向量地址 | 验证不了时序 |
| 真实实验箱 | 全部 | 依赖硬件可用性 |
选哪种取决于题目问什么。问控制字和初值的,手算加表格最快;问“输出波形什么样”的,得靠仿真或实验箱。用仿真器跑之前先确认端口地址与题目一致,很多人栽在仿真里用了默认地址、和题面给的不一样。
5. 单步快照与错题回归集:把答案校到指令级
答案给的是一个结果,而习题考的是过程。把结果拆成一条条指令的快照,才是真正把答案用起来的方式。
5.1 用 T 命令逐条比对寄存器与标志
在 DEBUG 里用T单步,Turbo Debugger 里按 F7,每执行一条指令就检查全部寄存器:AX、BX、CX、DX、SI、DI、BP、SP、DS、ES、SS、CS、IP,以及 FLAGS 里的 CF、ZF、SF、OF。做法是先把答案预期结果写成一行注释贴在代码旁边,单步到对应位置时逐项比对,第一个对不上的点就是错误源头。
最容易被跳过的是 SP。涉及 push、call、中断的题目,SP 变化不对往往是栈段没设或清栈语句漏写,这类错误不会体现在最终数据上,但会在子程序返回时炸掉。
5.2 把易错题固化成可回归的批处理集
把每道做错的题单独存成一个 asm 文件,放进同一个目录,配一个批处理一次跑完:
rem runall.bat 批量编译运行并比对输出 @echo off setlocal enabledelayedexpansion for %%f in (t*.asm) do ( masm %%f; link %%~nf; %%~nf.exe > %%~nf.out fc %%~nf.out %%~nf.exp > nul if errorlevel 1 (echo FAIL: %%f) else (echo PASS: %%f) )逻辑说明:%%~nf取不带扩展名的文件名,每个题目配一个同名.exp文件存放期望输出,fc做文本比对,返回非零说明结果与期望不一致。改一处公共代码后跑一遍,就能看出哪些题被改坏了。这个习惯的价值在期末复习阶段特别明显,比重新翻一遍答案快得多。
5.3 三类题各自的验证优先级
| 题型 | 首选验证 | 次选 | 不建议 |
|---|---|---|---|
| 地址与标志位计算 | 写小 exe 打印 FLAGS 快照 | DEBUG 单步 | 直接背答案数字 |
| 指令序列结果 | Turbo Debugger 单步 | EMU8086 仿真 | 只看最终结果 |
| 接口芯片初始化 | 对照控制字逐位拼装 | Proteus 看波形 | 抄同学的控制字 |
提示:把每道错题的最小复现 exe 和期望输出留在同一个目录里,就是一套自己的回归测试集。控制字改一位、初值换一个,跑一遍 fc 就能知道影响面,这比在纸上重新推一遍可靠。
真正的分水岭不在做了多少题,而在于每道题都留下了能重跑的 exe 和比对脚本,答案文件只是最后用来确认的那一步。
本文还有配套的精品资源,点击获取