指令和运算 – 指令
-
CPU最底层的运算单元 – 计算机指令集, instruction set, 亦是机器语言
-
程序 – 也是数据 – 位于存储器 ( 存储程序型计算机)
-
程序如何成为计算机可以识别并且进行执行的代码?代码 – 经过编译 compile --> 汇编语言 ASM Assembly Language-- 经过汇编器 Assembler --> 机器语言 machine language
-
Linux c,c程序,先转变为汇编语言,再转变为机器语言
gcc -g -c test.c
objdump -d -M intel -S test.o
指令的类型
- 算术类指令
- 数据传输类指令
- 逻辑类指令
- 条件分支类指令
- 无条件跳转指令
MIPS 指令集
- 一个指令最底层是如何表达的? 相当于是一串二进制码,里面有些位数代表指令的类型,有些位数代表指令具体的执行逻辑,操作符,操作数之类。
- MIPS 的指令是一个 32 位的整数,高 6 位叫操作码(Opcode),也就是代表这条指令具体是一条什么样的指令,剩下的 26 位有三种格式,分别是 R、I 和 J。
- R 指令是一般用来做算术和逻辑操作
I 指令,则通常是用在数据传输、条件分支
J 指令就是一个跳转指令
CPU如何执行指令
- CPU = 一堆寄存器 ; 寄存器 – 触发器(Flip-Flop)或者锁存器(Latches)
- N 个触发器或者锁存器,就可以组成一个 N 位(Bit)的寄存器,能够保存 N 位的数据
寄存器类型
- PC 寄存器(Program Counter Register),我们也叫指令地址寄存器(Instruction Address Register)。
- 指令寄存器(Instruction Register),用来存放当前正在执行的指令。
- 条件码寄存器(Status Register),比如溢出,进位这些标识符
- 整数寄存器、浮点数寄存器、向量寄存器和地址寄存器等等。有些寄存器既可以存放数据,又能存放地址,我们就叫它通用寄存器。
if…else – 示例,具体的c程序转化成汇编码
if (r == 0)
3b: 83 7d fc 00 cmp DWORD PTR [rbp-0x4],0x0
3f: 75 09 jne 4a <main+0x4a>
{
a = 1;
41: c7 45 f8 01 00 00 00 mov DWORD PTR [rbp-0x8],0x1
48: eb 07 jmp 51 <main+0x51>
}
else
{
a = 2;
4a: c7 45 f8 02 00 00 00 mov DWORD PTR [rbp-0x8],0x2
51: b8 00 00 00 00 mov eax,0x0
}
除了简单地通过 PC 寄存器自增的方式顺序执行外,条件码寄存器会记录下当前执行指令的条件判断状态,然后通过跳转指令读取对应的条件码,修改 PC 寄存器内的下一条指令的地址,最终实现 if…else 以及 for/while 这样的程序控制流程
关于函数 – CPU如何执行相关指令
- add 函数编译之后,代码先执行了一条 push 指令和一条 mov 指令
- 在函数执行结束的时候,又执行了一条 pop 和一条 ret 指令
- 这四条指令的执行,压栈(Push)和出栈(Pop)操作。
- 整个函数 A 所占用的所有内存空间,就是函数 A 的栈帧(Stack Frame)
- rbp 是 register base pointer 栈基址寄存器(栈帧指针),指向当前栈帧的栈底地址
- rsp 是 register stack pointer 栈顶寄存器(栈指针),指向栈顶元素
编译、链接和装载:拆解程序执行
add_lib.o 以及 link_example.o 目标文件 – 通过链接器(Linker)把多个目标文件以及调用的各种函数库链接起来 – 可执行文件(Executable Program)
- 第一个部分由编译(Compile)、汇编(Assemble)以及链接(Link)三个阶段组成。 --> 一个可执行文件。
- 第二部分,我们通过装载器(Loader)把可执行文件装载(Load)到内存中
ELF(Execuatable and Linkable File Format),可执行与可链接文件格式
ELF 文件格式把各种信息,分成一个一个的 Section 保存起来。
- text Section,也叫作代码段或者指令段(Code Section)
- data Section,也叫作数据段(Data Section)
- .rel.text Secion,叫作重定位表(Relocation Table)
- .symtab Section,叫作符号表(Symbol Table)
程序装载到内存
实际的可执行文件:ELF文件,和PE文件
- 在内存里,找到一段连续的内存空间,然后分配给装载的程序,然后把这段连续的内存空间地址,和整个程序指令里指定的内存地址做一个映射。
- 指令里用到的内存地址叫作虚拟内存地址(Virtual Memory Address),实际在内存硬件里面的空间地址,我们叫物理内存地址(Physical Memory Address)
- 分段(Segmentation),系统分配出来的那个连续的内存空间
- 内存碎片(Memory Fragmentation)
- 内存交换(Memory Swapping),通过和硬盘进行IO,倒腾出位置,解决内存碎片
- 内存分页:分页是把整个物理内存空间切成一段段固定尺寸的大小。 进行程序加载,内存交换的最小单元,这样就可以大大减少IO的数据量,减少因为程序交换停顿的时间。
- 缺页错误(Page Fault):当要读取特定的页,却发现数据并没有加载到物理内存里的时候
动态链接
- 期望:同样功能的代码,在不同的程序里面,不需要各占一份内存空间
- 共享库:Windows 下,.dll 文件, Dynamic-Link Libary(DLL,动态链接库)。Linux 下,.so 文件,Shared Object(一般我们也称之为动态链接库)
- PLT,程序链接表(Procedure Link Table)
- 全局偏移表(GOT,Global Offset Table)