安装环境
- 本书源代码 http://asmirvine.com/gettingStartedVS2017/Irvine.zip
- https://blog.csdn.net/caipengbenren/article/details/88148018
brew install --cask dosbox
mkdir -p ~/dos/masm
# 把当前文件下的文件夹masm的所有文件复制到~/dos/masm中,这样就成功安装masm
# 打开dosbox
mount c ~/dos/masm # 挂载本地磁盘
# hello.asm 已在masm中
masm hello.asm # 一直回车到ok
link HELLO.OBJ # 一直回车到ok
hello.exe # 运行输出
- nasm
brew reinstall nasm
nasm -f macho64 -o hello.o hello64.asm # 64
ld -o hello -e _main hello.o -static
第一章 基础知识
- 汇编指令是机器指令的助记符,同机器指令一一对应
- 每一种CPU都有自己的汇编指令集
- CPU可以直接使用的信息在存储器中存放
- 在存储器中指令和数据没有区别,都是二进制信息
- 存储单元:存储器被分为若干个存储单元,每个存储单元从0开始顺序编号。一个存储单元存储一个字节Byte(8bit)
- 每个CPU芯片都有许多管脚,这些管脚和总线相连。也可以说,这些管脚引出总线。一个CPU可以引出3种总线的宽度标志着这个CPU不同方面的性能:
- 地址总线的宽度决定了CPU的寻址能力。宽度为N,寻址2^N个存储单元。
- 数据总线的宽度决定了CPU与其他器件进行数据传送时的一次数据传送量。宽度为N,传输N个bit
- 控制总线的宽度决定了CPU对系统中其他器件的控制能力。
第二章 寄存器
-
在CPU中
- 运算器进行信息处理
- 寄存器进行信息存储
- 控制器控制各种器件进行工作
- 内部总线连接各种器件,在它们之间进行数据的传送
-
通用寄存器:存放一般性的数据
- 16位寄存器 AX BX CX DX
- 每一个寄存器可分为两个8位寄存器 AH AL
-
8086CPU 字节:一个字节8个bit。字:记为word,一个字由两个字节组成,就是当前CPU的一次寻址位数。
-
十六进制后缀H,二进制后缀B,十进制什么都不加
-
AX 00C5H ;add al,93H 结果 AX 是 0058H 而不是 0158H。此时AL是作为一个独立的8位寄存器来使用的,和AH没有关系。
mov AX,00C5H
add AL,93H // 0058H 此时AL是作为一个独立的8位寄存器来使用的,和AH没有关系。
mov AX,00C5H
add AX,93H // 0158H
-
在进行数据传送或运算时,要注意指令的两个操作对象的位数应当一致。
-
CPU访问内存单元时,必须向内存提供内存单元的物理地址。8086CPU在内部用段地址和偏移地址移位相加的方法形成最终的物理地址。
- 物理地址 = 段地址 * 16 + 偏移地址
-
8086CPU段寄存器 CS DS SS ES
-
CS和IP是8086CPU最关键的两个寄存器,它们指示了CPU当前要读取指令的地址。CS是代码段寄存器,IP是指令指针寄存器。
-
在8086CPU中,任意时刻,设CS中的内容为M,IP中的内容为N,8086CPU将从内存 M*16+N 单元开始,读取一条指令并执行。
-
8086CPU工作过程简要描述如下:
- (1) 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓存器
- (2) IP=IP+所读取指令的长度,从而指向下一条指令
- (3) 执行指令。转到步骤(1),重复此过程。
-
CPU将CS:IP指向的内存单元中的内容看做指令,因为在任何时候,CPU将CS、IP中的内容当做指令的段地址和偏移地址,用它们合成指令的物理地址,到内存中读取指令码,执行。
-
程序员可以通过修改CS、IP中的内容来控制CPU执行目标指令。mov指令被称为传送指令,不能修改CS、IP。
能够改变CS、IP的内容的指令统称为转移指令
。jmp 段地址:偏移地址
。jmp 2AE3:3 执行后:CS=2AE3,IP=0003H,CPU将从2AE33H处读取指令jmp 某一合法寄存器:用寄存器中的值修改IP
。如
jmp ax # 执行前 ax=1000H CS=2000H IP=0003H # 执行后 ax=1000H CS=2000H IP=1000H 指向地址 21000H
debug使用
- R 查看、改变CPU寄存器内容。修改寄存器值:r ax 回车
- D 查看内存内容。d 1000:9 列出从1000:9处开始的128个字节。也可以使用D 段地址:起始偏移地址 结尾偏移地址
- E 改写内存中的内容。
e 1000:0 0 1 2 3 4 5 6 7 8 9 10
e 1000:0 1 'a' 2 'b' 3 'c'
d 1000:0 # 读取
e 1000:0 1 "a+b" 2 "c++" 3 "IBM" # 写入 a+b c++ IBM
- U 将内存中的机器指令翻译成汇编指令
e 1000:0 1 "a+b" 2 "c++" 3 "IBM"
# b80100 mov ax,0001
# b90200 mov cx,0002
# 01c8 add ax,cx
e 1000:0 b8 01 00 b9 02 00 01 c8
u 1000:0
t # 执行
- T 执行一条机器指令
- A 以汇编指令的格式在内存中写入一条机器指令
a 1000:0 mov ax,12e3
- P 执行指令,遇到loop循环可以一步执行完
- Q 退出debug模式
- G “G 0012”是指直接执行到0012这条指令之后
第三章 寄存器(内存访问)
内存中的字存储
- 在CPU中,用16位寄存器来存储一个字,高8位存放高位字节,低8位存放低位字节
- 字单元:即存放一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成。
- 我们将起始地址为N的字单元简称为N地址字单元
- 大端字节序:低位字节在高地址,高位字节低地址上。这是人类读写数值的方法。
- 小端字节序:与上面相反。低位字节在低地址,高位字节在高地址。
DS和[address]
-
DS:用来存放要访问的数据的段地址
-
mov 指令
- 将数据直接送入寄存器。mov ax,1000
- 将一个寄存器中的内容送入另一个寄存器。 mov ax,bx
- 将一个内存单元中的内容送入一个寄存器中。 mov al,[200] ,将DS*16+200的内容存入al中
# dosbox e 1000:200 "junmo" # 输入代码 a 3000:0 mov bx,1000 mov ds,bx mov al,[200] mov cx,[202] # 修改指令读取地址 rcs 3000 rip 0
- 将寄存器内容存入内存单元。mov [200],al,将al内容存入 DS:200
-
[…] 表示一个内存单元,[NUM]中的NUM表示内存单元的偏移地址。8086CPU不支持直接将数据传入段寄存器的操作,需要先存入通用寄存器再从通用寄存器传送到段寄存器。
-
8086CPU栈机制:以字为单位进行的。
- PUSH ax 将寄存器ax中的数据送入栈中
- POP ax 从栈顶取出数据送入ax
- 栈顶段地址存放在SS中,偏移地址存放在SP中。任意时刻,SS:SP指向栈顶元素
- 入栈时,栈顶从高地址向低地址方向增长
- 8086CPU不保证我们对栈的操作不会越界,它只知道栈顶在何处(SS:SP),而不知道我们安排的栈空间大小。
push 内存单元 pop 内存单元
PUSH、POP实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与MOV指令不同的是,PUSH和POP指令访问内存单元的地址不是在指令中给出的,而是由SS:SP指出的。同时,PUSH和POP指令还要改变SP中的内容
CPU执行MOV只需要一步操作,就是传送,而执行PUSH、POP却需要两步操作。执行PUSH时,先改变SP=SP-2,后向SS:SP处传送。执行POP时,先读取SS:SP处的数据,后改变SP=SP+2。
PUSH、POP等栈操作指令,修改的只是SP,也就是说,栈顶的变化范围最大为0-FFFFH。
-
对于数据段,将它的段地址放在DS中,用mov,add,sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当做数据来访问。
-
对于代码段,将它的段地址放在CS中,将段的第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令。
-
对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中,这样CPU在需要进行栈操作的时候,比如执行push、pop指令等,就将我们定义的栈段当做栈空间来用。
第四章 第一个程序
ASSUME cs:abc ; assume 假设程序abc和寄存器cs相关联
abc SEGMENT
mov ax,2
add ax,ax
add ax,ax
mov ax,4c00H ; 程序返回
int 21H
abc ENDS
END
- 汇编指令是对应机器码的指令,可以被编译为机器指令,最终为CPU运行。
- 伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关编译工作
- 标号:一个标号指代了一个地址。如在SEGMENT前面的abc,作为一个段的名称,这个段的名称最终将被编译、连接程序处理为一个段的段地址。
- masm编译:目标文件(.obj),列表文件(.lst),交叉引用文件(.crf)
- link链接的作用
- 当源文件很大时,可以将它分为多个源文件来编译,每个源文件编译成为目标文件后,再用链接程序将它们连接到一起,生成一个可执行文件
- 程序调用了某个库文件的一个子程序,需要将这个库文件和该程序生成的目标文件连接到一起,生成一个可执行文件
- 一个源文件编译后,得到了存在机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件。连接程序将这些内容处理为最终可执行信息。所以,在只有一个源程序文件,而又不需要调用某个库中的子程序的情况下,也必须用连接程序对目标文件进行处理,生成可执行文件。
- 简洁编译和连接:“masm abc.asm;”,“link abc.obj;”。注意结尾的分号
- 在command中执行abc.exe的过程
- 在DOS中直接执行abc.exe时,是正在运行的command将abc.exe的程序加载到内存
- command设置CPU的 CS:IP 指向程序的第一条指令(即程序的入口),从而使程序得以运行
- 程序运行结束后,返回到command,CPU 继续运行command。
- 使用debug执行abc.exe 。
- 使用P执行 int 21H
- 使用Q退出Debug
第五章 [BX]和loop指令
- 要完整的描述一个内存单元,需要两种信息:内存单元的地址;内存单元的长度(类型)。
- 约定:采用"( )"来表示一个寄存器或内存单元中的内容。(ax)表示ax中的内容;(1000H)表示内存单元 1000H 的内容
- 约定:符号idata表示常量
- mov ax,[bx] ;(ax)=((ds)*16+(bx))
- mov ax,[0] ; 在debug里,是要把ds:0内存的内容转移到ax,但是在汇编源程序里,则是把0转移到ax,可以采用 mov ax,ds:[0]
- loop指令:(cx)=(cx)-1 ;判断cx中的值,不为零则跳回标号处执行程序,如果为0则向下执行
mov cx,3
L1: ; 循环执行(cx)次
mov ax,1000h
add ax,ax
loop L1
- 在汇编源程序中,数据不能以字母开头,所以需要在前面加0。比如,9138h 在汇编源程序中可以写成“9138h”,而A000h在汇编源程序中需要写成“0A000H”
- debug g 0012是指直接执行到0012这条指令之后。采用p可以直接执行完循环
第六章 包含多个段的程序
- end除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。我们若要CPU从何处开始执行程序,只要在源程序中用“end 标号”指明就可以了。
ASSUME cs:codemsg
codemsg SEGMENT
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h ; 数据
start: mov bx,0 ; 程序入口
mov ax,0
mov cx,8
s: add ax,cs:[bx]
add bx,2
loop s
mov ax,4c00H
int 21H
codemsg ENDS
END start
- 程序手动设置栈
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; 用dw定义16个字符数据,在程序加载后,取得16个字的
; 内存空间,存放这16个数据。在后面的程序中将这段空间当做栈来使用
start: mov ax,cs ; 程序入口
mov ss,ax
mov sp,30h ;将栈顶设定为 ss:sp,cs:30h
-
将数据、代码、栈放入不同的段。
- ASSUME cs:code,ds:data,ss:stack,并不是指定了cs,ds,ss的地址,ASSUME是伪指令,由编译器执行,也是仅在源程序中存在的信息,CPU并不知道他们。assure的作用是定义具有一定用途的段和寄存器联系起来。
- 若要CPU按照我们的安排行事,就要用机器指令控制它,源程序中的汇编指令是CPU要执行的内容。源程序中的最后“end start”说明了程序的入口,这个入口将被写入可执行文件的描述信息,可执行文件中的程序被加载入内存后,CPU的 CS:IP 被设置指向这个入口。
- 总之,CPU到底如何处理我们定义的段中的内容,是当做指令执行,当做数据访问,还是当做栈空间,完全靠程序中具体的汇编指令,和汇编指令对 CS:IP、SS:SP、DS等寄存器的设置来决定的。
ASSUME cs:code,ds:data,ss:stack data SEGMENT dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h data ENDS stack SEGMENT dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 stack ENDS code SEGMENT start: mov ax,stack mov ss,ax mov sp,20h ; 设置栈顶 ss:sp 指向 stack:20h mov ax,data mov ds,ax ; ds 指向数据段 mov bx,0 ; ds:bx 指向data段中的第一个单元 mov cx,8 s: push [bx] add bx,2 loop s mov bx,0 mov cx,8 s0: pop [bx] add bx,2 loop s0 mov ax,4c00H int 21H code ENDS END start
第七章 更灵活的定位内存地址的方法
- [bx+idata] 也可以写为 idata[bx] 都表示((ds)*16+(bx)+idata)
- si和di是8086CPU中和bx功能相近的寄存器,si和di不能够分成两个8位寄存器
- [bx+si] 也可写为 [bx][si]
- [bx][si][idata] 也可写为 idata[bx][si] 或 [bx][si].idata 或 bx.idata[si]
- 一般而言,需要暂存数据的时候,我们都应该使用栈。比如嵌套循环的cx寄存器的值,可以先在内存循环开始处push,重新复制cx,内存循环执行完成,再pop出来
mov cx,100 L1: push cx ; 入栈 mov cx,20 L2: inc ax loop L2 add ax,ax pop cx ; 出栈 loop L1
第八章 数据处理的两个基本问题
- 指令执行前,所要处理的数据可以在3个地方:CPU内部、内存、端口。
- 8086CPU中,只有bx si di 和bp可以用[…]来进行内存单元寻址。组合为 bx+[si|di];bp+[si|di]。
在[...]中使用bp,如果没有指定段地址,默认在ss中。bx的段地址默认在ds中。
- 指令处理的数据长度
- 通过寄存器来指明要处理的数据尺寸
mov ax,1 ; 长度为字 mov al,1 ; 长度为字节
- 在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编指令中可以为word或byte
mov word ptr ds:[0],1 ; 长度为字 mov byte ptr ds:[0],1 ; 长度为字节
- 其他方法:有些指令默认了访问的是字单元还是字节单元。如push [1000H],访问的就是字单元,因为push只进行字操作
- div 除法指令
; div reg ; div 内存单元 div byte ptr ds:[0] ; 商为 (al) = (ax) / ((ds)*16+0)的商 ; 余数为 (ah) = (ax) / ((ds)*16+0)的余数 div word ptr ds:[0] ; 商为 (ax) = ((dx)*10000H+(ax)) / ((ds)*16+0)的商 ; 余数为 (dx) = ((dx)*10000H+(ax)) / ((ds)*16+0)的余数
- 伪指令dd用来定义dword(double word,双字)型数据的。
- dup是一个操作符,它是和db、dw、dd等数据定义伪指令配合使用的,用来进行数据的重复。
db 3 dup(0) ; 定义三个字节,值都为0 db 3 dup(0,1,2) ; 定义了9个字节,它们是0、1、2、0、1、2、0、1、2。 db 3 dup('abc','ABC') ; 定义了18个字节,相当于 db 'abcABCabcABCabcABC'
第九章 转移指令的原理
- 可以修改IP,或同时修改CS和IP的指令统称为转移指令。
- 只修改IP时,称为段内转移,比如 jmp ax
- 同时修改CS和IP时,称为段间偏移,比如 jmp 1000:0
- 操作符offset,由汇编器处理,功能是取得标号的偏移地址。
- jmp short 标号(转到标号处执行指令);这种格式实现的是段内短偏移,它对IP的修改范围为 -128~127,也就是说,它向前转移最多越过128个字节,向后转移最多越过127个字节。
"jmp short 标号"
指令所对应的机器码中,并不包含转移的目的地址,而包含的是转移的位移
。这个位移等于标号处的地址减去jmp指令后的第一个字节的地址。- "jmp far ptr 标号"实现的是段间转移,又称远转移。
0BBD:0006 EA0B01BD08 JMP 0BBD:0108
; "0B 01 BD 08"是目的地址在指令中的存储顺序,高地址"BD OB"是转移的段地址:OBBDH,低地址的"0B 01"是偏移地址:010BH
- jmp word ptr 内存单元地址(段内转移) :从内存单元地址处开始存放着一个字,是转移的目的偏移地址
- jcxz指令为有条件转移指令,所有的条件转移指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。
- “jcxz 标号” 相当于 if((cx)==0) jmp short 标号
- loop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。
- loop 标号的功能相当于 (cx)–;if((cx)!=0)jmp short 标号
第十章 CALL和RET指令
- call和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP。它们经常被共同用来实现子程序的设计。
- ret指令用栈中的数据,修改IP的内容,从而实现近转移。相当于pop IP
- retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。相当于 pop IP;pop CS
- CPU执行call指令时,进行两步操作:
- 将当前的IP或CS和IP压入栈中
- 转移
- CALL 标号:相当于 push IP; jmp near ptr 标号 【near 偏移量为-32768~32767】
- CALL far ptr 标号:相当于 push CS; push IP;jmp far ptr 标号
- MUL指令:mul reg/内存单元
- 两个相乘的数:要么都是8位,要么都是16位。如果是8位,一个默认放在AL里,另一个放在8位的reg或内存字节单元中;如果是16位,一个默认在AX中,另一个放在16位reg或内存单元中。
- 结果:如果是8位乘法,结果默认放在AX中;如果是16位乘法,结果高位默认在DX中存放,低位在AX中放。
- 程序之间数据传递:
- 1.将数据存储在内存中,把它们所在的内存空间的首地址放在寄存器中传递
- 2.用栈传递参数
- 存储器冲突:可以采用栈解决。嵌套循环是的cx寄存器
第十一章 标志寄存器
-
ZF标志:零标志位,记录相关指令执行后,其结果是否为0,结果为0,zf=1
-
PF标志:奇偶标志位,记录相关指令执行后所有bit位中1的个数,为偶数,pf=1
-
SF标志:符号标志位,记录相关指令执行后,其结果为负,sf=1
-
CF标志:进位标志位。一般情况下,在进行
无符号
数运算时,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。 -
OF标志:溢出标志位。有符号运算时,超过计算的位数范围时,of=1
-
adc:带进位加法指令
-
sbb:带借位减法指令
-
cmp:比较指令。相当于减法,只是不保存结果,但对标志寄存器会产生影响。
-
检测比较结果的条件转移指令。e equal b below a above
- je 等于则转移 zf=1
- jne 不等于则转移 zf=0
- jb 低于则转移 cf=1
- jnb 不低于则转移 cf=0
- ja 高于则转移 cf=0且zf=0
- jna 不高于则转移 cf=1或zf=1
-
DF标志和串传送指令
- cld 设置df=0;std 设置df=1
- df=0,每次操作si,di递增。df=1,每次操作si,di递减
- movsb指令:将ds:si指向的内存单元中的字节送入es:di中,然后根据df位的值,将si和di递增1或递减1
- movsw指令:将ds:si指向的内存单元中的字送入es:di中,然后根据df位的值,将si和di递增2或递减2
- rep的作用就是根据cx的值,重复执行后面的串传送指令。
rep movsb ;等价 s:movsb loop s
- 将’Welcome to masm!'传送入紧邻的内存单元中。
ASSUME cs:code data segment db 'Welcome to masm!' db 16 dup(0) data ends code SEGMENT start: mov ax,data mov ds,ax mov si,0 ; 设置ds:si 指向data:0 mov es,ax mov di,10h ; 设置es:di 指向data:10h mov cx,16 ; 循环16次 cld ; 设置df=0,开启正向传递 rep movsb mov ax,4c00h int 21h code ENDS
-
pushf的作用是将标志寄存器的值压入栈中,popf是从栈中弹出数据,送入标志寄存器。
第十二章 内中断
-
任何一个通用的CPU,比如8086,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从CPU外部发送过来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理。这种特殊的信息,称为中断信息。中断的意思是指,CPU不再接着(刚执行完的指令)向下执行,而是转去处理这个特殊信息。
-
用来处理中断信息的程序称为中断处理程序。
-
CPU用8位的中断码通过中断向量表找到相应的中断处理程序的入口地址。
- 中断向量:就是中断处理程序的入口地址
- 中断向量表:中断处理程序入口地址的列表
- 对于8086CPU,内存0000:0000到0000:03FF这1024个单元中存放这中断向量表,每个表项4个字节。一个表项高地址存放段地址,低地址存放偏移地址。
-
中断过程:用中断类型码找到中断向量,并用它设置CS和IP,这个工作是由硬件自动完成的
-
8086收到中断信息后的中断过程
- 从中断信息中取得中断类型码N
- 标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值);PUSHF
- 设置标志寄存器的第8位TF和第9位IF的值为0
- CS的内容入栈;PUSH CS
- IP的内容入栈;PUSH IP
- 从内存地址为 中断类型码 * 4 和 中断类型码 * 4 + 2 的两个字单元中取出中断处理程序的入口地址设置IP和CS
-
中断处理程序的编写方法
- 保存用到的寄存器
- 处理中断
- 恢复用到的寄存器
- 用iret指令返回。相当于 pop IP;pop CS;popf;
-
单步中断:基本上,CPU在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。单步中断的中断类型码为1,则它所引发的中断过程如下
- 取得中断类型码1
- 标志寄存器入栈,TF、IF设置为0
- CS、IP入栈
- (IP)=(1 * 4),(CS)=(1 * 4+2)
-
对于ss寄存器传送命令之后,即便发生中断,CPU也不会响应。这样做的主要原因是,ss:sp 联合指向栈顶,而对他们的设置应该连续完成。如果在执行完设置ss的指令之后,CPU响应中断,引发中断过程,要在栈中压入标志寄存器、CS和IP的值。而ss改变,sp并未改变,ss:sp指向的不是正确的栈顶,将引发错误。
-
Debug利用单步中断来实现T命令的功能,也就是说,用T命令执行一条指令后,CPU响应单步中断,执行Debug设置好的处理程序,才能在屏幕上显示寄存器的状态,并等待命令的输入。而在mov ss,ax指令执行之后,CPU根本不响应任何中断,其中也包括单步中断,所以Debug设置号用来显示寄存器状态和等待输入命令的中断处理程序根本没有得到执行,所以我们看不到预期的结果。
第十三章 int指令
- int指令的格式为:int n,n为中断类型码,它的功能是引发中断过程。
- BIOS(基本输入输出系统),主要功能
- 硬件系统的检测和初始化程序
- 外部中断和内部中断的中断例程
- 用于对硬件设备进行I/O操作的中断例程;
- 其他和硬件系统相关的中断例程
- BIOS和DOS中断例程的安装过程
- 开机后,CPU一加电,初始化(CS)=0FFFFH,(IP)=0,自动从FFFF:0单元开始执行程序。FFFF:0处有一条跳转指令,CPU执行该指令后,转去执行BIOS中的硬件系统检测和初始化程序
- 初始化程序将建立BIOS所支持的中断向量,即将BIOS提供的中断例程的入口地址登记在中断向量表中,注意,对于BIOS所提供的中断例程,只需将入口登记在中断向量表中即可,因为它们是固化到ROM中的程序,一直在内存中存在。
- 硬件系统检测和初始化完成后,调用int 19h进行操作系统的引导。从此将计算机交给操作系统控制
- DOS启动后,除完成其他工作外,还将它所提供的中断例程装入内存,并建立相应的中断向量。
第十四章 端口
-
在PC机系统中,和CPU通过总线相连的芯片除各种存储器外,还有以下3种芯片
- 各种接口卡(比如,网卡、显卡)上的接口芯片,它们控制接口卡进行工作
- 主板上的接口芯片,CPU通过它们对部分外设进行访问
- 其他芯片,用来存储相关的系统信息,或进行相关的输入输出处理
-
在这些芯片中,都有一组可以由CPU读写的寄存器。这些寄存器,它们在物理上可能处于不同的芯片中,但是它们由以下两点相同:
- 都和CPU的总线相连,当然这种连接时通过它们所在的芯片进行的
- CPU对它们进行读或写的时候都是通过控制总线向它们所在的芯片发出端口读写命令
-
可见。从CPU的角度,将这些寄存器都当做端口,对它们进行统一编址,从而建立了一个统一的端口地址空间。每一个端口在地址空间中都有一个地址。
-
在访问端口时,CPU通过端口地址来定位端口。因为端口所在的芯片和CPU通过总线相连,所以,端口地址和内存地址一样,通过地址总线来传送。在PC系统中,CPU最多可定位64K的不同范围,则端口地址的范围为0-65535
-
端口的读写指令只有两条:in从端口读取数据和out往端口写入数据
-
读取内存时总线相关操作:mov ax,ds:[8]
- CPU通过地址线将地址信息8发出
- CPU通过控制线发出内存读命令,选中存储器芯片,并通知它,将要从中读取数据。
- 存储器将8号单元中的数据通过数据总线送入CPU
-
访问端口时总线相关操作:in al,60h ;从60h 号端口读入一个字节
- CPU通过地址线将地址信息60h发出
- CPU通过控制线发出端口读命令,选中端口所在芯片,并通知它,将要从中读取数据
- 端口所在的芯片将60h端口中的数据通过数据线送入CPU
-
在in和out指令中,只能使用ax或al来存放端口中读取或或写入到端口的数据
-
CMOS RAM芯片:两个端口,地址端口为70H,存放要访问的CMOS RAM单元的地址。数据端口为71H,存放从选定的CMOS RAM单元中读取的数据或要写入到其中的数据。
-
从CMOS RAM的2号单元读取数据
- 将2送入端口70h
- 将端口71h读出2号单元的内容
mov al,2 out 70h,al in al,71h
-
逻辑左移指令 shl
- 将一个寄存器或内存单元中的数据向左移位
- 将最后移出的一位写入CF中
- 最低位用0补全
-
逻辑右移指令 shr
- 将一个寄存器或内存单元中的数据向右移位
- 将最后移出的一位写入CF中
- 最高位用0补全
第十五章 外中断
-
接口芯片与端口:外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的端口中;CPU向外设的输出也不是直接送入外设,而是先送入端口中,再由相关的芯片送入外设。CPU还可以向外设输出控制命令,而这些控制命令也是先送到相关芯片的端口中,再由相关的芯片根据命令对外设实施控制。
-
外设的输入到达,相关芯片将向CPU发出相关的中断信息。CPU在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入。
-
可屏蔽中断:CPU可以不响应的外中断。CPU是否响应可屏蔽中断,要看标志寄存器的IF位的设置。当CPU检测到可屏蔽中断信息时,如果IF=1,则CPU在执行完当前指令后响应中断,引发中断过程。如果IF=0,则不响应可屏蔽中断。
-
外中断来自于CPU外部,中断类型码是通过数据总线送入CPU的;而内中断的中断类型码是在CPU内部产生的。
-
不可屏蔽中断时CPU必须响应的外中断。当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程。
-
对于8086CPU,不可屏蔽中断类型码固定为2,所以中断过程中,不需要取中断类型码。
-
PC机键盘处理过程
- 键盘输入:键盘上的每一个键相当于一个开关,键盘有一个芯片对键盘的每一个键的开关状态进行扫描。
- 按下一个键,开关接通,产生一个扫描码,扫描码说明了按下的键在键盘的位置。扫描码被送入主板上的相关芯片的寄存器中,该寄存器的端口地址为60h。in al,60h
- 松开按下的键,也产生一个扫描码。松开的按键扫描码也被送入60h
- 按键按下时产生的扫描码叫通码,松开时叫断码。通码的第7位为0,断码的第7位为1。断码 = 通码 + 80H
- 引发9号中断:键盘的扫描码到达 60H 端口时,相关的芯片就会向CPU发出中断类型码为9的可屏蔽中断。CPU检测到该中断信息后,如果IF=1,则响应中断,引发中断过程,转去执行 int 9 中断例程。
- 执行 int 9 号中断例程。BIOS提供了 int 9 中断例程,用来进行基本的键盘输入处理。
- 读出60H 端口中的扫描码
- 如果是字符键的扫描码,则将该扫描码和它所对应的字符码(即ASCII码)送入内存中的BIOS键盘缓冲区;如果是控制键(如CTRL)和切换键(如CapsLock)的扫描码,则将其转变为状态字节(用二进制位记录控制键和切换键状态的字节)写入内存中存储状态字节的单元。0040:17单元存储键盘状态字节。
- 对键盘系统进行相关的控制,比如,向相关的芯片发出应答信息。
- 键盘输入:键盘上的每一个键相当于一个开关,键盘有一个芯片对键盘的每一个键的开关状态进行扫描。
-
CPU对外设输入的处理方法:
- 外设输入送入端口
- 向CPU发出外中断(可屏蔽中断)信息
- CPU检测到可屏蔽中断信息,如果IF=1,CPU在执行当前指令后响应中断,执行相应的中断例程
- 可在中断例程中实现对外设输入的处理。
-
端口和中断机制,是CPU进行I/O的基础
-
使用int 16H 中断例程读取键盘缓冲区
- 检测键盘缓冲区中是否有数据
- 没有则继续做第一步
- 读取缓冲区第一个字单元中的键盘输入
- 将读取的扫描码送入ah,ASCII码送入al
- 将已读取的键盘输入从缓冲区中删除
参考书
《汇编语言》王爽