汇编语言基础(一)

提示:本篇文章主要是记录8086CPU


一、汇编语言的发展史

最初是由机器语言0和1组成,计算机只识别0和1,即是高电频率和低电频,程序员开发程序需要写一堆0和1,后来为了解决这个问题于是出现了汇编语言,用符号代替了0和1,比机器语言便于阅读和记忆.
在到了后面就出现了高级语言,因为汇编语言还是不容易懂,于是高级语言就诞生了,更接近人类自然语言,例如C/C++/Java/OC/Swift

例如: 将寄存器BX的内容送入寄存器AX
机器语言: 1000100111011000
汇编语言:mov ax, bx
高级语言:  ax=bx

二、汇编语言与高级语言和机器语言的关系

在这里插入图片描述

  • 汇编语言机器语言一一对应,每一条汇编语言都有对应的机器语言
  • 汇编语言可以通过编译得到机器语言机器语言可以通过反编译得到汇编语言
  • 高级语言可以通过编译得到汇编语言/机器语言,但是汇编语言/机器语言不能反编译成高级语言

三、汇编语言的特点

  • 可以直接访问、控制各种硬件设备,比如存储器、CPU等,能最大限度地发挥硬件的功能
  • 汇编指令是机器指令的助记符,同机器指令一一对应。每一种CPU都有自己的机器指令集\汇编指令集,所以汇编语言不具备可移植性
  • 知识点过多,开发者需要对CPU等硬件结构有所了解,不易于编写、调试、维护
  • 不区分大小写,比如mov和MOV是一样的

四、汇编语言的种类

目前讨论比较多的汇编语言有
8086汇编(8086处理器是16bit的CPU)
Win32汇编
Win64汇编
AT&T汇编(Mac、iOS模拟器)
ARM汇编(嵌入式、iOS设备)

五、软件程序执行过程

在这里插入图片描述
1.程序安装以后,就会安装到硬盘中
2.用户启动程序,就会装载到内存中,
3.然后由CPU读取内存,并且执行,CPU同时还可以把执行结果存储到内存中
4.当CPU读取内存中程序时候会根据指令去控制计算的硬件,例如显示器,音响等

六、CPU总线

在这里插入图片描述
每一个CPU芯片都有许多管脚,这些管脚和总线相连,CPU通过总线跟外部器件进行交互
CPU总线分为3大类

  • 地址总线
  • 数据总线
  • 控制总线

例如:CPU从内存的3号单元读取数据

在这里插入图片描述

  1. CPU通过地址总线将3传递给内存,内存会根据传递过来的地址3找到里面的内容是08
  2. 控制总线会告诉内容CPU是要读还是要写,例如是读取
  3. 内存会把08通过数据总线返回给CPU,这样CPU就能读取到内存数据了

(一)地址总线

它的宽度决定了CPU的寻址能力
8086的地址总线宽度是20,所以寻址能力是1M( 2^20 )

(二)数据总线

它的宽度决定了CPU的单次数据传送量,也就是数据传送速度
8086的数据总线宽度是16,所以单次最大传递2个字节的数据
1个字节是8位

(三)控制总线

它的宽度决定了CPU对其他器件的控制能力、能有多少种控制

七、内存

在这里插入图片描述

CPU寻址能力

8086CPU有20根地址总线,按理来说它的寻址能力应该是220 (1M),但是它只能送出216 (64KB)的数据,这不就浪费4根导线吗?于是8086CPU是这么解决的,
8086采用一种在内部用2个16位地址合成的方法来生成1个20位的物理地址:
在内部,段地址 x 16(16换算成16进制就是10H)+ 偏移地址,得出物理地址
相关部件提供段地址和便宜地址,,段地址x16,其实也就是相当于16进制的10H
例如:
段地址是: 2000H
偏移地址是: 1F60H
物理地址是: 2000H *10H = 20000H + 1F60H = 21F60H

八、CPU典型构成

在这里插入图片描述

(一)寄存器

在这里插入图片描述

寄存器总体来说是分1.通用寄存器; 2.控制寄存器; 3.段寄存器

