【计算机系统】深入理解计算机系统第三版笔记·上半
本文最后更新于:2024年5月15日 晚上 18:44
深入理解计算机系统第三版笔记·上半
2024/04/19
—— Shizuri Yuki
第一章 计算机系统漫游
1 信息就是位+上下文
1 |
|
ASCII码表
000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 | HIGH 3 | BIN |
---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | HEX | LOW 4 |
NUL(null,\0) |
DLE |
SP(Space) |
0 |
@ |
P |
| p| **0** | **0000** | | SOH| DC1| !| 1| A| Q| a| q| **1** | **0001** | | STX| DC2| "| 2| B| R| b| r| **2** | **0010** | | ETX| DC3| #| 3| C| S| c| s| **3** | **0011** | | EOT| DC4| $| 4| D| T| d| t| **4** | **0100** | | ENQ| NAK| %| 5| E| U| e| u| **5** | **0101** | | ACK| SYN| &| 6| F| V| f| v| **6** | **0110** | | BEL(Bell,)| ETB| '| 7| G| W| g| w| **7** | **0111** | | BS(Backspace,| CAN| (| 8| H| X| h| x| **8** | **1000** | | HT(Horizontal
Tab,| EM| )| 9| I| Y| i| y| **9** | **1001** | | LF(New
Line,)| SUB| *| :| J| Z| j| z| **A** | **1010** | | VT(Vertical
Tab,| ESC(Escape)| +| ;| K| [| k| {| **B** | **1011** | | FF(| FS| ,| <| L| ` |
l |
| |
C |
CR(Carriage Return,\r) |
GS |
- |
= |
M |
] |
m |
} |
D | 1101 |
SO |
RS |
. |
> |
N |
^ |
n |
~ |
E | 1110 |
SI |
US |
/ |
? |
O |
_ |
o |
DEL |
F | 1111 |
速记:
\t
为 0x09,\n
为 0x0A,\r
为 0x0D,space
为 0x20,ESC
为 0x1B。0
为 0x30,数字以0开头。A
为 0x41,a
为 0x61。小写在大写后面。
2 处理器读、执行指令流程
3 系统硬件组成
- 总线:
- I/O 设备:每个I/O设备都通过一个
控制器或适配器(两者不同) 与I/O总线相连
- 控制器:I/O设备本身或主板上的芯片组
- 适配器:一块插在主板插槽上的卡
- 主存:RAM
- 处理器:
外存:指磁盘。
4 储存设备层次
5 操作系统管理硬件
操作系统是应用程序和硬件之间插入的一层软件。
所有应用程序对硬件的操作尝试都必须通过操作系统。
基本功能
- 防止硬件被失控的应用程序滥用
- 向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备
操作系统基本抽象概念
- 进程:对处理器、主存和I/O设备的抽象表示
- 虚拟内存:对主存和磁盘I/O设备的抽象表示
- 文件:对I/O设备的抽象表示

6 Amdahl 定律
第二章 信息的表示和处理
1 信息存储
1.1 字
每台计算机都有一个字长(word size),指明整数和指针数据的标称大小(nominal size)。因为虚拟地址是以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。
也就是说,对于一个字长为 \(w\) 位的机器而言,虚拟地址的范围为 \(0\) ~ \(2^{w-1}\) ,程序最多访问 \(2^{w}\) 个字节。
x86-32架构字长是32,x86-64架构字长是64。
1.2 数据大小

2 寻址和字节顺序
小端模式(Little Endian)
小端模式是指数据的低位字节存储在低地址上,而数据的高位字节则存放在高地址上。
1
int a = 0x12345678; // --> 78 56 34 12
x86和x86-64都采用的小端模式。所以查看内存时,读到的多字节变量要反过来读。
大端模式(Big Endian)
大端模式则与小端相反,它是指数据的高位字节存储在低地址上,而数据的低位字节存放在高地址上。
1
int a = 0x12345678; // --> 12 34 56 78
ARM采用小端、大端可调的模式。不过一般使用小端模式。
网络传输的字节序通常为大端模式。
3 整数
注意事项:
有符号数的符号扩展。
当C语言执行一个运算时,如果它的一个运算数是有符号的而另一个是无符号的,那么C语言会隐式地将有符号参数强制类型转换为无符号数,并假设这两个数都是非负的,来执行这个运算。
1
2
3
4
5
6
7
8#include <stdio.h>
int main() {
unsigned int x = 20;
int y = -5;
if (x > y) printf("x > y\n");
else printf("x <= y\n");
return 0;
}输出:
1
2$ gcc if.c -o if && ./if
x <= y原因在于C语言在比较无符号数和有符号数时自动把所有数都看作无符号数,导致y变得很大。
补码的相反数:
~x + 1
补码乘法:视为无符号数进行乘法,然后直接截断再转为补码作为结果。
乘以常数:
x * 14 == x * (2^3 + 2^3 + 2^1) == (x << 3) + (x << 2) + (x << 1)
x * 14 == x * (2^4 - 2^1) == (x << 4) - (x << 1)
补码除法(除以2的幂,
x/2^k
):注意负数不能直接移位,需要加上偏置值 \(2^k-1\)。
x < 0 ? (x+(1<<k)-1)>>k : x>>k
4 浮点数
4.1 二进制小数
转为十进制小数方法:先将小数点后的位全部左移到小数点前(共左移 \(k\) 位),然后转为十进制整数,之后除以 \(2^k\) 即为十进制小数。
十进制小数转为二进制方法:
- 将小数点前的整数部分按照常规的十进制整数转换为二进制的方法进行转换。
- 对于小数部分,以下是转换方法:
- 乘以2,然后记录下整数部分作为一个bit。
- 保留小数部分,再次乘以2,然后再次记录下整数部分。
- 重复这个过程,直到小数部分变成0,或者获得了足够的精度。
4.2 IEEE 浮点数表示
由于是科学计数法,所以数值越大,它的最小递增单位就越大,最大的递增单位是超级大的。
以float为例的给定位表示:
- 规格化
- 尾数:
1.____frac(22:0)____
(小数点前有隐含的1) - 阶码:
E = e - Bias
。float的Bias为127,double的Bias为1023。
- 尾数:
- 非规格化
- 尾数:
0.____frac(22:0)____
(小数点前是0) - 阶码:
E = 1 - Bias
。float的E为-126,double的E为-1022。
- 尾数:
- 特殊值
- 无穷:用于表示溢出的结果
- NaN:(Not a Number)用作一些无法表示的结果,比如 \(\sqrt{-1}\) 和 \(\infin - \infin\) 。
4.3 浮点数数值范围
要注意非规格和规划化数是恰好衔接的。
规格化数的Step随着阶码的增加逐渐变大。非规格化数的Step不变,所以非规格化数区域数值变化是线性的。

4.4 舍入
四舍六入五成双。(银行家算法)
我们将最低有效位的值0认为是偶数,反之认为是奇数。
4.5 浮点运算
注意不同的计算顺序可能会导致最终的结果不同。因为精度损失的原因。
第三章 程序的机器级表示
1 寄存器

1.1 通用寄存器

x86-64 仍然支持AH、BH、CH、DH寄存器的访问,但是具有一定限制。
x86-64 中 Intel 称R8R15的低8位寄存器为R8LR15L,而微软称为R8B~R15B。
E
:Extended
R
:Register
X
:The placeholder of H
and L
, because AX can be divided into AH and AL.
寄存器 | 常用用途 |
---|---|
RAX (Accumulator Register) | 第1个返回值寄存器、累加器 |
RBX (Base Register) | 内存指针、基址寄存器 |
RCX (Counter Register) | 第4个整数参数、this指针、循环控制、计数器 |
RDX (Data Register) | 第3个整数参数、第2个返回值寄存器、整数乘除法 |
RSI (Source Index) | 第2个整数参数、串指令源指针、索引寄存器 |
RDI (Destination Index) | 第1个整数参数、串指令目标指针、索引寄存器 |
RBP (Base Pointer) | 栈顶指针 |
RSP (Stack Pointer) | 栈帧基址指针 |
R8 | 第5个整数参数 |
R9 | 第6个整数参数 |
R10 | 静态链指针(用于嵌套函数) |
R11 | \ |
R12 | \ |
R13 | \ |
R14 | \ |
R15 | \ |
RIP (Instruction Pointer) | 指令指针。只读,且不可拆分。指向下一条需要执行的指令地址。 |
1.2 段寄存器
(CS,DS,SS,ES,FS,GS)
段寄存器源于Intel 8086处理器,ALU的数据总线宽度只有16位,而为了更大的内存单元,Intel 想到了一个折中的办法:把内存分段,并设计了4个段寄存器,CS,DS,ES和SS,分别用于指令、数据、其它和堆栈,这样地址总线为32位。
CS:Code Segment 代码段寄存器
DS:Data Segment 数据段寄存器
SS:Stack Segment 堆栈段寄存器
ES:Extra Segment 附加段寄存器(辅助段)
FS:Flag Segment 标志段寄存器(辅助段,80386起增加)
GS:Global Segment 全局段寄存器(辅助段,80386起增加)
由于段寄存器总是和其他一些像指针寄存器,变址寄存器,控制寄存器一起使用,所以在这里,不单独介绍段寄存器,而是将段寄存器和一些其他的常用寄存器搭配介绍。
CS 寄存器组合 IP 寄存器
CS:IP 两个寄存器指示了 CPU 当前将要读取的指令的地址,其中 CS 为代码段寄存器,而 IP 为指令指针寄存器 。当一个可执行文件加载到内存中以后,CS:IP 两个寄存器便指向了这个可执行文件的起始地址,然后 CPU 就可以从这个起始地址开始往下读取指令,当读取完指令后,CS:IP 将会自动的改变,基本上是改变 IP,从而指向下一条要读取的指令,这样就可以执行这个可执行文件了。任何时候,CS:IP 指向的地址中的内容都是 CPU 当前执行的指令。
SS 寄存器组合 SP 寄存器
SS:SP 两个寄存器指向的是内存栈的栈顶元素。当使用 PUSH 指令向栈中压入 1 个字节单元时,SP =SP - 1;即栈顶元素会发生变化;当使用 POP 指令从栈中弹出 1 个字节单元时, SP =SP + 1;即栈顶元素会发生变化。
DS 寄存器和 ES 寄存器
DS 寄存器和 ES 寄存器都属于段寄存器,其实它们和 CS 寄存器以及 SS 寄存器用起来区别不大,既然是段寄存器的话,自然它们存放的就是某个段地址了。我们知道,CPU 要访问一个内存单元时,必须要提供一个指向这个内存单元的物理地址给 CPU ,而在 8086 CPU中,物理地址是由段地址左移 4 位(乘以16)后加上偏移地址形成的。我们也就只需要提供段地址和偏移地址即 OK 。DS寄存器存放的就是数据段的段地址 ,除了BP基指针寄存器外,其余的寄存器都默认使用DS寄存器的值作为段地址。
FS 寄存器和 GS 寄存器
FS 寄存器用于指向 TIB(Thread Information Block)。该结构体包含进程中运行线程的各种信息,进程中的每个线程都对应着一个TIB结构体。
Windows x86 使用FS寄存器,GS寄存器始终置为0。
Windows x64 使用FS寄存器支持32位程序运行,使用GS寄存器支持64位程序运行。
程序只使用FS和GS其中一个,不会都使用。
1.3 EFLAGS 寄存器

重要的常用标志:
- CF :进位标志。
- ZF :零标志。
- SF :符号标志。
- OF :溢出标志。
- AF :辅助进位标志。指示BCD加减法的进位或借位情况。
- PF :奇偶校验标志。统计最低有效字节(低8位)的奇偶性。(1=偶,0=奇)
- DF:方向标志。指示串指令执行时EDI和ESI寄存器的自动递增方向。(0=从低到高)
2 数据格式

注意:
long在64位Windows下是4字节,在64位Linux下是8字节。
32模式下的long long采用额外的操作软支持64位数值的运算。低32位存入EAX,高32位存入EDX。

注意区别系统的“字”和这里的“字”!!!
3 操作数
3.1 立即数
AT&T | Intel | 结果 |
---|---|---|
movq $1234567,%rax |
movq rax,1234567 |
rax=1234567 |
3.2 寄存器
AT&T | Intel | 结果 |
---|---|---|
movq %rcx,%rax |
movq rax,rcx |
rax=rcx |
3.3 内存
\[ \text{Effective Address}=\text{BaseReg}+\text{IndexReg}*\text{ScaleFactor}+\text{Disp} \\ \text{(实际地址=基址寄存器+索引寄存器*放大因子+位移)} \]
3.3.1 x86-32 内存寻址模式
基址寄存器:可以使用任意通用寄存器。
(注意EIP不是通用寄存器!IA32通用寄存器只有EAX EBX ECX EDX EBP ESP ESI EDI )
索引寄存器:可以使用除 ESP 外的通用寄存器。用于访问数组中的某个元素。
位移(Displacement):常数值,被编码到指令中。
放大因子:有效的放大因子为1、2、4、8。
共8种寻址方式:
寻址方式 | AT&T示例 | Intel示例 | 描述 | 常用 |
---|---|---|---|---|
Disp | movl $0x123,0x804c010 |
mov ds:0x804c010,0x123 |
绝对寻址 | 访问全局变量 |
BaseReg | mov (%eax),$0x666 |
mov $0x666,[eax] |
间接寻址 | 访问变量指针的值 |
BaseReg+Disp | mov %eax,-0x8(%ebp) |
mov [ebp-0x8],eax |
基址+偏移量寻址 | 访问局部变量、函数参数 |
Disp+IndexReg*SF | mov 0x8(,%eax,4),%eax |
mov eax,[eax*4+0x8] |
比例变址寻址 | 访问全局数组元素 |
BaseReg+IndexReg | movl (%ebx,%ecx),%eax |
mov eax,[ebx+ecx] |
变址寻址 | 访问BYTE数组 |
BaseReg+IndexReg+Disp | movl 10(%ebx,%ecx),%eax |
mov eax,[ebx+ecx+10] |
变址寻址 | 访问结构体成员 |
BaseReg+IndexReg*SF | movl (%ebx,%ecx,4),%eax |
mov eax,[ebx+ecx*4] |
比例变址寻址 | 访问数组指针的元素 |
BaseReg+IndexReg*SF+Disp | movl 10(%ebx,%ecx,4),%eax |
mov eax,[ebx+ecx*4+10] |
比例变址寻址 | 访问结构体的数组成员、多维数组 |
注:在32位下,基址寄存器是EBP或ESP时,默认的段寄存器是SS,否则,默认的段寄存器是DS。所以AT&T汇编省略了段寄存器,但Intel风格的没有省略。
3.3.2 x86-64 内存寻址模式
特别注意事项
- 在 x86-64 禁止使用32位寄存器作为内存的地址!必须使用64位的寄存器!!!!
x86-64多了1种寻址方式,常用于替代x86-32的Disp绝对寻址:
寻址方式 | AT&T示例 | Intel示例 | 描述 |
---|---|---|---|
RIP + Disp | movl $0x123,0x2f00(%rip) |
mov [rip+0x2f00],0x123 |
RIP相对寻址 |
该寻址方式支持PIC(Position Independent Code)位置无关代码,对于加载动态库比较方便,因为动态库的全局变量可以通过相对寻址。如果使用绝对寻址的话,就必须多一次重定向,更麻烦。
示例代码:
1 |
|
AT&T汇编(x86-64 gcc 13.2):
1 |
|
当执行到 movl $0x123,0x2f00(%rip)
的时候,PC指向下一条指令的地址,即 %rip==0x401114
。
此时 %rip + Disp = 0x401114 + 0x2f00 == 0x404014
,该位置为全局变量 i
。
启用 -m32
标志编译后:
1 |
|
可见全局变量 i
变成了固定地址,也就是x86-32的绝对寻址。
4 数据传送指令
4.1 操作数组合
注意:数据传送指令中没有内存到内存的操作数组合。
类型 | AT&T | Intel |
---|---|---|
\(\text{Reg}\leftarrow \text{Imm}\) | movl $0x4050,%eax |
mov eax,0x4050 |
\(\text{Reg}\leftarrow \text{Reg}\) | movw %bp,%sp |
mov sp,bp |
\(\text{Mem}\leftarrow \text{Imm}\) | movb $-17,(%rsp) |
mov [rsp],-17 |
\(\text{Mem}\leftarrow \text{Reg}\) | movq %rax,-12(%rbp) |
mov [rbp-12],rax |
\(\text{Reg}\leftarrow \text{Mem}\) | movb (%rdi,%rcx),%al |
mov al,[rdi+rcx] |
4.2 MOV
把数据从源位置复制到目的位置 ,不做任何变化。
指令(AT&T风格) | 描述 |
---|---|
movb S,D |
传送字节 |
movw S,D |
传送字 |
movl S,D |
传送双字 |
movq S,D |
传送四字(S=IMM时是假的四字,实际上是双字) |
movabsq IMM,REG |
传送真正的64位立即数到寄存器 |
特别注意事项
movl S,REG
会将32位寄存器外部的高4字节清零。而其他的movb movw
只会更新指定的那些寄存器字节或内存位置!movq IMM, D
立即数只能是32位的,然后符号扩展到64位。movabsq IMM,REG
则支持64位的立即数,但是目的操作数只能是寄存器,不能是内存。
4.3 MOVZ
零扩展方式从较小源操作数传送较大源操作数。
以寄存器或内存地址作为源操作数,以寄存器作为目的操作数。
MOVZ S,D |
以零扩展进行传送 |
---|---|
movzbw REG/MEM,REG |
将 字节 零扩展传送到 字 |
movzbl REG/MEM,REG |
将 字节 零扩展传送到 双字 |
movzbq REG/MEM,REG |
将 字节 零扩展传送到 四字 |
movzwl REG/MEM,REG |
将 字 零扩展传送到 双字 |
movzwq REG/MEM,REG |
将 字 零扩展传送到 四字 |
特别注意事项
- 没有
movzlq
!!!这个是用movl S,REG
将64位寄存器的高4字节清零实现的。 - 一定注意这里源操作数不能是立即数,目的操作数只能是寄存器!!
4.4 MOVS
符号扩展方式从较小源操作数传送较大源操作数。
以寄存器或内存地址作为源操作数,以寄存器作为目的操作数。
MOVS S,D |
以符号扩展进行传送 |
---|---|
movsbw REG/MEM,REG |
将 字节 符号扩展传送到 字 |
movsbl REG/MEM,REG |
将 字节 符号扩展传送到 双字 |
movsbq REG/MEM,REG |
将 字节 符号扩展传送到 四字 |
movswl REG/MEM,REG |
将 字 符号扩展传送到 双字 |
movswq REG/MEM,REG |
将 字 符号扩展传送到 四字 |
movslq REG/MEM,REG |
将 双字 符号扩展传送到 四字 |
cltq |
把 %eax 符号扩展到 %rax |
注意事项
cltq
没有操作数。效果与movslq %eax,%rax
一样,但是编码更短。
4.5 PUSH / POP / LEAVE
栈指针 %rsp 指向栈顶元素,帧指针 %rbp 指向保存调用者的 %rbp 的值的地址。
指令 | 等效指令 | 描述 |
---|---|---|
pushq S |
\(\begin{align}&\texttt{subq \$8,\%rsp}\\&\texttt{movq S,(\%rsp)}\end{align}\) | 将四字压入栈 |
popq D |
\(\begin{align}&\texttt{movq (\%rsp),D}\\&\texttt{addq \$8,\%rsp}\end{align}\) | 将四字弹出栈 |
leave |
\(\begin{align}&\texttt{movq \%rbp,\%rsp}\\&\texttt{popq \%rbp}\end{align}\) | 将帧指针恢复到它之前的值 |
5 算术和逻辑指令
5.1 常用算术操作
伪指令(AT&T) | 效果 | 描述 |
---|---|---|
ADD S,D |
D = D + S |
|
ADC S,D |
D = D + S + CF |
配合两个32位组成的64位的高32加法 |
SUB S,D |
D = D - S |
|
SBB S,D |
D = D - S - CF |
配合两个32位组成的64位的高32减法 |
IMUL S,D |
D = signed(D) * S |
有符号整数乘法 |
MUL S,D |
D = unsigned(D) * S |
无符号整数乘法 |
IDIV S,D |
D = signed(D) / S |
有符号整数除法 |
DIV S,D |
D = unsigned(D) / S |
无符号整数除法 |
INC D |
D++ |
递增 |
DEC D |
D-- |
递减 |
NEG D |
D = -D |
相反数 |
位运算 | ||
NOT D |
D = ~D |
按位取反 |
AND S,D |
D &= S |
|
OR S,D |
D |= S |
|
XOR S,D |
D ^= S |
|
SAL k,D |
D <<= k |
算术左移,同时设置CF为最后一个被移出操作数的bit,要求 |
SHL k,D |
D <<= k |
逻辑左移,效果和 SAL k,D 完全一致 |
SAR k,D |
D >>= k |
算术右移,同时设置CF为最后一个被移出操作数的bit |
SHR k,D |
D = unsigned(D) >> k |
逻辑右移,同时设置CF为最后一个被移出操作数的bit |
SHLD k,S,D |
D = (unsigned(D)<<k)|(S的高k位补D的低k位) |
逻辑左移,但使用源操作数来补,CF被设置 |
SHRD k,S,D |
D = (S的低k位补D的高k位)|(unsigned(D)>>k) |
逻辑右移,但使用源操作数来补,CF被设置 |
ROL k,D |
左旋 |
![]() |
ROR k,D |
右旋 |
![]() |
RCL k,D |
带有CF的左旋 |
![]() |
RCR k,D |
带有CF的右旋 |
![]() |
5.2 特殊算术指令
x86-64
%rdx
作为高64位, %rax
作为低64位。
指令(ATT) | 效果 | 描述 |
---|---|---|
imulq S |
` |