OS_lab2
OS_Lab2
实验概述
在本次实验中,同学们会学习到x86汇编、计算机的启动过程、IA-32处理器架构和字符显存原理。根据所学的知识,同学们能自己编写程序,并且让计算机在启动后加载运行,增进对计算机启动过程的理解,为后面编写操作系统加载程序奠定基础。同时,同学们将学习使用gdb来调试程序的基本方法。
实验要求
DDL:2025.3.23
提交内容:将3+1(选做)个任务的代码和实验报告放到压缩包中,命名为“lab2-姓名-学号”,提交到实验课程邮箱:os_sysu_lab@163.com。
将实验报告的pdf提交至 http://inbox.weiyun.com/zPIW1se1
实验入门
- 从汇编语言开始–汇编语言提供了一些特权指令,而高级指令并未提供对应的指令。
体系结构
IA-32处理器:从Intel 80386开始到32位的奔腾4处理器
Intel 32位的处理器也被称为x86处理器
IA-32处理器有三种基本操作模式:保护模式*、*实地址模式(简称实模式)和系统管理模式。
IA-32的重要组成部分:
- 地址空间:保护模式:使用32位地址总线、32位寄存器;实模式:20位地址总线、16位寄存器
- 基本寄存器:IA-32处理器主要有8个通用寄存器eax, ebx, ecx, edx, ebp, esp, esi, edi、6个段寄存器cs, ss, ds, es, fs, gs、标志寄存器eflags、指令地址寄存器eip。
- 通用寄存器:通用寄存器有8个,分别是eax, ebx, ecx, edx, ebp, esp, esi, edi,均是32位寄存器。
- 通用寄存器用于算术运算和数据传输。32位寄存器用于保护模式,为了兼容16位的实模式,每一个32位寄存器又可以拆分成16位寄存器和8位寄存器来访问。
0-31位 | 0-15位 | 8-15位 | 0-7位 |
---|---|---|---|
eax | ax | ah | al |
ebx | bx | bh | bl |
ecx | cx | ch | cl |
edx | dx | dh | dl |
esi,edi,ebp和esp并无8位的寄存器访问方式
0-31位 | 0-15位 |
---|---|
esi | si |
edi | di |
esp | sp |
ebp | bp |
通用寄存器的特殊用法:
- eax在乘法和除法指令中被自动使用,通常称之为扩展累加寄存器。
ecx在loop指令中默认为循环计数器。
esp用于堆栈寻址。因此,我们绝对不可以随意使用esp。
esi和edi通常用于内存数据的高速传送,通常称之为扩展源指针和扩展目的指针寄存器。
ebp通常出现在高级语言翻译成的汇编代码中,用来引用函数参数和局部变量。除非用于高级语言的设计技巧中,ebp不应该在算术运算和数据传送中使用。ebp一般称之为扩展帧指针寄存器。
段寄存器。段寄存器有cs, ss, ds, es, fs, gs,用于存放段的基地址,段实际上就是一块连续的内存区域。
指令指针。eip存放下一条指令的地址。有些机器指令可以改变eip的地址,导致程序向新的地址进行转移,如ret指令。
状态寄存器。eflags存放CPU的一些状态标志位。下面提到的标志如进位标志实际上是eflags的某一个位。常用的标志位如下。
- 进位标志(CF)。在无符号算术运算的结果无法容纳于目的操作数时被置1。
- 溢出标志(OF)。在有符号算术运算的结果无法容纳于目的操作数时被置1。
- 符号标志(SF)。在算术或逻辑运算产生的结果为负时被置1。
- 零标志(ZF)。在算术或逻辑运算产生的结果为0时被置1。
实地址模式
- 如上述所说,实地址模式的寄存器都是16位的,因此名称都不带e
- 实地址的地址线是20位的,但寄存器都是16位的–采用“段地址+偏移地址”
$$
物理地址=(段地址<<4)+偏移地址
$$
段寄存器也有约定俗成的规则。一个典型的程序有3个段,数据段、代码段和堆栈段。
- cs包含16位代码段的基地址。
- ds包含16位数据段的基地址。
- ss包含一个16位堆栈段的基地址。
- es、fs和gs可以指向其他数据段的基地址。
由于段地址必须通过段寄存器给出,因此下面直接用“段寄存器”来代替“段地址”,即物理地址可表示为“段寄存器:偏移地址”。
汇编基础
- 汇编代码一般保存在以
.s
或.asm
为后缀的文件中。(我们可以在终端采用特定命令将高级语言代码转换为汇编代码)
寄存器 | 作用 |
---|---|
ax | 累加寄存器 |
cx | 计数寄存器 |
dx | 数据寄存器 |
ds | 数据段寄存器 |
es | 附加段寄存器 |
bx | 基地址寄存器 |
si | 源变址寄存器 |
di | 目的变址寄存器 |
cs | 代码段寄存器 |
ip | 指令指针寄存器 |
ss | 栈段寄存器 |
sp | 栈指针寄存器 |
bp | 基指针寄存器 |
flags | 标志寄存器 |
- 汇编注释:在汇编代码中使用分号
;
来注释 - 在汇编代码中,一行只能写一条汇编语句而无需以任何符号结尾
- 例子:
add eax,3 ;这是注释
- 例子:
nasm汇编–标识符
- 标识符用来表示变量、常量、过程或代码标号
- 标识符包含1-247个字符
- 对大小写不敏感!
- 标识符第一个字符必须是字母、下划线或@;不可以是数字
- 标识符不能与汇编器的保留字相同。
nasm汇编–标号
- 标号是充当指令或数据位置标记的标识符;标号的值就是其后指令或数据的起始地址(偏移地址)
- 数据标号标识了变量的地址,为在代码中引用该变量提供了方便。
数据类型 | 含义 |
---|---|
db | 一个字节 |
dw | 一个字,2个字节 |
dd | 双字,4个字节 |
例子:
1 |
|
对应了:
array[0] = 1024
array[1] = 2048
array[2] = 4096
array[3] = 8192
- 代码标号:代码标号标识了汇编指令的起始地址,通常作为跳转指令的操作数。
- 代码标号后面必须要有冒号
:
;但数据标号后面没有
数据传送指令
符号 | 含义 |
---|---|
<reg> |
寄存器,如ax,bx等 |
<mem> |
内存地址,如标号var1,var2等 |
<con> |
立即数,如3,9等 |
mov
指令:将源操作数的内容复制到目的操作数中
1 |
|
Intel汇编中,前者是目的操作数,后者是源操作数
nasm汇编–内存寻址方法
寄存器寻址:
mov ax,cx
立即数寻址:
mov ax,7
ormov ax,tag
(tag表示的是标号)直接寻址:
mov ax, [0x5c00] ; ax = 0xFF
在根据偏移地址去取内存中的变量时,要加上
[]
,否则就只是将变量地址放到寄存器中mov ax, [tag] ; ax = 0xFF
我们指令中如果没有显式指定段地址,那么我们的地址就是偏移地址
访问数据段,使用段寄存器ds。
访问代码段,使用段寄存器cs。
- 访问栈段,使用段寄存器ss。
基址寻址:基址寻址使用基址寄存器和立即数来构成真实的偏移地址
- 基址寄存器只能是bx或bp;用bx做基址寄存器时,段地址寄存器默认为ds,使用bp时默认为ss
1 |
|
变址寻址:变址寻址使用变址寄存器和立即数来构成真实的偏移地址。
变址寄存器只能是
si
或di
,默认段寄存器为ds
1
2mov ax, [si + 4 * 4]
mov [di], 0x5
基址变址寻址:我们通过基址寄存器、变址寄存器、立即数来构成真实的偏移地址。默认段地址由基址寄存器的类型确定,即
bx
对应ds
、bp
对应ss
,如下所示。1
2
3
4
5mov [bx + si + 5 * 4], ax
mov [bx + di + 5 * 4], ax
mov ax, [bx + si + 5 * 4]
mov ax, [bp + si + 5 * 4]
mov ax, [bp + di + 5 * 4]
x86汇编–算数和逻辑指令
add指令:前面是目的操作数,所以const不可以在前面。
1
2
3
4
5
6
7
8add <reg>, <reg>
add <reg>, <mem>
add <mem>, <reg>
add <reg>, <con>
add <mem>, <con>
; e.g.
add ax, 10 ; eax := eax + 10
add byte[tag], alsub指令:类似于add
1
2
3
4
5
6
7
8sub <reg>, <reg>
sub <reg>, <mem>
sub <mem>, <reg>
sub <reg>, <con>
sub <mem>, <con>
; e.g.
sub al, ah ; al := al - ah
sub ax, 126imul是整数相乘指令,它有两种指令格式,一种为两个操作数,将两个操作数的值相乘,并将结果保存在第一个操作数中,第一个操作数必须为寄存器;第二种格式为三个操作数,其语义为:将第二个和第三个操作数相乘,并将结果保存在第一个操作数中,第一个操作数必须为寄存器。
1 |
|
idiv完成整数除法操作,idiv只有一个操作数,此操作数为除数,而被除数则为
edx:eax
中的内容(一个64位的整数),操作的结果有两部分:商和余数,其中商放在eax寄存器中,而余数则放在edx寄存器中1
2
3
4
5idiv <reg> ;这里给出的是除数
idiv <mem>
; e.g.
idiv ebx
idiv dword[var]inc,dec指令分别表示自增1或自减1
1
2
3
4
5
6
7inc <reg>
inc <mem>
dec <reg>
dec <mem>
; e.g.
dec ax
inc byte[tag]and, or, xor分别表示将两个操作数逻辑与、逻辑或和逻辑异或后放入到第一个操作数中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17and <reg>, <reg>
and <reg>, <mem>
and <mem>, <reg>
and <reg>, <con>
and <mem>, <con>
or <reg>, <reg>
or <reg>, <mem>
or <mem>, <reg>
or <reg>, <con>
or <mem>, <con>
xor <reg>, <reg>
xor <reg>, <mem>
xor <mem>, <reg>
xor <reg>, <con>
xor <mem>, <con>not表示对操作数每一位取反
1 |
|
- neg表示取负
1 |
|
- shl,shr表示逻辑左移和逻辑右移
1 |
|
控制转移指令
jmp无条件跳转–
jmp <label>
jcondition有条件跳转
1
2
3
4
5
6
7je <label> ; jump when equal
jne <label> ; jump when not equal
jz <label> ; jump when last result was zero
jg <label> ; jump when greater than
jge <label> ; jump when greater than or equal to
jl <label> ; jump when less than
jle <label> ; jump when less than or equal tocmp指令:操作数1-操作数2,结果与机器状态寄存器eflags中的条件码比较
栈操作指令
- 栈的增长方式是从高地址向低地址增长
- push指令:将操作数压入内存的栈中;可以对reg,mem,con做
- pop指令:将栈顶的数据放入到操作数中;reg,mem
- pushad指令是将ax, cx, dx, bx, sp, bp, si, di 依次压入栈中。
popad
指令是对栈指令一系列的pop操作,pop出的数据放入到di, si, bp, sp, bx, dx, cx, ax中。
过程调用
call和ret指令是用来实现子过程(或者称函数,过程,意思相同)调用和返回。call指令首先将当前eip的内容入栈,然后将操作数的内容放入到eip中。ret指令将栈顶的内容弹出栈,放入到eip中。
计算机开机启动过程
- 计算机的启动需要程序加载,而计算机不启动则无法运行程序。
- 加电开机–BIOS启动–加载MBR–硬盘启动–内核启动
Example1:Hello World
- task:在MBR被加载到内存地址0x7c00之后,向屏幕输出蓝色的Hello World
- 为了便于控制显示,IA-32处理器将显示矩阵映射到了内存0xB8000~0xBFFFF处,称为显存地址。
- 在文本模式下,控制器的最小可控制单位为字符,每一个显示字符自上向下,从左到右依次使用显存地址中的两个字节表示–低字节表示所要显示的字符,高字节表示字符的颜色属性。
字符的颜色属性的字节高四位为背景色,低四位为前景色,具体如下:
我们使用的是二维的点,但是在栈上是线性的 =》转换为一维的点
$$
\text{显存起始位置}=\text{0xB8000}+2\cdot(80\cdot x+y)
$$(x,y)=(row,col),公式中的乘2是因为每个显示字符会使用两个字节表示
编写MBR:
1 |
|
org
是汇编语言中的伪指令,用于指令程序 <u>
加载到内存中的起始地址 </u>
0x7c00
是 BIOS 将引导扇区加载到内存中的固定地址。BIOS 在启动时会自动将磁盘的第一个扇区(512字节)加载到内存地址 0x7c00
处,然后从这里开始执行。
6个段寄存器cs, ss, ds, es, fs, gs
由于汇编不允许使用立即数直接对段寄存器赋值,所以要借助ax,在第三行给ax赋值为0之后,再让ax给段寄存器赋值
$
表示当前汇编地址,$$
表示代码开始的汇编地址。times 510 - ($ - $$) db 0
表示填充字符0直到第510个字节
利用nasm汇编器来将代码编译成二进制文件:
nasm -f bin mbr.asm -o mbr.bin
-f
参数制定了输出文件格式-o
指定的是输出的文件名
生成MBR后,将其写入到硬盘的首扇区。
创建一个虚拟磁盘–
qemu-img create filename [size]
- qemu-img create hd.img 10m
将MBR写入
hd.img
的首扇区–使用dd
命令dd if=mbr.bin of=hd.img bs=512 count=1 seek=0 conv=notrunc
if
表示输入文件。of
表示输出文件。bs
表示块大小,以字节表示。count
表示写入的块数目。seek
表示越过输出文件中多少块之后再写入。conv=notrunc
表示不截断输出文件,如果不加上这个参数,那么硬盘在写入后多余部份会被截断。
启动qemu来模拟计算机启动:
qemu-system-i386 -hda hd.img -serial null -parallel stdio
-hda hd.img
表示将文件hd.img
作为第0号磁盘映像。-serial dev
表示重定向虚拟串口到空设备中。-parallel stdio
表示重定向虚拟并口到主机标准输入输出设备中。- GDB是什么? - C语言中文网 更多参数详见文档
debug
使用gdb来配合qemu来进行debug,需要在qemu的启动命令中加入 -s -S
参数,举例:
1 |
|
在另一个终端进入gdb,让gdb连接上qemu:target remote:1234
本次实验会用到的指令:
gdb指令 | 含义 |
---|---|
b *address | 在内存地址address中设置断点 |
r | 运行程序 |
c | 继续运行 |
p *addr | 打印地址的值 |
info registers | 查看寄存器 |
x/10i $pc | 显示从程序计数器的地址开始的10条汇编指令 |
set disassembly-flavor intel | 设置gdb反汇编的语法为intel风格 |
任务1 MBR
1.1 复现example1
done!
1.2 修改example1的代码
修改Example 1的代码,使得MBR被加载到0x7C00后在(12,12)处开始输出你的学号。注意,你的学号显示的前景色和背景色必须和教程中不同。说说你是怎么做的,并将结果截图。
编译汇编文件:nasm -f bin mbr.asm -o mbr2.bin
创建虚拟磁盘: qemu-img create hd.img 10m
将MBR写入hd.img的首扇区: dd if=mbr2.bin of=hd.img bs=512 count=1 seek=0 conv=notrunc
启动qemu:
1 |
|
结果:
任务2 实模式中断
资料:
AH | 功 能 | 调用参数 | 返回参数 / 注释 |
---|---|---|---|
1 | 置光标类型 | (CH)0―3 = 光标开始行 (CL)0―3 = 光标结束行 | |
2 | 置光标位置 | BH = 页号 DH = 行 DL = 列 | |
3 | 读光标位置 | BH = 页号 | CH = 光标开始行 CL = 光标结束行 DH = 行 DL = 列 |
4 | 读光笔位置 | AH=0 光笔未触发 =1 光笔触发 CH=象素行 BX=象素列 DH=字符行 DL=字符列 | |
5 | 显示页 | AL = 显示页号 | |
6 | 屏幕初始化或上卷 | AL = 上卷行数 AL =0全屏幕为空白 BH = 卷入行属性 CH = 左上角行号 CL = 左上角列号 DH = 右下角行号 DL = 右下角列号 | |
7 | 屏幕初始化或下卷 | AL = 下卷行数 AL = 0全屏幕为空白 BH = 卷入行属性 CH = 左上角行号 CL = 左上角列号 DH = 右下角行号 DL = 右下角列号 | |
8 | 读光标位置的属性和字符 | BH = 显示页 | AH = 属性 AL = 字符 |
9 | 在光标位置显示字符及其属性 | BH = 显示页 AL = 字符 BL = 属性 CX = 字符重复次数 | |
A | 在光标位置只显示字符 | BH = 显示页 AL = 字符 CX = 字符重复次数 | |
E | 显示字符(光标前移) | AL = 字符 BL = 前景色 | 光标跟随字符移动 |
13 | 显示字符串 | ES:BP = 串地址 CX = 串长度 DH, DL = 起始行列 BH = 页号 AL = 0,BL = 属性 串:Char,char,……,char AL = 1,BL = 属性 串:Char,char,……,char AL = 2 串:Char,attr,……,char,attr AL = 3 串:Char,attr,……,char,attr | 光标返回起始位置 光标跟随移动 光标返回起始位置 光标跟随串移动 |
应用举例:汇编语言——一些中断的调用 - b1ing丶 - 博客园
本任务会用到的:
功能 | 功能号 | 参数 | 返回值 |
---|---|---|---|
设置光标位置 | AH=02H | BH=页码,DH=行,DL=列 | 无 |
获取光标位置和形状 | AH=03H | BX=页码 | AX=0,CH=行扫描开始,CL=行扫描结束,DH=行,DL=列 |
在当前光标位置写字符和属性 | AH=09H | AL=字符,BH=页码,BL=颜色,CX=输出字符的个数 | 无 |
中断的调用方式:
1 |
|
2.1
请探索实模式下的光标中断int 10h
,实现将光标移动至(8,8),获取并输出光标的位置。说说你是怎么做的,并将结果截图。
做法:
利用任务1中打印到输出端的方法,先将段寄存器初始化为0,以及栈顶指向显示矩阵的首地址。
设置光标的位置:ah=2
1
2
3
4
5mov ah,2 ;设置光标位置
mov bh,0 ;page set to be 0
mov dh,8 ;row
mov dl,8 ;col
int 10h获取光标的位置:ah=3
1
2
3mov ah,3 ;获取光标位置和形状
mov bx,0
int 10h再将坐标字符送到显示矩阵中存储:要注意这里的需要将二进制转换为ASCII字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16mov al, '('
mov [gs:2*(80*8+8)],ax
mov al,dh
add al,'0' ;将二进制转换为ASCII字符
mov [gs:2*(80*8+9)],ax
mov al,','
mov [gs:2*(80*8+10)],ax
mov al,dl
add al,'0'
mov [gs:2*(80*8+11)],ax
mov al,')'
mov [gs:2*(80*8+12)],axending
1
2
3jmp $
times 510-($-$$) db 0
db 0x55,0xaa
结果:
2.2
利用实模式下的中断,从(8,8)开始输出你的学号。说说你是怎么做的,并将结果截图。
光标移动到(8,8),复用2.1任务中的指令
在光标位置输出学号,需要利用光标位置来计算显存矩阵位置的偏移量,然后循环打印学号即可,过程中要记得将地址
+2
1 |
|
光标移动到(8,8),并从此开始输出我的学号。
- 结果:
2.3
参考《汇编语言》第17章、键盘I/O中断调用。关于键盘扫描码,可以参考键盘扫描码表。
在2.1和2.2的知识的基础上,探索实模式下的键盘中断int 16h
,利用键盘中断,实现任意 键盘输入并回显 的效果。说说你是怎么做的,并将结果截图。
INT 16H(键盘I/O中断)
AH=0:从键盘读入ASCII字符,放在AL中。
AH=1:测试有无键被按下。ZF=0,表示按过任意键,并在AL中获得该键的ASCII码。ZF=1,未按过键。
AH=2:读取特殊功能键的状态至AL中。
D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
---|---|---|---|---|---|---|---|
Ins | CapsLock | NumLock | ScrollLock | Alt | Ctrl | 左Shift | 右Shift |
过程:
为了在显存输出,也是类似地进行初始化。
会使用到int 16h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23org 0x7c00
[bits 16]
xor ax, ax ; 清零 ax
mov ds, ax ; 初始化段寄存器
mov es, ax
mov ss, ax
mov sp, 0x7c00 ; 设置栈指针
mov ax, 0xb800 ; 设置显存段地址
mov gs, ax
mov ah, 0x00 ; 从键盘读取一个字符
int 0x16 ; AL = ASCII 字符,AH = 扫描码
mov ah, 0xF2 ; 设置字符属性--颜色
mov [gs:0], ax ; 将字符和属性写入显存
jmp $ ; 无限循环
times 510-($-$$) db 0 ; 填充剩余空间
db 0x55, 0xaa ; 引导扇区结束标志
结果:
任务3 汇编
要求:
- 任务3使用的是32位寄存器
a1
、if_flag
、my_random
等都是预先定义好的变量和函数,直接使用即可。- 需要补全的代码文件在
assignment/student.asm
中。 - 代码编写好之后使用
make run
来测试代码 - 你可以修改
test.cpp
中的student_setting
中的语句来得到你想要的a1,a2
。 - 最后附上结果截图
3.1 分支逻辑的实现
请将下列伪代码转换成汇编代码,并放置在标号 your_if
之后。
1 |
|
条件分支
- 利用跳转指令、跳跃指令、条件跳转指令
1 |
|
- 条件跳转指令:
1 |
|
- 用于算术运算:
指令 | 描述 | 标志测试 |
---|---|---|
JE/JZ | 跳转等于或跳转零 | ZF |
JNE/JNZ | 跳转不等于或跳转不为零 | ZF |
JG/JNLE | 跳转大于或跳转不小于/等于 | OF, SF, ZF |
JGE/JNL | 跳转大于/等于或不小于跳转 | OF, SF |
JL/JNGE | 跳转小于或不大于/等于 | OF, SF |
JLE/JNG | 跳少/等于或跳不大于 | OF, SF, ZF |
- 逻辑运算:
指令 | 描述 | 标志测试 |
---|---|---|
JE/JZ | 跳转等于或跳转零 | ZF |
JNE/JNZ | 跳转不等于或跳转不为零 | ZF |
JA/JNBE | 跳转向上或不低于/等于 | CF, ZF |
JAE/JNB | 高于/等于或不低于 | CF |
JB/JNAE | 跳到以下或跳到不高于/等于 | CF |
JBE/JNA | 跳到下面/等于或不跳到上方 | AF, CF |
结果代码:
1 |
|
只要搞清楚如何分析分支跳转就可以了!
div: 只有一个操作数,计算后商放在eax寄存器中,而余数则放在edx寄存器中
3.2 循环逻辑的实现
请将下列伪代码转换成汇编代码,并放置在标号 your_while
之后。
1 |
|
- 无条件循环指令
1 |
|
- 条件循环指令
1 |
|
结果:(更新)
1 |
|
重点:
不可以把a2的值读取到eax中
要记得把地址 *4 ,因为存储的是字节
3.3 函数的实现
请编写函数 your_function
并调用之,函数的内容是遍历字符数组 string
。
1 |
|
结果:(更新–旧版本没意识到出错了……)
1 |
|
重点:
- 在 32 位汇编中,栈的操作单位是 4 字节(32 位)。无论是压栈(
push
)还是弹栈(pop
),都是以 4 字节为单位进行的。 - 函数要记得返回
make run结果:
任务 4 汇编小程序(选做)
字符弹射程序。请编写一个字符弹射程序,其从点(2,0)处开始向右下角45度开始射出,遇到边界反弹,反弹后按45度角射出,方向视反弹位置而定。同时,你可以加入一些其他效果,如变色,双向射出等。注意,你的程序应该不超过510字节,否则无法放入MBR中被加载执行。静态示例效果如下,动态效果见视频assignment/assignment-4-example.mp4
。
实现思路:
设置显示字符的行、列寄存器(ch-行,cl-列),然后设置两个寄存器分别用于做水平方向和竖直方向的位移(最初设置为1,1是因为我们要从(2,0)点出发往右下方45°移动
设置一个专门进行弹跳的循环
- 判断是否到达边界,如果到达边界需要反转对应方向的增量(其实还可以直接使用neg反转)
- 边界调整完后进入展示循环
展示循环
- 需要计算当前字符需要被放置到的显存矩阵的位置(这里尤其要注意,我们原本放置的寄存器是8位寄存器,但是bx是16位寄存器,需要进行0扩展)
- 将字符和颜色传入显存矩阵
- 修改字符和颜色(简单地通过+1来实现)
- 调用延时(由于我们把延时设置为一个标签了,这时候要保存原来的值后跳转出去需要借用到栈,在调用完之后返回再弹出栈)
实验代码:
1 |
|
本任务感谢隔壁舍友L同学的鼎力支持!从她的代码中学习到了不同位数寄存器要使用movzx
存储,同时使用int 15h
的0x86功能来产生延时 等知识。
此处同时贴上相关的知识点:
功能86H
功能描述:延迟
入口参数:AH=86H
CX:DX=微秒
出口参数:CF=0——操作成功,AH=00H
- 总微秒数 = CX * 65536 + DX
- 例如,如果CX = 0001h,DX = 86A0h,则延时为: 0001h * 65536 + 86A0h = 65536 + 34464 = 100,000微秒 = 100毫秒
延时时间 | CX值 | DX值 | 16进制总微秒 |
---|---|---|---|
10毫秒 | 0000h | 2710h | 0000’2710h |
50毫秒 | 0000h | C350h | 0000’C350h |
100毫秒 | 0001h | 86A0h | 0001’86A0h |
250毫秒 | 0003h | D090h | 0003’D090h |
500毫秒 | 0007h | A120h | 0007’A120h |
1秒 | 000Fh | 4240h | 000F’4240h |
5秒 | 004Ch | 4B40h | 004C’4B40h |
代码示例:
1 |
|
movzx
理解MOVZX指令:汇编语言数据传送的无符号扩展-CSDN博客
用处是将数据从源操作数复制到目的操作数,并使用零扩展将剩余高位填充为0.
movzx destination, source
代码中奖ch移动到bx,将一个8位的寄存器复制到16位寄存器并进行了零扩展。
类似:movsx(复制并进行符号扩展)