1. 通用寄存器

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 段寄存器

  • 8086在访问内存时要由相关部件提供内存单元的段地址和偏移地址,送入地址加法器合成物理地址
  • 8086有4个段寄存器:CS、DS、SS、ES,
  • 当CPU需要访问内存时由这4个段寄存器提供内存单元的段地址:
    CS (Code Segment):代码段寄存器
    DS (Data Segment):数据段寄存器
    SS (Stack Segment):堆栈段寄存器
    ES (Extra Segment):附加段寄存器
(1)CS寄存器和IP寄存器

CS为代码段寄存器,IP为指令指针寄存器,它们指示了CPU当前要读取指令的地址
任意时刻,8086CPU都会将CS:IP指向的指令作为下一条需要取出执行的指令
CS是段寄存器,提供段地址,
IP可以理解成,偏移地址
CPU通过CS:IP(CS16+IP)就可以得到物理地址,从而可以找到内存中的数据
在这里插入图片描述
例如: CPU想着读取内存中mov ax, 0123H这一段代码,它会这样工作:
1.CS段寄存器会提供一个段地址:2000,IP指令寄存器会指向0000,然后把段地址和偏移地址传递给地址加法器
2.地址加法器会进行计算2000
16(16是16进制,其实也就是*10)+0000,得到物理地址20000
3.CPU知道物理地址以后,通过地址总线在内存中找20000地址,于是便能得到B8,23,01
4.CPU会通过数据总线将B8,23,01传回来放到指令缓冲区,这个时候IP会继续执行指向0003,因为指令缓冲区取回来的是3个字节,所以IP会指向0003
5.然后执行控制器才会执行指令缓冲器的数据B8,23,01,从而将结果放到AX通用寄存器中
6.这个时候下一条指令已经计算好了物理地址是20003H,通过地址总线开始在内存中找了,就会找到BB,03,00
7.CPU会通过数据总线把BB,03,00传递到缓冲器,这个时候IP就会变成0005,然后控制器在执行,以此类推,不断执行.
在这里插入图片描述

注意 :
1.为什么当CPU找到20000H以后,知道读取3个字节,读到20006H的时候读取2个字节??
这是因为B8,BB,89,01这些特殊的原因,CPU一看到他们就知道该读取几个字节了
2.内存中(20006H~20007H),中89D8,内存是不知道它是代码,还是一个整形数字,取决于你用的是哪个寄存器,如果你是用CS寄存器提供的段地址,那它就是一段代码(mov ax, bx),如果你是用DS寄存器提供的段地址,那就是一个整形数字(89D8)

CPU在执行哪一条指令,主要就看CS:IP

(2)jmp指令

既然CS:IP是决定CPU执行哪一条代码,那么我们可以改变CS:IP来实现让CPU改变执行顺序。
按理说应该这样改变:
mov CS,20000H
mov IP, 0003H
mov虽然强大,但是不能改变CS,IP寄存器
想要修改必须通过jmp指令来修改:
例如:
在这里插入图片描述
jmp还可以仅修改IP寄存器
jmp 0003H,就把IP修改成0003H了,还可以把0003H放AX通用寄存器中,jmp ax, ax如果是0003H,那IP就是0003H,如果是0005H,那IP就是0005H

总结:jmp的两中用法

  1. jmp cs:ip (修改了cs寄存器和ip寄存器)
  2. jmp ip (仅仅修改了ip寄存器)
(3)DS寄存器和[address]

CPU要读写一个内存单元时,必须要先给出这个内存单元的地址,在8086中内存地址由段地址和便宜地址组成!!!
8086中有一个DS段寄存器,通常用来存放访问数据的段地址
DS寄存器,不支持,直接赋值,必须通过中转赋值!

例如: 
CPU想着读取内存中10000H的数据,放到al寄存器中
mov ds, 1000H   // 错误,不能直接赋值
mov  al, [0]
以上是不对的,DS寄存器,不支持,直接赋值,必须通过中转

mov  bx, 1000H
mov ds, bx
mov al, [0]

只要看到[address],其实就是相当于ds:address
mov al, [0]
就相当于把 ds:address(1000H:0H),物理地址就是10000H,中的数据放到了al寄存器中

练习:
写几条指令,将al数据放到内存单元为10000H中
mov bx, 1000H
mov ds, bx
mov [0], al  
//其实最后一句应该这样写 mov 1000H:[0], al  只不过  mov [0], al  是简写

