.net core底层入门学习笔记(三-汇编)

.net core底层入门学习笔记(三)

<font color=#999AAA 第三篇主要讲X86汇编相关



前言

机器码是计算机可以直接执行的代码,.net通过IL语言在不同平台上,生成不同的机器码实现跨平台开发。机器码的表现形式,与书写使用的是汇编语言,学习汇编有助于理解.net程序的一些执行原理与内部机制。.net也是站在汇编,操作系统,cpu架构等基础上的。


一、汇编的理解

首先要理解最简单的计算机架构:CPU,内存,寄存器。有了这三者,才能进行数据的基础计算,存取,修改等操作。机器码通过一系列的指令,操作内存,计算器,CPU进行各种基础的数据计算,存取等工作。把数据与机器码同时会保存在内存中,称为冯诺依曼架构,这样的架构可以适应各种变化的需求。

1.机器码的格式

  • 机器码在计算机中也表现为一组01组成的数值,保存在内存中
  • 机器码按照特定格式存储与读取,其中1到多个数值代表1条指令,1条指令是CPU执行操作的最小单元(其实还可以细分),而这套计算机架构能够实现的所有指令的集合,就是人们口中常说的指令集。
  • 机器码格式的约定(有点类似IL指令,应该是IL指令类似CPU指令):指令类型 操作目标 操作来源。
  • 指令按功能分的大致类型:1.在内存,寄存器,相互之间移动数值;2.从外部端口读取数值,输出数值到外部端口;3.跳转读取的位置;4.数值计算(加减乘除等);5.比较数值,并保存标记。
    为了能够实现上述功能,需要一个特别的寄存器:计数寄存器,计数寄存器指向CPU要读取的下一条指令所在的内存位置

指令集的优化,两个方向:精简指令集(RISC),复杂指令集(CISC),一个是提升单条指令执行时间(让单条做更少的事情),一个是提升单条指令的功能性(让其能做的事情更多)。

流水线工作方式:CPU内部工作步骤:读取指令,解码指令,执行指令,访问内存,写入寄存器。为了提升性能,采用流水线工作方式,即不等一套指令执行完,只要某个步骤执行完,这个步骤相关的工作单元马上执行下一条指令的对应步骤。
流水线问题:跳转指令,无法直接获取下一条指令,这样就会断开流水线作业,解决办法:分支预测机制。

2.内存

  • 内存中每个单元存储一个数值(0或1),称为记忆单元。1个记忆单元称为1Bit,8个记忆单元称为1Byte,1024Byte称为1M…
    特别的16个记忆单元称为双字节,32个四字节。存储值的范围区分有符号与无符号数,虽然存储的数值范围的个数相同,但是范围不同。如8个记忆单元,无符号范围:0-255,有符号范围:-128-127.
    16进制的1个数,需要4个记忆单元,一个字节可以表达两个16机制的数
  • 既然内存中,有符号与无符号数,可能存储的内容一样,那么怎么区分呢?CPU使用一种二补数的方式,使得加减时不需要区分,通过二补数的形式,-3+1 = -1 与 253+1 =254,从内存中表现是一致的。
  • 大端与小端:高位与地位的数值摆放顺序。网络传输统一使用大端法,字节转换时需要注意,.net中BitConverter转换数值为字节数组需要注意。
  • 内存地址:表示从内存中第几个字节读取或者写入。内存地址本身也是数值,可以保存在内存或者寄存器中,区分32位与64位地址,这个决定了内存最大的容量,如果是32位表示地址,那么最大表示的值为512G。一个数值是当数据,还是当内存地址,需要指令码来明确指定。
  • 虚拟内存:CPU对程序的内存地址,与实际地址做映射
  • 分段与分页:分段:利用寄存器作基础地址,再根据指令中的地址做偏移得到真实物理地址;分页:把内存划分固定大小的页,X86-64中每一页为4K,虚拟页与实际页进行映射。

