操作系统 - 系统以下
大约 4 分钟
操作系统 - 系统以下
物理内存 vs 程序员眼中的内存
内存的物理机制
- 内部部分
- 内存集成电路
- 引脚 (接口) 部分
- 有多个电路引脚 (以那种单片机上黑色的22引脚的小内存为例)
- (2) VCC 电源,GND 电源
- (10) A0-A9 地址信号引脚。= 10位 = 1024个地址 = 索引1024Byte的空间
- (8) D0-D7 数据信号引脚。= 8位 = 一字节 = 每个地址可以存储8个字节
- (2) RD WR 控制信号引脚。即读还是写
- 信号引脚中,通过5V高电平,0V低电平,来表示10
- 顺序
- 写则依次:(1) 指定地址、(2) 输入数据、(3) 将WR设为1
- 读则依次:(1) 制定地址、(2) 将RD设为1、(3) 从数据引脚中获取
- 有多个电路引脚 (以那种单片机上黑色的22引脚的小内存为例)
字节序
详见CSAPP或内存管理相关笔记
高级语言、汇编语言、机器语言
略,详见CSAPP
objdump -d -S hello.o // -S可以看对应的C语言语句
不过还是Godbolt香 (戈伯特)
CPU执行过程
物理
集成电路,根据指令处理各种处理的电子电路,每个指令对对应相应的电子电路工作
- 控制器:负责传入寄存器,得到结果后控制计算机
- 寄存器 (20~100个):暂存指令、数据等(PC(计数器)有可能也在这个位置,例如IP就是指令指针寄存器)
- 运算器:运算
流程
- 读取:CPU从内存中读取指令
- 解码:将10串拆成操作码和操作数,知道要去执行什么操作
- 执行
- (循环,根据计数器(PC)循环)
CPU指令集
- arm架构CPU
- X86架构CPU
查看CPU架构
- windows:systeminfo,找 "系统类型" 一栏
- linux:lscpu,找 "Architecture" 一栏
常用指令
详见SCAPP
- mov A,B
- add A,B
- push A
- pop A
- call A
- ret 无
常用操作数
- 寄存器
- rbp (register base pointer):栈基址寄存器(栈帧指针),指向当前帧的栈底地址
- rsp (register stack pointer):栈顶寄存器(栈指针),指向栈顶元素
- (函数调用时会使用上面两个)
- rip:存储下一条指令的内存地址
- 内存
- 常数
程序 - 机器码 的对应
函数调用
int mod(int a, int b) {
return a % b;
}
int add(int a, int b) {
int res = mod(a, b);
return res + b;
}
void main() {
int a = 3;
int b = 2;
int c = add(a, b);
}
函数调用栈
- 函数调用链:main -> add -> mod -> add -> main
- 每个栈帧会包含:
- 参数值
- 局部变量
- 返回地址 (出栈时使用,并返回对应的位置)
汇编:
000000000000000 <mod>
push rbp # 栈基址
mov rbp, rsp
……
pop rbp # 将rbp和rsp退出上一个栈中。退回地址:rbp退回到他指向的地址
ret # 回调。回调地址:rip存着
000000000000015 <add>
push rbp # 栈基址
mov rbp, rsp
……
call 32 <add+0x1d> # 调用mod函数
mov DWORD PTR [rbp-0x4], eax
……
leave # = mov rbp, rsp + pop rbp
ret
00000000000003f <main>
push rbp # 栈基址
mov rbp, rsp
……
call 64 <main+0x25> # 调用add函数
mov DWORD PTR [rbp-0xc], eax
leave
ret
main函数不是第一个被调用的函数,被名为start函数调用。在执行main函数之前,就已经存在栈帧了
其他指令
if 指令
void main() {
int a = 3;
int a = 2;
int c;
if (a == b) {
c = a + b;
} else {
c = a - b;
}
return c;
}
汇编
000000000000000 <main>
push rbp
mov rbp, rsp
# int a = 3;
mov DWORD PTR [rbp-0x8], 0x3
# int b = 2;
mov DWORD PTR [rbp-0xc], 0x2
# int c;
# if (a == b) {
mov eax, DWORD PTR [rbp-0x8]
cmp eax, DWORD PTR [rbp-0xc] # cmp =compare,比较结果放在条件码寄存器(标志位寄存器)
jne 27 <main+0x27> # jnp = jump if not equal,会去查看条件寄存器的零标志位
mov eax, DWORD PTR [rbp-0xc]
# c = a+b;
mov edx, DWORD PTR [rbp-0x8]
add eax, edx
mov DWORD PTR [rbp-0x4], eax
jmp 34 <main+0x34>
# } else {
# c = a - b
0x0027
mov eax, DWORD PTR [rbp-0xc]
mov edx, DWORD PTR [rbp-0x8]
sub edx, eax
mov eax, edx
mov DWORD PTR [rbp-0x4], eax
# }
# return c;
0x0034
mov eax, DWORD PTR [rbp-0x4]
# }
pop rbp
ret
for 指令
int main() {
int a = 0;
for (int i = 0; i < 3; i++) {
a += i;
}
return a;
}
汇编
000000000000000 <main>
push rbp
mov rbp, rsp
# int a = 0;
mov DWORD PTR [rbp-0x4], 0x0
# for (int i = 0; i < 3; i++) {
mov DWORD PTR [rbp-0x8], 0x0
jmp 1e <main+0x1e>
# a += i;
0x0014
mov eax, DWORD PTR [rbp-0x8]
add DWORD PTR [rbp-0x4], eax
# int main() {
# int a = 0;
# for (int i = 0; i < 3; i++) {
add DWORD PTR [rbp-0x8], 0x1
0x001e
cmp DWORD PTR [rbp-0x8], 0x2
jle 14 <main+0x14>
# a += i;
# }
# return a;
mov eax, DWORD PTR [rbp-0x8], 0x4
# }
pop rbp
ret
goto
C语言有个goto指令,可以代替条件控制。用那个来代替 if、for 等流程控制(或者说在流程控制出来前就是这样做的),应该就很好理解了