注意补充一点:
mov ax, 1000H
mov ds, ax
mov [0],66H
代码的意思是将 66H放到内存地址为10000H中,但是66H是放成(66H还是0066H),
准确点应该是 
mov word prt [0], 66H    //word是2个字节,内存中会存成66H(10000H),00H(10001H),会覆盖原来10001H中的数据
mov byte prt [0], 66H     //byte是1个字节,内存就会放成66H,下一个内存单元(10001H)不会出现00,不会覆盖原有的数据
mov [0], ax  //要是这么写,肯定是2个字节,因为ax就是2个字节 就不需要加word或者byte了
mov [0], al   //这肯定是1个字节 就不需要加word或者byte了

当从内存单元读取数据的时候,注意寄存器是读取的2个字节还是1个字节,例如ax计算器是2个字节,al或者ah是1个字节.看图
在这里插入图片描述
mov ax, [0] 是从10000H中读取的是23H,但是ax是2个字节16位,所以会自动往高地址走1个字节
所以ax的值就是1123H,不是2311H,
1123H,高字节是11H,低字节是23H,高字节放到高地址(10001H),低字节放到低地址(10000H)中
为什么这么放,因为8086是小端模式。。

(4)大小端
  • 大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中(高低\低高)
  • 小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中(高高\低低)

在这里插入图片描述

总结: 内存地址从底到高排列的时候: 小端模式就倒着放。大端模式就按照顺序放就行。

(5)SS栈寄存器

在这里插入图片描述
可以这么理解: 栈就是一个盒子,一开始指针SP会指向盒子的最底部,push一条数据以后,这一条数据肯定是2个字节,SP指针会上移动2个字节,越往上,内存地址是越小的.
pop一条数据,是从上面取出一个数据,SP指针往下移动,越往下内存地址是越大

① push ax (入栈)

在这里插入图片描述

② pop ax (出栈)

在这里插入图片描述

③ push- pop

在这里插入图片描述

如果1000H~1000FH当做栈空间,并且是空的,那么指针SP必须指在1000FH在下面一个位置,1000FH+10H=10010H中

将10000H~1000FH当做栈空间,初始状态栈是空的; 
mov ax , 1000H
mov ss , ax
mov sp , 0010H

设置AX=00AH , BX=001BH
mov ax, 00AH
mov bx, 001BH

利用栈交换AX和BX中的数据
push ax
push bx

pop ax
pop bx

想想: 先是放入ax,在放bx,然后取出bx数据给ax,取出ax数据给了bx,完成了交换

(二)运算器

运算器目前不涉及

(三)控制器

主要是控制硬件(显示器,鼠标,键盘)目前也不涉及

九、 小练习,输出Hello Word!

打印字符串本质,其实就是将字符串存储到内存中,然后在通过指令找到内存中的字符串,打印出来

assume cs:code

code segment
start:

	 mov ax,4c00h
	 int  21h
code end

end start

--------------------------------代码含义说明-------------------------------------
assume cs:code : //提醒开发者每个段的含义,其实没什么作用,为了规范提供代码可读性能,进行书写

这是一对,开始结束
code segment
code end  

//这是CPU执行代码的入口,通过start CPU就能自动确认CS段地址是多少,不用专门去找段地址了
start:
end start 

//代表程序退出int代表interrupt,中断的意思,int 21h代表执行的是DOS系统功能调用
mov ax,4c00h
int  21h

// 全局变量设置
db 10 dup(6) //生成10个6
//同时也可以给变量起个名字
age db 20h     //db是一个字节
no dw 30h      //dw是2个字节
assume cs:code, ds:data     //提醒开发者每个段的含义,其实没什么作用,为了规范提供代码可读性能,进行书写
;数据段
data segment
   db 'Hello Word!$'   //$是结束语, db相当于全局变量,db是一个字节 ,dw是2个字节
data ends

;代码段
code segment
start:
	mov ax, data
	mov ds, ax           //这2句是让上面的data数据段 与现在的代码段产生关联
	
	mov dx, 0h         // 设置偏移地址是0h,,打印的话必须找到地址 ds:[address]  偏移地址
	
	mov ah, 9h        //  打印指令 (也就是数据开始从ds:[address]开始打印,直到遇到$就停止打印)
	int   21h         // 退出指令
     ;退出
     mov ax, 4c00h
     int 21h
