开始之前
好吧,不太清楚究竟是怎么回事,但是貌似我们学校的《微机原理》改名成了《汇编与接口》。汇编这玩意儿还是有点意思的,就是写起来对人来说太不友好了( 不过除了非常底层的优化外,貌似手撸汇编在现在的意义没那么大(?
实验环境搭建
环境搭建也算是老生常谈的话题了。。基本上每学一门新语言都要经历这个步骤。
注意这次我们所需的是 32 位的 masm
环境,所以我们需要 visual studio
来提供 msvc
工具链。
由于部分用于代码高亮和自动补全的的插件仍然没有适配 vs2022
,所以我们需要安装 vs2019
。
这是下载链接 ,注意,你得注册一个微软账户才能在上面下载,简单来说就是去注册一个 outlook 邮箱。
如图所示,在安装时勾选"使用c++的桌面开发"即可。
在安装后打开 vs
,进入项目后点击顶栏的拓展-管理拓展,然后搜索 asmdude
并点击安装,然后关闭并重新进入 vs
即可启用拓展 。
接下来新建一个空项目然后按照下图操作。
...并点击确定。 右键右侧的源文件并新建一个 main.asm
,当然命名是无所谓的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 .386 .model flat, coption casemap :none includelib libcmt. lib includelib legacy_stdio_definitions. libEXTERN printf :PROC .data HelloWorld db "Hello World!:-)" , 0 .code main PROC push offset HelloWorld call printf add esp , 4 ret main ENDP END
注意我们现在是在编写32位的汇编,所以请确保在 x86
的 Debug
模式下编译和调试,然后点击顶栏的绿色三角。
到此就完成了环境的搭建。
零碎小玩意儿
基本上这节就是些小知识点,因为编排问题没有加进正文里,所以统一放这儿了。
IA-32中的数据单位
1
比特 (Bit)
4
半字节 (Nibble)
8
字节 (Byte)
16
字 (Word)
32
双字 (Double Word)
64
四字 (Quad Word)
stdcall 调用约定
stdcall
是一种常见的调用约定,其中函数的参数通过栈传递,函数的参数是从右到左依次推送到栈上,而栈上的每个参数占据4字节的空间。
琐碎的指令们
跳转指令
jmp
unconditional jump
je
jump if equal
jne
jump if not equal
jl
jump if less
jg
jump if greater
jle
jump if less or equal
jge
jump if greater or equal
jo
jump if overflow
jno
jump if not overflow
js
jump if sign (比较结果为负数)
jns
jump if not sign
jc
jump if carry (产生进位)
jnc
jump if not carry
值得注意的是,有符号数使用 jg
和 jl
,而无符号数使用 ja
和 jb
除法指令 div
这一节为语言模型生成内容,参考 promt
如下 1 请介绍 IA-32 masm 中的 div 指令并举一个用其判断奇偶数的例子
div
指令是IA-32汇编语言中的除法指令,用于将一个32位的无符号整数除以一个操作数,并将商存储在EDX:EAX寄存器对中的EAX寄存器中,余数存储在EDX寄存器中。
div
指令的基本语法如下:
其中,operand
表示除数。
为了判断一个数是奇数还是偶数,可以使用div
指令来计算除以2的余数。如果余数为0,则说明被除数是偶数;如果余数为1,则说明被除数是奇数。
下面是一个使用div
指令判断奇偶数的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 mov eax , 13 mov edx , 0 mov ecx , 2 div ecx cmp edx , 0 jne odd jmp done odd: done:
在上述示例中,我们将13存储在eax
寄存器中作为被除数,将2存储在ecx
寄存器中作为除数。然后使用div ecx
指令进行除法运算,商存储在eax
寄存器中,余数存储在edx
寄存器中。接下来,我们通过比较余数和0的值来判断奇偶性,如果余数不等于0,则跳转到odd
标签,执行奇数的处理逻辑;如果余数等于0,则跳转到done
标签,执行偶数的处理逻辑。
这是一个简单的例子,演示了如何使用div
指令来判断一个数的奇偶性。根据实际需求,您可以根据eax
寄存器的值进行不同的处理。
交换指令 xchg
此节为语言模型生成内容,参考 prompt
如下 1 介绍 IA-32 masm 中的 xchg 指令并举一个用其交换数据的例子
xchg
指令是IA-32汇编语言中的交换指令,用于交换两个操作数的值。它可以用于交换寄存器、内存和寄存器之间的数据。
xchg
指令的基本语法如下:
1 xchg destination, source
其中,destination
和source
表示要交换值的操作数。
下面是一个使用xchg
指令交换两个变量值的示例:
1 2 3 4 5 6 mov eax , 10 mov ebx , 20 xchg eax , ebx
在上述示例中,我们使用mov
指令将变量A的初始值10存储在eax
寄存器中,将变量B的初始值20存储在ebx
寄存器中。然后使用xchg
指令交换eax
和ebx
的值。交换后,eax
寄存器中存储的是变量B的值20,ebx
寄存器中存储的是变量A的值10。
通过xchg
指令,我们可以方便地交换两个变量的值,而不需要借助额外的寄存器或内存位置。这在某些算法或数据处理任务中可能会有用。
逻辑与指令 test
此节使用语言模型生成,prompt 格式同前节
test
指令是IA-32汇编语言中的逻辑与(AND)操作指令。它执行两个操作数之间的位逻辑与运算,并根据结果设置标志寄存器的状态。
test
指令的基本语法如下:
1 test destination, source
其中,destination
和source
表示要进行逻辑与运算的操作数。
test
指令执行以下操作:
将destination
和source
进行逻辑与运算,将结果存储在临时位置中。
根据结果设置标志寄存器的状态,特别是设置零标志位(ZF)和进位标志位(CF)。
test
指令常用于条件判断、位掩码和清零操作等。
下面是一些test
指令的使用示例:
1 2 3 4 5 6 7 8 9 10 11 mov eax , 10 test eax , eax mov ebx , 0xFF test ebx , 0x0F test edx , edx jz zero zero:
在第一个示例中,将常数10存储在eax
寄存器中,然后对eax
寄存器和自身执行逻辑与运算。这将检查eax
寄存器的值是否为零,并根据结果设置标志寄存器的状态。
在第二个示例中,将常数0xFF存储在ebx
寄存器中,然后对ebx
寄存器和常数0x0F执行逻辑与运算。这将检查ebx
寄存器的低4位是否都为1,并根据结果设置标志寄存器的状态。
在第三个示例中,将edx
寄存器与自身执行逻辑与运算。这将检查edx
寄存器的值是否为零,并根据结果设置标志寄存器的状态。如果edx
寄存器为零,将会发生跳转到zero
标签处执行相应的操作。
通过test
指令,我们可以执行逻辑与运算并根据结果设置标志寄存器的状态,以便进行条件判断和控制程序的流程。
处理器结构
基本结构
算术逻辑单元 (ALU
)
寄存器
指令处理单元
寄存器
寄存器就是 CPU
暂时存取数据的地方,在 IA-32
基本执行环境中包括8个32位通用寄存器,6个12位段寄存器,以及32位标志寄存器和指令指针。
常用寄存器
通用寄存器
通用寄存器一般是指处理器中最长使用的整数存取寄存器,可以用于保存整型数据和地址等。
在上图的寄存器展示中,这些32位寄存器也可以当作16位寄存器使用,只需要去除寄存器名前的 "E" 即可,例如 AX
, BX
, SI
, DI
等等。
甚至可以将寄存器的低16位拆成2个8位寄存器使用,例如图中 EAX
的 D15
-D8
就对应着 AH
,而低八位的 D7
-D0
则对应着 AL
寄存器。
通用寄存器的常用用途
EAX
: 最常用的寄存器,函数调用传参,作为累加器,算数运算和外设传送信息等
EBX
: 基址寄存器,长用来存放存储器地址
ECX
: 计数器
EDX
: 数据寄存器,可用来存放数据
ESI
: 源变址寄存器,用于指向字符串或数组的源操作数。源操作数是指保存传送结果或运算结果的寄存器。
EDI
: 目的变址寄存器,用于指向字符串或数组的目的操作数,目的操作数是指保存传送结果或者运算结果的操作数。
EBP
: 基址指针寄存器,默认指向程序堆栈区域的数据,主要用于在子程序中访问通过堆栈传递的参数和局部变量。
ESP
: 堆栈指令寄存器,专门 用于指向程序堆栈区域顶部的数据,在涉及堆栈操作时会自动变化。
标志寄存器
标志寄存器
OF
溢出标志、SF
符号标志、ZF
零标志、PF
奇偶标志、CF
进位标志、AF
调整标志
专用寄存器
指令寄存器 EIP
: 保存将要执行指令的地址
段寄存器: CS
DS
SS
ES
FS
GS
浮点寄存器、向量寄存器、媒体寄存器、系统寄存器等。
存储器组织
存储器模型
平展存储模型:存储器是连续的地址空间,每个存储单元存储一个字节并拥有一个线性地址,IA-32
处理器最大支持 \(2^{32} - 1\) (4GB) 的地址空间。
段式存储模型: 存储器由一组独立的地址空间(段)组成,代码、数据、堆栈位于分开的段中,程序利用逻辑地址寻址段中的字节,IA-32
支持 \(2^{14}\) 个大小类型各异的段,段的最大容量与平展存储相同,均为 4GB 。
实地址存储模型: 8086 的存储模型,是段式存储的特例,线性地址空间最大位 1MB ,由最大为 64KB 的多个段组成。
工作方式
保护方式(Protected Mode)
IA-32
处理器固有的工作状态
具有强大的段页式存储管理和特权与保护能力
使用全部32条地址总线,可寻址4GB物理存储器
使用平展或段式存储模型
利用虚拟8086方式支持实地址8086软件
实地址方式(Real Address Mode)
可以进行32位处理的快速8086
只能寻址1MB物理存储器空间,每个段不超过64KB
可以使用32位寄存器、32位操作数和32位寻址方式
只能支持实地址存储模型
系统管理方式(System Management Mode, SMM)
用于实现节能和系统安全管理
保存当前任务后切换到分开的地址空间
逻辑地址
逻辑地址是程序设计和处理器中使用的相对地址,逻辑地址 = 段基地址:偏移地址,编程使用的逻辑地址会被处理器映射为线性地址,在输出前转换为物理地址。
基本段
代码段: 存放指令代码
数据段: 存放数据
堆栈段: 堆栈数据
基本段的逻辑地址
代码段: CS
:EIP
数据段: DS
/FS
/GS
:取决于寻址方式,由存储器计算得出
堆栈段: SS
:ESP
段选择器
逻辑地址的段基地址部分由16位的段寄存器确定。段寄存器保存16位的段选择器,段选择器是一种特殊的指针,指向段描述符,段描述符包括段基地址,由段基地址就可以指明存储器中的一个段。
根据存储模型不同,段寄存器的具体内容也有所不同。
平展存储模型
6个段寄存器均指向线性地址空间的0地址
程序设置2个重叠的段,一个用于代码,一个用于数据和堆栈,CS段寄存器指向代码,其他段寄存器指向数据和堆栈段。
段式存储模型
段寄存器保存不同的段选择器,指向线性地址空间不同的段,例如下图所示,程序最多可以访问6个段,CS指向代码段,SS指向堆栈段,DS和其余4个段寄存器指向的数据段。
image.png
实地址存储模型
实地址存储的主存只有 1MB(\(2^{20}\) 字节),仅使用地址总线的低20位,物理地址范围为 00000H-FFFFFH。实地址存储模型也进行分段处理,但是存在2个限制:
每个段最大64KB
段只能开始于低4位地址全为0的物理地址处,因此实地址的段寄存器可以保存段基地址的高16位
地址转换
保护方式的地址转换
平整存储模型
段基地址为0,偏移地址等于线性地址
段式存储模型
段基地址和偏移地址均为32位
端基加上线性偏移等于线性地址
此外,在不使用分页机制时,线性地址与物理地址一一对应,使用分页时,线性地址空间被分成大小一致的块(页),构成虚拟存储器并转换到物理地址空间。
实地址方式的地址转换
汇编语言/实验混合
这块照搬书或者 PPT 就有点太无聊了,所以决定混合着上机实验一块复习了。
masm 模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 .586 .MODEL flat,stdcall includelib kernel32. lib includelib ucrt. lib includelib legacy_stdio_definitions. lib ExitProcess PROTO, dwExitCode:DWORD printf proto C: vararg scanf PROTO C : vararg _getch PROTO C : vararg _kbhit PROTO C : vararg.data .code main PROC push 0 call ExitProcess;对应return 0 main ENDP end main
实验二:简单分支程序设计
实验目的:
掌握算术运算指令
掌握 cmp
, test
以及各类转移指令
掌握 c 语言关系表达式和逻辑表达式对应的汇编实现
掌握分支程序设计
1. 判断输入
将下面C语言程序的代码片段转换为功能等价的汇编语言代码片段;编写一完整的汇编语言程序验证转换的正确性,其中sinteger(输入的整数) 与sign(输出的信息)均为双字变量。
1 2 3 4 5 6 if (sinteger == 0 ) sign = 0 ;else if ( siteger > 0 ) sign = 1 ; else sign = -1 ;
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 .586 .MODEL flat,stdcall includelib kernel32. lib includelib ucrt. lib includelib legacy_stdio_definitions. lib ExitProcess PROTO, dwExitCode:DWORD printf proto C: vararg scanf PROTO C : vararg _getch PROTO C : vararg _kbhit PROTO C : vararg.data sinteger dd 0 sign dd 0 string db '%d' , 0 .code main PROC push offset sinteger push offset string call scanf add esp , 8 cmp sinteger, 0 je zero jl negtive mov sign, 1 jmp print zero: mov sign, 0 jmp print negtive: mov sign, -1 print: push sign push offset string call printf add esp , 8 push 0 call ExitProcess main ENDP end main
感觉这个语法和 SHENZHENG/IO
里面限制20行的汇编也差不多(
不过里面用来判断的关键词是 teq
/tgt
/tlt
/tcp
游戏截图
稍微解释一下在这个实验中用到的 IA-32
masm
指令的用法
在 .data
数据段我们声明了两个初始化0的用于存储输入数字的双字变量,以及一个存入了格式化字符串的byte数组。
在代码段,使用 offset/addr
关键字获取偏移并压栈,并使用 call 关键字调用 scanf
函数读入,由于使用 stdcall
调用约定,所以栈指针 esp 上移 \(2 \times 4 = 8\) 个字节。
此处一种更简单的写法是使用 invoke
宏来处理,这个宏的用法会在之后的实验中介绍。
完成读入后使用 cmp
指令判断其与0的大小关系,并使用条件跳转指令 je
(jump if equal), jl
(jump if less) 与标签跳转至对应的分支。
并处理完对应分支的操作后重新跳转回主控制流,对应 jmp print
。
由于读入和输出的格式化字符串一致,所以此处就复用了,接下来的操作流程和开始读入时一致,故此处就不详细介绍了。
2.转换ASCII大小写
将下面C语言程序的代码片段转换为功能等价的汇编语言代码片段,其中 ch1
(输入的字符)与 caps
(输出的信息)均为字节变量。
1 2 3 4 if (ch1> =’a’ && ch1< =’z’) caps=0 ; ch1=upper(ch1);if (ch1> =’A’ && ch1< =’Z’) caps=1 ;
提示:小写字母与大写字母的 ASCII
码的差值为 20H
,也可以用 ch1 = ch1&0x5f
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 .586 .MODEL flat,stdcall includelib kernel32. lib includelib ucrt. lib includelib legacy_stdio_definitions. lib ExitProcess PROTO, dwExitCode:DWORD printf proto C: vararg scanf PROTO C : vararg _getch PROTO C : vararg _kbhit PROTO C : vararg.data ch1 byte 'a' cha byte 95 caps byte '0' infstring db "%c" , 0 outfstring db "cha='%c' cap='%c'" , 0 errfstring db "error" , 0 .code main PROC invoke scanf, addr infstring, addr ch1 mov al , ch1 cmp al , 'a' jb captial cmp al , 'z' ja captial and al , cha mov ch1, al jmp print captial: cmp al , 'A' jb error cmp al , 'Z' ja error mov caps, '1' print: invoke printf,addr outfstring , ch1, caps ret error: invoke printf,addr errfstring ret main ENDP end main
这一段代码中使用 invoke 宏来简化手动管理堆栈平衡的操作,并且使用了 ret 来返回控制权,同时为了处理输入字符可能存在的非法情况,加入了错误处理部分。
3. 100以内的偶数求和
将下面C语言程序的代码片段转换为功能等价的汇编语言代码片段;编写一完整的汇编语言程序验证转换的正确性,其中 sum
与 i
均为双字变量。
1 2 3 4 sum=0 ; for (i=1 ; i<=100 ; i++) if (i%2 ==0 ) sum=sum+i;
提示:可采用 div
指令求余数
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 .586 .MODEL flat,stdcall includelib kernel32. lib includelib ucrt. lib includelib legacy_stdio_definitions. lib ExitProcess PROTO, dwExitCode:DWORD printf proto C: vararg scanf PROTO C : vararg _getch PROTO C : vararg _kbhit PROTO C : vararg.data fstr db 'sum = %d' ,0 .code main PROC mov eax , 1 mov ebx , 0 mov ecx , 0 _loop: mov ecx , eax cmp ecx , 100 ja _end and ecx , 1 cmp ecx , 0 jnz _no_add add ebx , eax _no_add: add eax , 1 jmp _loop _end: invoke printf, addr fstr, ebx ret main ENDP end main
此处采用了一个 trick 来判断奇偶性,对于一个二进制原码整数来说,其奇偶性取决于其 0 位是否为 0,即结尾是 0 的必然是偶数,否则为奇数。
所以 and ecx, 1
如果此时 ecx
中结果为 1,说明其必然为奇数,不需要相加。
4. 100以内的正数求和
采用无条件 jmp
和条件转移 jcc
指令构造 while
或 do while
循环结构(不得使用 loop
指令),完成下面的求和任务并输出 sum
和 n
(变量为双字) (实际上是条件控制的循环) \[
sum=\sum_{i=1}^{100}i
\] 思考题:假设 sum
为双字无符号整数,在 sum
不溢出的情况下求出 n
的最大值;并输出此时的 sum
和 n
的值。
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 .586 .MODEL flat,stdcall includelib kernel32. lib includelib ucrt. lib includelib legacy_stdio_definitions. lib ExitProcess PROTO, dwExitCode:DWORD printf proto C: vararg scanf PROTO C : vararg _getch PROTO C : vararg _kbhit PROTO C : vararg.data fstr db 'sum = %d' , 0 .code main PROC mov eax , 1 mov ebx , 0 _loop: cmp eax , 100 ja _end add ebx , eax add eax , 1 jmp _loop _end: invoke printf, addr fstr, ebx ret main ENDP end main
思考题回答
1 2 3 4 5 6 7 8 9 10 const max = 2 **32 -1 const f = (n ) => (n * (n+1 )) /2.0 let n = 1 ;while (n++){ if (f (n) > max) { break ; } }console .log ("n = " , n)console .log ("sum = " , f (n))
使用 node
或者浏览器控制台运行即可,答案是 n = 92682, sum = 4295022903
5. 三数排序
从键盘上输入3个有符号的双字整数,编写一完整的程序按照又大到小的顺序输出这3个数。
提示:采用 xchg
指令交换两个变量的值。
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 .586 .MODEL flat,stdcall includelib kernel32. lib includelib ucrt. lib includelib legacy_stdio_definitions. lib ExitProcess PROTO, dwExitCode:DWORD printf proto C: vararg scanf PROTO C : vararg _getch PROTO C : vararg _kbhit PROTO C : vararg.data format db '%d %d %d' , 0 _a dd 0 _b dd 0 _c dd 0 .code main PROC invoke scanf, offset format, addr _a, addr _b, addr _c mov eax , _a mov ebx , _b mov ecx , _c cmp eax , ebx jnl l1 xchg eax , ebx l1: cmp eax , ecx jnl l2 xchg eax , ecx l2: cmp ebx , ecx jnl l3 xchg ebx , ecx l3: invoke printf, offset format, eax , ebx , ecx invoke ExitProcess,0 main ENDP end main
6. 判断闰年
输入年份并判断其是否是闰年,闰年需要满足以下条件中的一个:
该年份能被 4 整除同时不能被 100 整除;
该年份能被400整除。
提示:可采用 div
指令求余数
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 .586 .MODEL flat,stdcall includelib kernel32. lib includelib ucrt. lib includelib legacy_stdio_definitions. lib ExitProcess PROTO, dwExitCode:DWORD printf proto C: vararg scanf PROTO C : vararg _getch PROTO C : vararg _kbhit PROTO C : vararg.data fistr db '%d' , 0 fostr1 db 'is leep year' ,0 fostr2 db 'is not leep year' , 0 .code main PROC push ebp mov ebp , esp sub esp , 8 mov dword ptr [ebp ], 0 lea eax , [ebp -8 ] invoke scanf, addr fistr, eax mov ecx , dword ptr [ebp -8 ] mov eax , ecx mov ebx , 400 cdq idiv ebx cmp edx , 0 jz is_leap mov eax , ecx mov ebx , 4 cdq idiv ebx cmp edx , 0 jne not_leap mov eax , ecx mov ebx , 100 cdq idiv ebx cmp edx , 0 jne is_leap not_leap: invoke printf,addr fostr2 jmp _end is_leap: invoke printf,addr fostr1 _end: add esp , 8 mov esp , ebp pop ebp mov eax , 0 ret main ENDP end main
此处代码的分析使用语言模型生成,参考 prompt
如下 1 2 逐行分析下列 IA-32 masm 代码中 .code 段的意图
分析
该汇编代码段中的.code
段是用于定义程序的主要代码逻辑和流程。下面逐行分析该代码段的意图:
开始定义一个名为main
的过程(Procedure)。
保存旧的基址寄存器值并将当前栈指针赋值给基址寄存器,创建一个新的堆栈帧。
1 2 3 sub esp , 8 mov dword ptr [ebp ], 0 lea eax , [ebp -8 ]
在堆栈上分配8字节的空间,并将该空间的第一个双字(32位整数)初始化为0。然后,将eax
寄存器设置为指向这个双字的地址。
1 2 invoke scanf, addr fistr, eax mov ecx , dword ptr [ebp -8 ]
调用scanf
函数,从用户输入中读取一个整数,并将其存储到之前分配的空间中。然后,将ecx
寄存器设置为该整数的值。
1 2 3 4 5 6 mov eax , ecx mov ebx , 400 cdq idiv ebx cmp edx , 0 jz is_leap
将eax
寄存器设置为ecx
的值,然后将ebx
寄存器设置为400,执行带符号除法指令idiv
,将eax
寄存器中的值除以ebx
寄存器中的值。将得到的余数存储在edx
寄存器中,然后将edx
与0进行比较。如果相等(余数为0),则跳转到is_leap
标签处。
1 2 3 4 5 6 mov eax , ecx mov ebx , 4 cdq idiv ebx cmp edx , 0 jne not_leap
将eax
寄存器设置为ecx
的值,然后将ebx
寄存器设置为4,执行带符号除法指令idiv
,将eax
寄存器中的值除以ebx
寄存器中的值。将得到的余数存储在edx
寄存器中,然后将edx
与0进行比较。如果不相等(余数不为0),则跳转到not_leap
标签处。
1 2 3 4 5 6 mov eax , ecx mov ebx , 100 cdq idiv ebx cmp edx , 0 jne is_leap
将eax
寄存器设置为ecx
的值,然后将ebx
寄存器设置为100,执行带符号除法指令idiv
,将eax
寄存器中的值除以ebx
寄存器中的值。将得到的余数存储在edx
寄存器中,然后将edx
与0进行比较。如果不相等(余数不为0),则跳转到is_leap
标签处
1 2 3 4 5 6 7 8 9 10 11 12 13 14 not_leap: invoke printf, addr fostr2jmp _endis_leap: invoke printf, addr fostr1_end: add esp , 8 mov esp , ebp pop ebp mov eax , 0 ret
在not_leap
标签处,调用printf
函数,打印字符串fostr2
。然后跳转到_end
标签处。在is_leap
标签处,调用printf
函数,打印字符串fostr1
。然后继续执行到_end
标签处。
在_end
标签处,释放在堆栈上分配的8字节空间,恢复堆栈指针,并将旧的基址寄存器值恢复。最后,将eax
寄存器设置为0并返回。
整个代码段的目的是读取用户输入的一个整数,然后判断该整数是否为闰年,并根据判断结果打印相应的字符串。
实验三:循环程序设计实验
1. 2012-2100的闰年数量
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 .586 .MODEL flat,stdcall includelib msvcrt. lib INCLUDELIB kernel32. lib INCLUDELIB ucrt. lib INCLUDELIB legacy_stdio_definitions. lib ExitProcess PROTO, dwExitCode:DWORD printf proto C: vararg scanf proto C: vararg.data Lyear dword 25 dup(0 ) msg byte 'Leap year is follow:' ,10 ,0 format byte '%d' ,9 ,0 .code main Proc xor ecx ,ecx mov esi ,2012 jmp testingbody: mov eax ,esi mov ebx ,4 cdq div ebx cmp edx ,0 jne next mov eax ,esi mov ebx ,100 cdq div ebx cmp edx ,0 je next mov dword ptr Lyear[ecx *4 ],esi inc ecx jmp over next: mov eax ,esi mov ebx ,400 cdq div ebx cmp edx ,0 jne over mov dword ptr Lyear[ecx *4 ],esi inc ecx over: inc esi testing: cmp esi ,2100 jl body pushad invoke printf,offset msg popad xor esi ,esi again: pushad invoke printf,offset format,dword ptr Lyear[esi *4 ] popad inc esi loop again invoke ExitProcess, 0 main endp end main
2. 回文串判断
“回文串”是一个正读和反读都一样的字符串,比如“eye”、“level”、“noon”等。请写一个程序测试一字符串是否是“回文”, 是“回文”则显示“Y”。
提示:
合理分配寄存器: left = esi, right = edi, flag = al = 'Y'
采用相对寻址处理数组。
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 INCLUDELIB kernel32. lib INCLUDELIB ucrt. lib INCLUDELIB legacy_stdio_definitions. lib .386 .model flat,stdcall ExitProcess PROTO,dwExitCode: DWORD printf PROTO C : dword ,:vararg scanf PROTO C : dword ,:vararg.data array BYTE "lwasdffdsawl" ,0 str_error BYTE "该字符串不是回文串" ,0ah ,0 str_ok BYTE "该字符串是回文串" ,0ah ,0 .code main PROC xor esi , esi mov edi , LENGTHOF array - 2 jmp TESTINGFORLOOP: mov al ,array[esi *1 ] mov bl ,array[edi *1 ] cmp al ,bl jne EXIT inc esi dec edi jmp TESTINGEXIT: invoke printf, OFFSET str_error jmp ENDLOOPTESTING: cmp esi ,edi jbe FORLOOP invoke printf, OFFSET str_okENDLOOP: invoke ExitProcess, 0 main ENDP END main
3. 字符统计
编程写一个完整的程序统计字符串msg中的空格的个数与小写字母的个数,并分别将它们存入space单元与char单元中并输出。
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 .586 .MODEL flat, stdcall includelib msvcrt. lib INCLUDELIB kernel32. lib INCLUDELIB ucrt. lib INCLUDELIB legacy_stdio_definitions. lib ExitProcess PROTO, dwExitCode:DWORD printf proto C: vararg scanf proto C: vararg.data msg byte "hello, m a s m!" , 10 , 0 space dword ? lowercase dword ? frmStr byte "space num = %d, lowercase num = %d" , 10 , 0 .code main proc mov ecx , lengthof msg mov esi , offset msg sub eax , eax mov ebx , eax again: mov dl , [esi ] cmp dl , ' ' jnz letter inc eax letter: cmp dl , 'a' jb next cmp dl , 'z' jg next inc ebx next: inc esi loop again pushad invoke printf, offset frmStr, eax , ebx popad invoke ExitProcess, 0 main endp end main
4. 求数组最大值与最小值
编程写一个完整的程序,求数组 array[12, 4, 168, 122, -33, 56, 78, 99, 345, 66, -5]
中的最大值与最小值,并将它们分别存入 max
和 min
单元中。
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 .586 .MODEL flat, stdcall includelib msvcrt. lib INCLUDELIB kernel32. lib INCLUDELIB ucrt. lib INCLUDELIB legacy_stdio_definitions. lib ExitProcess PROTO, dwExitCode: DWORD printf proto C: vararg scanf proto C: vararg _getch PROTO C: DWORD , : vararg.data array dword 12 , 4 , 168 , 122 , -33 , 56 , 78 , 99 , 345 , 66 , -5 fstr1 byte 'min=%d' , 0ah , 0 fstr2 byte 'max=%d' , 0ah , 0 .code main proc mov edx , offset array mov ecx , lengthof array mov eax , [edx ] mov ebx , [edx ] xor esi , esi _loop: mov esi , [edx ] check_min: cmp eax , esi jle check_max mov eax , esi check_max: cmp ebx , esi jge check_end mov ebx , esi check_end: add edx , 4 loop _loop invoke printf, addr fstr1, eax invoke printf, addr fstr2, ebx invoke ExitProcess, 0 main endp end main
5. 寻找回文数
求10到10000之间所有回文数并输出。要求每行输出10个数。
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 .586 .MODEL flat,stdcall includelib msvcrt. lib INCLUDELIB kernel32. lib INCLUDELIB ucrt. lib INCLUDELIB legacy_stdio_definitions. lib ExitProcess PROTO, dwExitCode:DWORD printf proto C: vararg scanf proto C: vararg.data msg1 byte '%d' ,9 ,0 msg2 byte 10 ,0 .code main Proc mov esi ,10 mov edi ,0 body: mov ecx ,0 mov eax ,esi reverse: imul ecx , 10 mov ebx , 10 cdq idiv ebx add ecx , edx cmp eax , 0 jne reverse cmp ecx , esi jne loop_check invoke printf,addr msg1, ecx inc edi cmp edi , 10 jne loop_check invoke printf, addr msg2 xor edi , edi loop_check: inc esi cmp esi ,10000 jl body invoke ExitProcess, 0 main endp end main
6. 剔除空格
有一个首地址为 string
的字符串,剔除 string
中所有的空格字符。请从字符串最后一个字符开始逐个向前判断、并进行处理。
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 .586 .MODEL flat, stdcall includelib msvcrt. lib INCLUDELIB kernel32. lib INCLUDELIB ucrt. lib INCLUDELIB legacy_stdio_definitions. lib INCLUDELIB kernel32. lib INCLUDELIB ucrt. lib INCLUDELIB legacy_stdio_definitions. lib.386 .model flat, stdcall ExitProcess PROTO,dwExitCode: DWORD printf PROTO C: DWORD , : vararg scanf PROTO C: DWORD , : vararg.data string byte 'Dubi Dubi Duba Dubi Dubi Duba Perry' , 0 format byte '%s' , 10 , 0 .code main Proc mov esi , lengthof string - 1 mov edi , esi jmp body_checkbody: cmp byte ptr string[esi ], ' ' jne _continue mov ebx , esi jmp move_check_move: mov al , byte ptr string[ebx + 1 ] mov byte ptr string[ebx ], al inc ebx move_check: cmp ebx , edi jl _move dec edi _continue: dec esi body_check: cmp esi , 0 jge body invoke printf, offset format, offset string invoke ExitProcess, 0 main endp end main
这段代码简单来说就是从后往前寻找空白字符,如果找到了,将后续所有往前移动一步然后接着找空白字符,直至找到字符串的头部。
7. 素数筛
求出2~1000之间的所有素数,并将它们存入 Prime
数组中,素数的个数存入变量 Pcounter
中。要求每行输出10个数。
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 .586 .MODEL flat, stdcall includelib msvcrt. lib INCLUDELIB kernel32. lib INCLUDELIB ucrt. lib INCLUDELIB legacy_stdio_definitions. lib ExitProcess PROTO, dwExitCode: DWORD printf proto C: vararg scanf proto C: vararg.data fstr1 byte '%d ' , 0 fstr2 byte 10 , 0 fstr3 byte 'prime number count: %d' , 10 , 0 prime dword 2 , 3 , 0 dup(1000 ).code main Proc mov esi , 4 mov ecx , 2 jmp body_check body: mov edi , 0 sieve: mov eax , esi mov ebx , dword ptr prime[edi *4 ] cdq idiv ebx cmp edx , 0 jz _continue inc edi cmp edi , ecx jl sieve mov dword ptr prime[ecx *4 ], esi inc ecx _continue: inc esi body_check: cmp esi , 1000 jl body mov eax , 0 mov esi , 0 print: mov ebx , dword ptr prime[eax *4 ] pushad invoke printf, addr fstr1, ebx popad inc eax inc esi cmp esi , 10 jne _continue_print pushad invoke printf, addr fstr2 popad mov esi , 0 _continue_print: loop print invoke ExitProcess, 0 main endp end main
实验四:子程序与操作系统功能调用
1. 递归阶乘
编写一个求 \(n!\) 的子程序,利用它求 \(1!+2! +3! +4! +5! +6! +7! +8!\) 的和(46233)并输出。
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 INCLUDELIB kernel32. lib INCLUDELIB ucrt. lib INCLUDELIB legacy_stdio_definitions. lib.386 .model flat, stdcall ExitProcess PROTO,dwExitCode: DWORD printf PROTO C: DWORD , : VARARG.data msg1 byte "f(x)=x!" ,10 ,0 msg2 byte "f(%d)=%d" ,10 , 0 msg3 byte "sum=%d" , 10 ,0 .code main PROC invoke printf, addr msg1 mov ecx , 1 mov edx , 0 fac: push ecx call Factorial add esp , 4 add edx , eax pushad invoke printf, addr msg2, ecx , eax popad inc ecx cmp ecx , 8 jle fac invoke printf, addr msg3, edx invoke ExitProcess, 0 main ENDP Factorial PROC push ebp mov ebp , esp sub esp , 4 mov eax , [ebp + 8 ] cmp eax , 1 jbe Return dec eax push eax call Factorial add esp , 4 imul eax , [ebp + 8 ] Return: mov esp , ebp pop ebp ret 4 Factorial ENDP END main
2. 斐波那契数列
Fibonacci numbers的定义: \[
f_1=1,\ f_2=1,\ f_n = f_{n-1}+f_{n-2}\ when \ n\ge3
\] 编程输出 Fibonacci numbers 的前20项。 思考题:在不产生溢出的情况下 \(n\) 的最大值是多少?
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 INCLUDELIB kernel32. lib INCLUDELIB ucrt. lib INCLUDELIB legacy_stdio_definitions. lib.386 .model flat, stdcall ExitProcess PROTO, dwExitCode: DWORD printf PROTO C : dword , :vararg scanf PROTO C : dword , :vararg.data array dword 1 , 1 , 18 dup(0 ) format byte '%d' , 10 , 0 .code main Proc mov esi , 2 jmp checkbody: mov eax , dword ptr array[esi *4 - 4 ] mov ebx , dword ptr array[esi *4 - 8 ] add eax , ebx mov dword ptr array[esi *4 ], eax inc esi check: cmp esi , 20 jl bodyxor esi , esi mov ecx , 20 print: pushad invoke printf, offset format, dword ptr array[esi ] popad add esi , 4 loop print invoke ExitProcess, 0 main endp end main
思考题答案
1 2 3 4 5 6 7 8 9 10 11 const max = 2 **32 - 1 let s = Array (100 ).fill (1 ) s[0 ]=0 for (let i = 3 ;;i++){ s[i]=s[i-1 ]+s[i-2 ] if (s[i] > max) { console .log ("i=" , i - 1 ) console .log ("max=" , s[i -1 ]) break } }
\[i = 47,
max = 2971215073\]
3. 欧几里得算法
编程写一个名为 Gcd
的求两个数最大公约数子程序,主子程序间的参数传递通过堆栈完成。调用 Gcd
子程序求出三个双自变量:dvar1
、dvar2
与 dvar3
的最大公约数并输出。
提示:\(result=gcd(gcd(dvar1,dvar2), dvar3)\)
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 .586 .MODEL flat, stdcall includelib msvcrt. lib INCLUDELIB kernel32. lib INCLUDELIB ucrt. lib INCLUDELIB legacy_stdio_definitions. lib ExitProcess PROTO, dwExitCode:DWORD printf proto C: vararg scanf proto C: vararg.data prompt byte '请输入三个整数(以空格分隔):' , 0 fmt1 byte '%d %d %d' , 0 fmt2 byte '最大公约数:%d' , 13 , 10 , 13 , 10 , 0 dvar1 dword ? dvar2 dword ? dvar3 dword ? res dword ?.code main proc invoke printf, offset prompt invoke scanf, offset fmt1, offset dvar1, offset dvar2, offset dvar3 push dvar1 push dvar2 call gcd add esp , 8 push eax push dvar3 call gcd add esp , 8 mov res, eax pushad invoke printf, offset fmt2, res popad invoke ExitProcess, 0 main endp gcd proc push ebp mov ebp , esp push ebx push edx mov eax , [ebp + 8 ] mov ebx , [ebp + 12 ] .if eax < ebx xchg eax , ebx .endif .while ebx != 0 xor edx , edx idiv ebx mov eax , edx xchg eax , ebx .endw pop edx pop ebx pop ebp ret gcd endp end main
代码解释
在提供的代码中,欧几里得算法的实现部分位于gcd proc
和gcd endp
之间的代码块中。让我们逐行详细解释这部分代码的实现过程:
1 2 3 4 5 6 7 8 gcd proc push ebp mov ebp , esp push ebx push edx mov eax , [ebp + 8 ] mov ebx , [ebp + 12 ]
首先,过程开始时,保存旧的栈帧指针(ebp)并将当前栈帧指针(esp)赋给ebp,以建立新的栈帧。然后,将ebx和edx寄存器的值入栈保存。接下来,将第一个参数(dvar1)的值加载到eax寄存器,将第二个参数(dvar2)的值加载到ebx寄存器。
1 2 3 .if eax < ebx xchg eax , ebx .endif
通过使用xchg指令,将eax和ebx的值交换,以确保eax中存储的值始终大于等于ebx中存储的值。
1 2 3 4 5 6 .while ebx != 0 xor edx , edx idiv ebx mov eax , edx xchg eax , ebx .endw
接下来,使用循环来执行欧几里得算法的主要部分。使用while循环,条件是ebx不等于0。
首先,通过使用xor指令将edx寄存器清零,以准备执行idiv(除法)操作。
然后,使用idiv指令将eax寄存器的值除以ebx寄存器的值。idiv指令执行有符号除法,并将商存储在eax中,余数存储在edx中。
将edx的值移动到eax寄存器,更新eax的值为余数。
最后,再次使用xchg指令交换eax和ebx的值。
这个循环将一直执行,直到ebx等于0为止,此时最大公约数存储在eax寄存器中。
1 2 3 4 5 pop edx pop ebx pop ebp ret gcd endp
最后,恢复先前保存的ebx和edx的值,然后恢复旧的栈帧指针ebp。通过ret指令,从gcd过程返回到调用它的位置。
在主过程中,使用call gcd
的方式调用gcd过程,将两个整数作为参数传递给它。然后,使用push eax
将最大公约数的值压入堆栈,以便后续处理。
这样,在主过程中调用两次gcd过程,分别计算了dvar1和dvar2、dvar3和之前计算得到的最大公约数的最大公约数。最后,将最终的最大公约数(res)存储在变量中,并使用printf函数将其输出到控制台。
4. 冒泡排序
编程写一个名为 Bubble
的冒泡排序子程序,主子程序间的参数传递通过堆栈完成;并写主程序验证它(array dword 10,1,8,7,5,3,4,6,2,9)。
参考代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 INCLUDELIB kernel32. lib INCLUDELIB ucrt. lib INCLUDELIB legacy_stdio_definitions. lib.386 .model flat, stdcall ExitProcess PROTO, dwExitCode: DWORD printf PROTO C: DWORD , :vararg scanf PROTO C: DWORD , :vararg.data array dword 10 , 1 , 8 , 7 , 5 , 3 , 4 , 6 , 2 , 9 msg byte '%d ' , 0 .code main Proc mov ecx , lengthof array - 1 mov esi , offset array push ecx push esi call BubbleSort mov ecx , lengthof array mov esi , 0 print: pushad invoke printf, offset msg, dword ptr array[esi * 4 ] popad inc esi loop print invoke ExitProcess, 0 main endp BubbleSort proc push ebp mov ebp , esp mov edx , dword ptr [ebp + 8 ] mov ecx , dword ptr [ebp + 12 ] _out: push ecx xor esi , esi _in: mov eax , [edx + esi * 4 ] mov ebx , [edx + esi * 4 + 4 ] cmp eax , ebx jle _continue mov [edx + esi * 4 ], ebx mov [edx + esi * 4 + 4 ], eax _continue: inc esi loop _in pop ecx loop _out pop ebp ret 8 BubbleSort endp end main
微机总线
总线是微机系统中用于传输信息的公共通道,部件通过总线相互连接实现数据传输,部件之间需要遵守共同的总线规范。
总线技术
总线类型
芯片总线
集成电路芯片内部,芯片级互联,或者系统中各种不同的器件连接起来的总线。
局部总线,微处理器的引脚信号。
片内总线,大规模集成电路内部连接。
内总线
模块级互联,主机内部功能单元互连的总线。
板级总线、母板总线,或系统总线
系统总线(System Bus)是微机系统的主要总线
内部总线从一条变为多条,形成多总线结构
外总线(External Bus)
设备级互连,微机与其外设或微机之间连接的总线
过去,指通信总线
现在,常延伸为外设总线
微机总线层次结构
总线的数据传输
主设备(Master):控制总线完成数据传输 从设备(Slave):被动完成数据交换
同一时刻最多一个主设备控制总线,从设备无限制 同一时刻最多一设备通过总线发送数据,接受设备数量无限制
总线的操作
请求和仲裁(Bus request & Arbitration)
使用总线的主模块提出申请
仲裁机制把总线分配给请求设备
寻址(Addressing)
主模块发出访问的从模块地址信息以及有关命令,启动从模块。
数据传送 (Data Transfer)
源模块通过数据总线向目标模块发送信息。
结束(Ending)
数据、地址、状态、命令信息均从总线上撤除,让出总线
仲裁
总线仲裁:决定当前控制总线的主设备
集中仲裁
系统具有中央仲裁器(控制器)
负责主模块的总线请求和分配总线的使用
主模块有两条信号线:总线请求和总线响应
分布仲裁
各个主模块都有自己的仲裁器和唯一的仲裁号
主模块请求总线时,发送其仲裁号
比较各个主设备仲裁号决定
同步方式
同步时序
总线操作过程由共用的总线时钟信号控制
适合速度相当的器件互连总线,否则需要准备好信号让快速器件等待慢速器件(半同步)
半同步时序需要增加READY等状态信号
处理器控制的总线时序采用同步时序
异步时序
总线操作需要握手联络(应答)信号控制
传输的开始伴随有启动(选通或读写)信号
传输的结束有一个确认信号,进行应答
操作周期可变、可以混合慢速和快速器件
传输类型
猝发传送(数据块传送)
给出起始地址,将固定块长的数据,一个就一个的从相邻地址读入或写出
写后读(Read-After-Write)
先写后读,适用于校验。
读修改写 (Read-Modify-Write)
先读后写同一个地址单元,适用共享数据保护
广播 (Broadcast)
一个主设备对多个从设备的写入操作
性能指标
总线宽度(8,16,32,64位)
总线能够同时传送的数据位数 位数越多,一次能够传送的数据量越大
总线频率Hz
总线信号的时钟频率 时钟频率越高,工作速度越快
总线带宽(Bandwidth)
单位时间传输的数据量MB/s 总线带宽越大,总线性能越高
\(总线带宽=总线传输速率=吞吐率=传输的数据量\div所需时间\)
计算例子
总线信号和时序
总线分类
地址总线
主从模块的地址总线输出
从模块的地址总线输入
数据总线
双向传输
控制总线
有输出也有输入信号
基本功能是控制存储器及I/O读写操作
还包括中断与DMA控制、总线仲裁、数据传输握手联络等
引脚信号
引脚信号由以下方面构成
信号的功能
信号的流向
有效方式
三态能力
引脚信号功能示意图
总线时序
总线时序描述了总线信号的时间变化规律和相互关系。一个时钟周期是处理器的基本工作节拍。
8086芯片
8086的引脚信号
8086的总线时序
8086的基本总线周期由四个时钟周期 \(T_{1-4}\) 构成,存储器读和IO读,存储器写和IO写又可以分别统一到一个时序图表达。
写总线周期
写总线周期时序图1
写总线周期图2
Tw状态
读总线周期
读总线周期图1
读读总线周期时序图2
中断请求和响应、总线请求和响应、复位、时钟等信号的作用
在8086处理器的总线系统中,以下是各个信号的作用:
中断请求和响应:
中断请求(INTR):外部设备通过将INTR引脚置为低电平来请求中断。设备可以向处理器发出中断请求,以便在需要时暂停当前正在执行的程序。
中断响应(INTA):处理器在接收到中断请求后,通过将INTA引脚置为低电平来响应中断请求。这向外部设备发出确认信号,表明处理器已经准备好接收中断向量。
总线请求和响应:
总线请求(BUSRQ):用于暂停处理器的总线操作,例如DMA控制器请求访问系统总线。
总线响应(BUSD):用于确认处理器的总线请求,以便处理器知道其他设备已经释放了系统总线。
复位信号(RESET):
复位信号用于将处理器重置为初始状态。当RESET引脚被拉低时,处理器会停止执行并重新初始化其内部状态。这是一个系统级信号,用于启动系统的初始化过程。
时钟信号(CLK):
时钟信号提供处理器的基准时钟脉冲。它驱动处理器的内部时序和操作,并确保各个部件在正确的时间进行操作。处理器根据时钟信号的上升沿或下降沿执行指令和其他操作。
总的来说,中断请求和响应信号用于处理外部设备的中断,使处理器能够在需要时响应外部事件。总线请求和响应信号用于处理器和其他设备之间的总线控制和共享。复位信号用于将处理器重置为初始状态,而时钟信号则提供处理器操作的基准时序。这些信号在处理器的正常操作和系统的协调中起着重要的作用。
总结
输入输出接口
IO接口典型模式
内部结构
IO接口典型结构图
数据寄存器
保存处理器与外甥间交换的数据的寄存器。
状态寄存器
保存外设或其接口电路当前的工作状态信息
控制寄存器
保存控制接口电路和外设操作的有关信息。
基本功能
数据缓冲
信号变换
IO端口的编址
端口与存储器地址独立编址
独立两个地址空间。
优点:不占用存储器空寂
缺点:指令功能简单,寻址方式不如存储器指令丰富
统一编址
统一编排,共享地址空间,将IO地址映射到了存储器空间。
优点:不用专门设计IO指令和引脚,IO访问可复用存储器指令。
缺点:IO端口占据了存储器的地址空间。
IO地址译码
IO地址译码
输入输出指令
输入指令 IN 和输出指令 OUT
IO寻址方式
直接寻址
由IO指令直接提供8位IO地址,只能寻址最低256个地址。
在IO指令中,用i8表示这个地址,形式类似于立即数,但是在IO指令上就表示直接寻址的IO地址。
DX间接寻址
用16位寄存器DX保存IO地址。
IA-32处理器的IO地址共64k,每个地址对应一个8位端口,无需分段管理,最低256个地址可直接寻址或间接寻址访问,其余地址只能通过DX间接访问。
数据传输量
无条件传送和查询传送
数据传送方式
软件控制的数据传送
无条件传送
字面意思,根据原文意思可能是指无阻塞的传送。
例如数码管、按键、LED灯总是处于就绪状态的设备可采用无条件传送,数据缓冲通过三台缓冲器和锁存器实现。
查询传送
询问外设工作状态并在外设准备好数据后开始数据传送。
特点
流程图
编程
查询输入
查询输出
中断传送
外设向处理器发送请求,处理器在满足条件的情况下中断暂停当前程序并与外设进行数据传送。
工作流程图
附加硬件控制的数据传送
DMA传送
需要大量数据传送的外设,处理器转移控制权给DMA控制器,由其控制外设与存储器的数据传送。
IO处理器控制传送
专门的IO处理器管理。