3.寄存器

  • 通用寄存器:由程序决定用途的寄存器,可将位数多的寄存器当作位数少的寄存器使用。约定使用的寄存器:乘法除法:eax,edx;栈操作:esp(这个会在后面介绍比较重要的寄存器);
  • 程序计数器:三类指令与程序计数器相关,1.不修改计数器,算术指令,访问指令,CPU会直接读取下一条指令;2.一定修改计数器,无条件跳转指令,返回指令等;3.可能修改计数器,条件跳转。
  • 标志寄存器:其中有很多定义好的标记位置,也有许多预留位,1.进位标志CF,是否发生进位或借位,寄存器0号记忆单元空值,2.零标志 ZF,结果是否为0,6号记忆单元,3.符号标志SF,计算结果最高位是否为1;4.溢出标志,计算结果是否发生溢出(正溢出,负溢出)
  • 其他寄存器,根据寄存器功能,还有其他很多订好的寄存器,在cpu中起到各种各样的作用。

二、汇编指令

1.汇编指令基础

  • 汇编指令记法:记法可能不同,但是针对机器码是相同的,目前X86的汇编记法主要有两种,一种Intel一种AT&T记法。
  • 汇编指令格式:指令格式 + 参数的方式,参数可能会有多个,表达不同的意思,比如:move reg32 imm32,表示移动(复制)立即数,到寄存器中,即给寄存器赋值。
    需要注意的点:
    1.汇编指令具体的各种指令,比如move,add,sub,mul,imul等基础计算指令,这些指令不仅会涉及到通用寄存器,其中特别是部分指令需要区分有符号计算与无符号计算,所以需要标记寄存器,溢出寄存器等寄存器参与。
    2.有些特殊的计算,不保存结果,比如cmp指令,执行减法但不保存结果,可用于比较大小。指令有长短,一般编译器都会倾向选择较短的指令。

2.基础流程控制

机器码通过跳转指令实现流程控制,跳转指令的作用修改CPU程序计算器,使得下一条执行的指令指向指定的指令地址。其中条件跳转指令,需要两条指令,一条影响标志寄存器,一条根据标志寄存器内容决定是否修改计数器到指定地址的指令。
一般跳转指令,使用相对地址时:在汇编中体现的为一个跳转标签,在机器码中会在编译过程中得知真实的地址,所以机器码中会表现为相对下一条指令的相对偏移来决定跳转到哪条指令地址上,且相对地址可以使用立即数。使用绝对地址时:需要先把地址保存起来(寄存器,或内存),再传给跳转指令。

  • 比较指令:cmp指令,test指令等,
  • 跳转指令:jump,不需要符号跳转(je/jz,jne/jnz),无符号数跳转(jb/jnae/jc,jbe/jna,ja/jnbe,jae/jnb/jnc),有符号数跳转(jl/jnge,jle/jng,jg/jnle,jge/jnl等)
    注意由于CPU不知道数值是否为有符号,所以只有上层程序自己根据操作的数值类型来调用不同的跳转指令

3.其他流程控制

同样的高级语言,经过不同编译器,不同编译选项,会生成不同结构的汇编代码,编译器会根据需要选择性能更好,长度更短,或者易于调试的结构。
for循环:

int a =0;
for(int c =0;c<100;++c)
{
	a+=c;
}

生成的汇编代码:

move eax,0
move ecx,0
L1:
cmp ecx,100
jge L2
add eax,ecx
add ecx,1
jmp L1
L2:
nop

.net编译器倾向生成的汇编:

move eax,0
move ecx,0
jmp L2
L1:
add eax,ecx
add ecx,1
L2:
cmp ecx,100
jge L1
nop

其他循环:while,do while等都与上面类似的结构,只是在执行顺序,跳转方面有所差异。

4.分支预测

现代cpu使用分支预测机制,提升流水线工作效率,它的机制是:如果某个条件跳转指令经常成立,则下一次预测跳转会执行,相同的如果经常不成立,则预测跳转不执行。简单来说,就是遇到条件跳转指令时,不等跳转指令执行完(即前面说的执行一个指令,经过读取指令,解码指令,执行指令,访问内存,写入寄存器),流水线开始工作,就将下一条指令进行读取。如果跳转指令执行完,发现不对,则直接跳转到下条真正的指令进行执行,前面的工作就废掉。

分支预测的实现:饱和计数器(计数器计数大于阈值),两级自适应预测(根据成立与不成立的模式)。
由于CPU记录分支缓冲区有限,未记录的跳转指令预测不成立,部分编译器可通知CPU某个条件性跳转的会大概率成立,用于提升性能。

总结

本篇主要介绍汇编代码的一些基础指令格式,需要注意点,各种计算指令以及对各种寄存器的使用,最后介绍了一点跳转指令如何支撑流程控制,以及条件跳转指令,CPU如果通过分支预测提升流水线工作效率。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值