code ends
end start

---------------------------------------程序改造偏移地址------------------------------------
在数据段时候如果加上其它几行代码
age db 20h
no dw 30h
db 10 dup(6)
db 'Hello Word!$'
此时,你的偏移地址需要设置多少??,肯定不是偏移0了
通过数,db 20h是1个字节,dw 30h是2个字节, db 10dup(6)是10个字节
应该偏移13个字节,即
mov dx 13
问题是不能一个一个的数,可以通过偏移量来实现,
给db 'Hello Word!$'一个变量string
age db 20h
no dw 30h
db 10 dup(6)
string db 'Hello Word!$'
代码段这样写
mov dx offset string



assume cs:code, ds:data, ss:stack
; 栈段
stack segment
	db 10 dup(8)           //给栈开辟10个字节,这一段设置成栈,里面放了10个8,等着去覆盖
stack ends
;数据段
data segment
	db 20 dup(9)        //在数据段 放了20个9等着去覆盖
data ends
;代码段
code segment
start:
    ;手动设置ss和ds
    mov ax, stack
    mov ss, ax
    mov ax, data
    mov ds, ax
    ;使用栈
    mov sp, 10
    push ax
    push bx
    pop ax
    pop bx
code ends
end start

 mov [0], ax  //只要是这么写了,就肯定放到数据段第一个,前提是上面设置了数据段
 注意:数据段是20最多,便宜地址不能超过20
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据你提供的引用内容,以下是一个使用汇编语言打印杨辉三角的示例代码: ```assembly section .data triangle db 10 dup(0) ; 存储杨辉三角的数组 section .text global _start _start: mov ecx, 10 ; 设置行数为10 mov ebx, 1 ; 初始化第一行的值为1 ; 打印杨辉三角 print_triangle: mov edi, 0 ; 清空数组索引 mov [triangle + edi], ebx ; 将当前行的第一个元素设置为1 inc edi ; 增加数组索引 ; 计算当前行的其他元素 calc_row: mov eax, [triangle + edi - 1] ; 获取上一行的当前元素 add eax, [triangle + edi] ; 加上上一行的下一个元素 mov [triangle + edi], eax ; 将计算结果存入当前行的当前元素 inc edi ; 增加数组索引 loop calc_row ; 继续计算下一个元素 ; 打印当前行的元素 mov edi, 0 ; 清空数组索引 print_row: mov eax, [triangle + edi] ; 获取当前行的当前元素 call print_number ; 调用打印数字的函数 inc edi ; 增加数组索引 cmp edi, ecx ; 比较数组索引和行数 jle print_row ; 如果数组索引小于等于行数,则继续打印 ; 打印换行符 mov eax, 0x0A ; 换行符的ASCII码 mov ebx, 1 ; 文件描述符为1(标准输出) mov ecx, esp ; 将换行符的地址存入ecx寄存器 mov edx, 1 ; 换行符的长度为1 int 0x80 ; 调用系统调用打印换行符 loop print_triangle ; 继续打印下一行 ; 退出程序 mov eax, 1 ; 系统调用号为1(退出程序) xor ebx, ebx ; 返回值为0 int 0x80 ; 调用系统调用退出程序 ; 打印数字的函数 print_number: pusha ; 保存寄存器的值 ; 将数字转换为字符串 mov edi, 10 ; 除数为10 xor ecx, ecx ; 清空ecx寄存器 mov ebx, 10 ; 循环10次 convert_number: xor edx, edx ; 清空edx寄存器 div edi ; 除法运算,商存入eax,余数存入edx add dl, '0' ; 将余数转换为ASCII码 push edx ; 将余数压入栈中 inc ecx ; 增加计数器 loop convert_number ; 继续循环 ; 打印字符串 print_string: pop edx ; 弹出栈顶元素(余数) mov eax, 4 ; 系统调用号为4(写文件) mov ebx, 1 ; 文件描述符为1(标准输出) mov ecx, edx ; 将余数存入ecx寄存器 mov edx, 1 ; 字符串的长度为1 int 0x80 ; 调用系统调用打印字符 loop print_string ; 继续打印下一个字符 popa ; 恢复寄存器的值 ret ; 返回 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值