深入理解计算机系统
C程序生命周期
#include <stdio.h>
int main(void) {
printf("Hello,World\n");
}
hello.c
每条C语句都会被转化成低级机器语言指令。然后这些指令按照可执行目标程序的格式打好包,以二进制磁盘文件的形式存放起来。目标程序也被称为可执行目标文件。
在Unix系统上,从源文件到目标文件的转化是由编译器驱动程序完成的。
-
预处理阶段
。读取源文件以“#”开头的语句,将目标文件插入到源文件里。得到"hello.i"。 -
编译阶段
。将预处理完成的文件翻译成汇编语言。得到"hello.s"。 -
汇编阶段
。将编译完成的文件翻译成机器语言指令,并以可重定目标程序的格式打包起来,将结果保存在"hello.o"里,此文件是一个二进制文件。 -
链接阶段
。链接器将自身程序以外的函数(比如printf函数)合并到hello.o程序中,而那些函数存放在一个以函数名命名的文件中(比如printf.o),最终得到一个可执行目标文件。可以被加载到内存,由系统执行。
系统硬件组成
总线
。贯穿整个系统的电子管道,负责数据的传递。I/O设备
。输入输出设备。主存
。主存是一个临时存储设备,用来存放正在运行的程序和程序处理的数据。是由一个动态随机存取存储器(DRAM)芯片组成。处理器
。中央处理单元简称处理器。是解释(或执行)存储在主存中指令的引擎。核心是程序计数器、寄存器、算术逻辑单元。高速缓存
。CPU和主存之间的一些寄存器。将目前的常用数据存放在里面以便更快的访问。
操作系统管理硬件
进程
。对CPU、主存、I/O设备的抽象表示。虚拟内存
。对主存和磁盘的抽象表示。文件
。对I/O设备的抽象表示。
进程
进程是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程。而并发巡行指的是多个进程的指令交错进行。
线程是进程的控制流,一个进程可以有多个线程。
虚拟内存
虚拟内存
是一个抽象概念,他为每一个进程提供了一个假象,即每个进程都是在独占地使用主存。每个进程看到的内存都是一致的,称为虚拟地址空间
。
文件
文件就是字节序列。
Amdahl定律
当我们对系统的某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速成都。若系统执行某应用程序需要时间为Told。假设系统某部分所需执行时间与该时间的比例为a,而该部分性能提升比例为k。即该部分初始所需时间为aTold,现在所需时间为 ( a T o l d ) / k (aT~old~)/k (aT old )/k。因此,总的执行时间应为
T n e w = ( 1 − a ) T o l d + ( a T o l d ) k = T o l d [ ( 1 − a ) + a k ] T~new~ = (1-a)T~old~+\frac{(aT~old~)}{k} = T~old~[(1-a)+\frac{a}{k}] T new =(1−a)T old +k(aT old )=T old [(1−a)+ka]
由此,可以计算加速比
S
=
T
o
l
d
T
n
e
w
S=\frac{T~old~}{T~new~}
S=T new T old 为
S
=
1
(
1
−
a
)
+
a
k
S = \frac{1}{(1-a)+\frac{a}{k}}
S=(1−a)+ka1
并发和并行
- 并发是一个通用的概念,指一个同时具有多个活动的系统。
- 并行指的是用并发来使一个系统运行得更快。
信息的表示和处理
信息存储
大多数计算机使用8位的块,或者字节(byte),作为最小的可寻址的内存单位,而不是访问内存中单独的位。机器级程序将内存视为一个非常大的字节数组,称为虚拟内存(virtual memory)。内存的每个字节都是由一个唯一的数字来标识,称他为地址(address),所有可能地址的集合就称为虚拟地址空间
字数据大小
每台计算机都有一个字长(word size),指明指针数据的标称大小(normal size)。因为虚拟地址是以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。也就是说,对于一个字长为 w w w位的机器而言,虚拟地址的范围为0~ 2 w 2^w 2w-1,程序最多访问 2 w 2^w 2w个字节。
寻址和字节顺序
在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用的字节中的最小的地址。某些机器选择在内存中按照从最低有效字节到最高有效字节的顺序存储对象,而另一些机器选择在内存中按照从最高有效字节到最低有效字节的顺序存储。前一种规则叫小端法(little endian),后一种规则叫大端法(big- endian)。有些新的微处理器是双端法(bi endian)
无符号数的编码
定义
对
于
向
量
x
⃗
=
[
x
x
−
1
,
x
x
−
2
,
x
x
−
3
,
⋯
 
,
x
0
]
:
对于向量\vec{x} = [x_{x-1},x_{x-2},x_{x-3},\cdots,x_0]:
对于向量x=[xx−1,xx−2,xx−3,⋯,x0]:
B
2
U
w
(
x
⃗
)
=
∑
i
=
0
w
−
1
x
i
2
i
B2U_w(\vec{x}) = \sum_{i=0}^{w-1}{x_i2^i}
B2Uw(x)=i=0∑w−1xi2i
无符号数的二进制表示有一个很重要的属性,也就是每个结介于
0
0
0~
2
w
−
1
2^w-1
2w−1之间的数都有唯一一个
w
w
w位的值编码。
无符号数编码的唯一性
B 2 U w B2U_w B2Uw函数是一个双射
范围
U M a x w = 2 w − 1 UMax_w=2^w-1 UMaxw=2w−1
补码编码
定义
将字的最高位设为符号位,用函数 B 2 T w B2T_w B2Tw函数来表示:
对
向
量
x
⃗
=
[
x
x
−
1
,
x
x
−
2
,
x
x
−
3
,
⋯
 
,
x
0
]
:
对向量\vec{x} = [x_{x-1},x_{x-2},x_{x-3},\cdots,x_0]:
对向量x=[xx−1,xx−2,xx−3,⋯,x0]:
B
2
T
(
x
⃗
)
=
−
x
w
−
1
2
w
−
1
+
∑
i
=
0
w
−
2
x
i
2
i
B2T(\vec{x}) = -x_{w-1}2^{w-1}+\sum_{i=0}^{w-2}{x_i2^i}
B2T(x)=−xw−12w−1+i=0∑w−2xi2i
补码编码的唯一性
B 2 T w B2T_w B2Tw函数是一个双射
范围
T M i n w = − 2 w − 1 TMin_w=-2^{w-1} TMinw=−2w−1
T M a x w = 2 w − 1 − 1 TMax_w=2^{w-1}-1 TMaxw=2w−1−1
补码转无符号编码
原理:
补码转换为无符号数
对
满
足
T
M
i
n
w
≤
x
≤
T
M
a
x
w
的
x
有
对满足TMin_w\leq x\leq TMax_w的x有
对满足TMinw≤x≤TMaxw的x有
T
2
U
(
x
)
=
{
x
+
2
w
,
x
<
0
x
,
x
≥
0
T2U(x) = \begin{cases} x+2^w,& x < 0\\ x,& x\geq 0\end{cases}
T2U(x)={x+2w,x,x<0x≥0
推导:
B
2
U
(
x
⃗
)
−
B
2
T
(
x
⃗
)
=
(
∑
i
=
0
w
−
1
x
i
2
i
)
−
(
−
x
w
−
1
2
w
−
1
+
∑
i
=
0
w
−
2
x
i
2
i
)
=
x
w
−
1
2
w
−
1
+
x
w
−
1
2
w
−
1
=
x
w
−
1
(
2
w
−
1
+
2
w
−
1
)
=
x
w
−
1
2
w
\begin{aligned} B2U(\vec{x})-B2T(\vec{x}) &= (\sum_{i=0}^{w-1}{x_i2^i})-(-x_{w-1}2^{w-1}+\sum_{i=0}^{w-2}{x_i2^i})\\ &=x_{w-1}2^{w-1}+x_{w-1}2^{w-1}\\ &=x_{w-1}(2^{w-1}+2^{w-1})\\ &=x_{w-1}2^w \end{aligned}
B2U(x)−B2T(x)=(i=0∑w−1xi2i)−(−xw−12w−1+i=0∑w−2xi2i)=xw−12w−1+xw−12w−1=xw−1(2w−1+2w−1)=xw−12w
由此可得到:
B
2
U
w
(
x
⃗
)
=
x
w
−
1
2
w
+
B
2
T
w
(
x
⃗
)
B2U_w(\vec{x}) = x_{w-1}2^w+B2T_w(\vec{x})
B2Uw(x)=xw−12w+B2Tw(x)
因此就有:
B
2
U
w
(
T
2
B
w
(
x
)
)
=
T
2
U
w
(
x
)
=
x
+
x
w
−
1
2
w
B2U_w(T2B_w(x)) = T2U_w(x) = x+x_{w-1}2^w
B2Uw(T2Bw(x))=T2Uw(x)=x+xw−12w
无符号数编码转补码
原理:
无符号数转补码
对 满 足 0 ≤ u ≤ U M a x w 的 u , 有 对满足0\leq u \leq UMax_w的u,有 对满足0≤u≤UMaxw的u,有
U
2
T
w
(
u
)
=
{
u
,
u
≤
T
M
a
x
w
u
−
2
w
,
u
>
T
M
a
x
w
U2T_w(u)=\begin{cases} u,&u \leq TMax_w\\ u-2^w, &u> TMax_w\end{cases}
U2Tw(u)={u,u−2w,u≤TMaxwu>TMaxw
推导:
设
u
⃗
=
U
2
B
w
(
u
)
,
这
个
位
向
量
也
是
U
2
T
w
(
x
)
的
补
码
表
示
设\vec{u} = U2B_w(u),这个位向量也是U2T_w(x)的补码表示
设u=U2Bw(u),这个位向量也是U2Tw(x)的补码表示
B
2
T
w
(
u
⃗
)
−
B
2
U
w
(
u
⃗
)
=
(
−
u
w
−
1
2
w
−
1
+
∑
i
=
0
w
−
2
u
i
2
i
)
−
(
∑
i
=
0
w
−
1
u
i
2
i
)
=
(
−
u
w
−
1
2
w
−
1
)
−
(
u
w
−
1
2
w
−
1
)
=
(
−
u
w
−
1
2
w
−
1
)
+
(
−
u
w
−
1
2
w
−
1
)
=
−
u
w
−
1
(
2
w
−
1
+
2
w
−
1
)
=
−
u
w
−
1
2
w
\begin{aligned} B2T_w(\vec{u})-B2U_w(\vec{u}) &= (-u_{w-1}2^{w-1}+\sum_{i=0}^{w-2}{u_i2^i})-(\sum_{i=0}^{w-1}{u_i2^i})\\ &= (-u_{w-1}2^{w-1})-(u_{w-1}2^{w-1})\\ &= (-u_{w-1}2^{w-1})+(-u_{w-1}2^{w-1})\\ &= -u_{w-1}(2^{w-1}+2^{w-1})\\ &= -u_{w-1}2^w \end{aligned}
B2Tw(u)−B2Uw(u)=(−uw−12w−1+i=0∑w−2ui2i)−(i=0∑w−1ui2i)=(−uw−12w−1)−(uw−12w−1)=(−uw−12w−1)+(−uw−12w−1)=−uw−1(2w−1+2w−1)=−uw−12w
由此可得到:
B
2
T
w
(
u
⃗
)
=
B
2
U
w
(
u
⃗
)
−
u
w
−
1
2
w
B2T_w(\vec{u}) = B2U_w(\vec{u})-u_{w-1}2^w
B2Tw(u)=B2Uw(u)−uw−12w
因此得出:
B
2
T
w
(
U
2
B
(
u
)
)
=
U
2
T
w
(
u
)
=
u
−
u
w
−
1
2
w
B2T_w(U2B(u)) = U2T_w(u) = u-u_{w-1}2^w
B2Tw(U2B(u))=U2Tw(u)=u−uw−12w
二进制小数
一个二进制小数的表示方法定义如下
b
=
∑
i
=
−
n
m
b
i
2
i
b = \sum_{i=-n}^{m}{b_i2^i}
b=i=−n∑mbi2i
形如 0.11 ⋅ ⋅ ⋅ 1 2 0.11···1_2 0.11⋅⋅⋅12的数表示的是刚好小于1的数,但平常都用 1 − ε 1-\varepsilon 1−ε来表示。
IEEE浮点表示
定点表示法不能有效的表示非常大的数字,因为我们可以用类似科学计数法的形式来表示一些数。
IEEE浮点标准用 V = ( − 1 ) s ∗ M ∗ 2 E V=(-1)^s*M*2^E V=(−1)s∗M∗2E的形式来表示一个数:
- 符号(sign) s s s决定这个数是正数还是负数,而对于数值0的符号位解释做特殊情况处理。
- 尾数(signficand) M M M是一个二进制小数,它的范围是 1 1 1 ~ 2 − ε 2-\varepsilon 2−ε,或者是 0 0 0 ~ 1 − ε 1-\varepsilon 1−ε。
- 阶码(exponent) E E E的作用是对浮点数加权,权重是2的 E E E次幂,可以是负数。
- 一个单独的符号位 s s s直接编码符号位 s s s。
- k k k位的阶码字段 e x p = e k − 1 ⋯ e 1 e 0 exp=e_{k-1}\cdots e_1e_0 exp=ek−1⋯e1e0编码阶码 E E E。
- n n n位小数字段 f r a c = f n − 1 ⋯ f 1 f 0 frac=f_{n-1}\cdots f_1f_0 frac=fn−1⋯f1f0编码尾数 M M M,但是编码出来的值也依赖于阶码字段的值是否等于0。
单精度浮点数中, s = 1 , k = 8 , n = 23 s=1,k=8,n=23 s=1,k=8,n=23。双精度中, s = 1 , k = 11 , n = 52 s=1,k=11,n=52 s=1,k=11,n=52。
根据 e x p exp exp的值和被编码的值可以分为三种不同的情况,最后一种情况有两种
- 规格化的, e x p exp exp不全是1也不全是0。在这种情况下,阶码字段被解释为以偏置(biased)形式表示的有符号整数。也就是说, E = e − B i a s E=e-Bias E=e−Bias, e e e是无符号数, B i a s Bias Bias是一个等于 2 k − 1 − 1 2^{k-1}-1 2k−1−1的偏置值。尾数定义为 M = 1 + f M=1+f M=1+f
- 非规格化的, e x p exp exp的八位全是0。 E = 1 − B i a s E=1-Bias E=1−Bias, M = f M=f M=f
-
e
x
p
exp
exp全是1
- f r a c frac frac全是0, 表示无穷大
- f r a c frac frac不全是0,表示NaN(Not a Number)
舍入
- 向偶数舍入:比中间界大的向上舍入,小的向下舍入,中间的数向最近的偶数舍入
- 向零舍入:向零进行舍入,整数向下,负数向上
- 向上舍入:向最近的比自己大的数舍入
- 向下舍入:向最近的比自己小的数舍入
运算
无符号数的运算
加法运算
x + w u y = { x + y , x + y < 2 w x + y − 2 w , 2 w ≤ x + y < 2 w + 1 x+_w^uy=\begin{cases}x+y,&x+y<2^w \\ x+y-2^w, &2^w\leq x+y<2^{w+1} \end{cases} x+wuy={x+y,x+y−2w,x+y<2w2w≤x+y<2w+1
说一个算术运算溢出,是指完整的整数结果不能放到数据类型的字长限制中去。
检测无符号数加法的溢出
对范围在 0 ≤ x , y ≤ U M a x w 0 \leq x,y \leq UMax_w 0≤x,y≤UMaxw中的 x , y x,y x,y,令 s = x + w u y s=x+_w^uy s=x+wuy。则对计算s,当且仅当 s < x ∣ ∣ s < y s<x||s<y s<x∣∣s<y时溢出
阿贝尔群
模数加法形成了一种数学结构,称为阿贝尔群(Abelian group)。它是可交换的和可结合的。它有一个单位元0,并且每个元素有一个加法逆元。让我们考虑 w w w位的无符号数的集合,执行加法运算 + w u +_w^u +wu。对于每个值 x x x,必然有某个值 − w u x -_w^ux −wux满足 − w u x + w u x = 0 -_w^ux+_w^ux=0 −wux+wux=0。
无符号数求反
对满足
0
≤
x
<
2
w
0\leq x<2^w
0≤x<2w的任意
x
x
x,其
w
w
w位的无符号逆元
−
w
u
x
-_w^ux
−wux由下式给出
−
w
u
x
=
{
x
,
x
=
0
2
w
−
x
,
x
>
0
-_w^ux=\begin{cases} x, &x=0 \\2^w-x, &x>0 \end{cases}
−wux={x,2w−x,x=0x>0
乘法运算
对
满
足
0
≤
x
,
y
≤
U
M
a
x
w
的
x
和
y
有
对满足0\leq x,y \leq UMax_w的x和y有
对满足0≤x,y≤UMaxw的x和y有
x
∗
w
u
y
=
(
x
⋅
y
)
m
o
d
2
w
x*^u_wy=(x·y)\quad mod\quad 2^w
x∗wuy=(x⋅y)mod2w
补码运算
加法运算
对
满
足
−
2
w
−
1
≤
x
,
y
≤
2
w
−
1
−
1
的
整
数
,
有
对满足-2^{w-1}\leq x,y \leq 2^{w-1}-1的整数,有
对满足−2w−1≤x,y≤2w−1−1的整数,有
x
+
w
t
y
=
{
x
+
y
−
2
w
,
x
+
y
>
2
w
−
1
−
1
正
溢
出
x
+
y
,
−
2
w
−
1
≤
x
+
y
≤
2
w
−
1
−
1
正
常
x
+
y
+
2
w
,
x
+
y
<
−
2
w
−
1
负
溢
出
x+^t_wy=\begin{cases}x+y-2^w,&x+y>2^{w-1}-1 &正溢出\\ x+y, &-2^{w-1}\leq x+y \leq 2^{w-1}-1 &正常\\ x+y+2^w, & x+y<-2^{w-1} &负溢出\end{cases}
x+wty=⎩⎪⎨⎪⎧x+y−2w,x+y,x+y+2w,x+y>2w−1−1−2w−1≤x+y≤2w−1−1x+y<−2w−1正溢出正常负溢出
推导:
x + w t y = U 2 T w ( T 2 U w ( x ) + w t T 2 U w ( y ) ) = U 2 T w [ ( x w − 1 2 w + x + y w − 1 2 w + y ) m o d 2 w ] = U 2 T w [ ( x + y ) m o d 2 w ] \begin{aligned} x+^t_wy&=U2T_w(T2U_w(x)+^t_wT2U_w(y))\\ &=U2T_w[(x_{w-1}2^w+x+y_{w-1}2^w+y)mod\quad2^w]\\ &=U2T_w[(x+y)mod\quad2^w]\\ \end{aligned} x+wty=U2Tw(T2Uw(x)+wtT2Uw(y))=U2Tw[(xw−12w+x+yw−12w+y)mod2w]=U2Tw[(x+y)mod2w]
定 义 z 为 整 数 和 z = x + y , z ′ 则 为 z ′ = z m o d 2 w , 而 z ′ ′ = U 2 T ( z ′ ) = x + w t y 。 有 4 种 情 况 定义z为整数和z=x+y,z'则为z'=z\quad mod\quad 2^w,而z''=U2T(z')=x+^t_wy。有4种情况 定义z为整数和z=x+y,z′则为z′=zmod2w,而z′′=U2T(z′)=x+wty。有4种情况
-
− 2 w ≤ z < − 2 w − 1 -2^w\leq z < -2^{w-1} −2w≤z<−2w−1。然后,我们会有 z ′ = z + 2 w z'=z+2^w z′=z+2w, 所以 0 ≤ z ′ < 2 w − 1 0\leq z' < 2^{w-1} 0≤z′<2w−1,由此得到 z ′ ′ = z ′ z''=z' z′′=z′。所以这种情况下两个负数加起来等于一个正数,这种情况被称为负溢出。
-
− 2 w − 1 ≤ z < 0 -2^{w-1}\leq z <0 −2w−1≤z<0。然后,我们会有 z ′ = z + 2 w z'=z+2^w z′=z+2w,所以 2 w − 1 ≤ z ′ < 2 w 2^{w-1}\leq z' <2^w 2w−1≤z′<2w,由此得到 z ′ ′ = − 2 w + z ′ = − 2 w + 2 w + z = z z''=-2^w+z'=-2^w+2^w+z=z z′′=−2w+z′=−2w+2w+z=z。也就是说 z ′ ′ = z z''=z z′′=z。
-
0 ≤ z < 2 w − 1 0\leq z < 2^{w-1} 0≤z<2w−1。然后,我们会有 z ′ = z z'=z z′=z,所以 0 ≤ z ′ < 2 w − 1 0 \leq z' < 2^{w-1} 0≤z′<2w−1,由此得到 z ′ ′ = z ′ = z z''=z'=z z′′=z′=z。 z ′ ′ = z z''=z z′′=z。
-
2 w − 1 ≤ z < 2 w 2^{w-1}\leq z < 2^w 2w−1≤z<2w。然后,我们会有$z’= z $,所以 2 w − 1 ≤ z ′ < 2 w 2^{w-1} \leq z' < 2^w 2w−1≤z′<2w,由此得到 z ′ ′ = z ′ − 2 w = z − 2 w z''=z'-2^w=z-2^w z′′=z′−2w=z−2w。两个正数加起来得到有一个负数,这种情况被称为正溢出。
检测补码加法的溢出
对满足 T M i n w ≤ x , y ≤ T M a x w TMin_w\leq x,y \leq TMax_w TMinw≤x,y≤TMaxw的 x x x和 y y y,令 s = x + w t y s=x+^t_wy s=x+wty。当且仅当 x > 0 , y > 0 x>0,y>0 x>0,y>0,但 s ≤ 0 s\leq 0 s≤0时,发生了正溢出。当且仅当 x < 0 , y < 0 x<0,y<0 x<0,y<0,但 s ≥ 0 s\geq0 s≥0时,就发生了负溢出。
补码的非
对
满
足
T
M
i
n
w
≤
x
≤
T
M
a
x
w
的
x
,
其
补
码
的
非
−
w
t
x
由
下
式
给
出
对满足TMin_w\leq x \leq TMax_w的x,其补码的非-^t_wx由下式给出
对满足TMinw≤x≤TMaxw的x,其补码的非−wtx由下式给出
−
w
t
x
=
{
T
M
i
n
w
,
x
=
T
M
i
n
w
−
x
,
x
>
T
M
i
n
w
-^t_wx=\begin{cases}TMin_w, &x=TMin_w \\ -x, &x>TMin_w\end{cases}
−wtx={TMinw,−x,x=TMinwx>TMinw
乘法运算
对
满
足
T
M
i
n
w
≤
x
,
y
≤
T
M
a
x
w
的
x
和
y
,
有
对满足TMin_w\leq x,y \leq TMax_w的x和y,有
对满足TMinw≤x,y≤TMaxw的x和y,有
x
∗
w
t
y
=
U
2
T
w
(
(
x
⋅
y
)
m
o
d
2
w
)
x*^t_wy=U2T_w((x·y)\quad mod \quad 2^w)
x∗wty=U2Tw((x⋅y)mod2w)
对于无符号数和补码乘法来说,乘法运算的位级表示都是一样的。
给
定
长
度
w
位
的
位
向
量
x
⃗
和
y
⃗
,
用
补
码
形
式
的
位
向
量
表
示
来
定
义
整
数
x
和
y
,
x
=
B
2
T
w
(
x
⃗
)
,
y
=
B
2
T
w
(
y
⃗
)
。
用
无
符
号
数
形
式
的
位
向
量
表
示
来
定
义
整
数
x
′
和
y
′
,
x
′
=
B
2
U
w
(
x
⃗
)
,
y
′
=
B
2
U
w
(
y
⃗
)
。
则
给定长度w位的位向量\vec{x}和\vec{y},用补码形式的位向量表示来定义整数x和y,x=B2T_w(\vec{x}),y=B2T_w(\vec{y})。\\用无符号数形式的位向量表示来定义整数x'和y',x'=B2U_w(\vec{x}),y'=B2U_w(\vec{y})。则
给定长度w位的位向量x和y,用补码形式的位向量表示来定义整数x和y,x=B2Tw(x),y=B2Tw(y)。用无符号数形式的位向量表示来定义整数x′和y′,x′=B2Uw(x),y′=B2Uw(y)。则
U
2
B
w
(
x
′
∗
w
u
y
′
)
=
T
2
B
w
(
x
∗
w
t
y
)
U2B_w(x'*^u_wy')=T2B_w(x*^t_wy)
U2Bw(x′∗wuy′)=T2Bw(x∗wty)
推导
x ′ ∗ w t y ′ = [ ( x + x w − 1 2 w ) ⋅ ( y + y w − 1 2 w ) ] m o d 2 w = ( x ⋅ y + x ⋅ y w − 1 2 w + y ⋅ x w − 1 2 w + x w − 1 2 w ⋅ y w − 1 2 w ) m o d 2 w = [ x ⋅ y + ( x y w − 1 + y x w − 1 ) 2 w + ( x w − 1 + y w − 1 ) 2 w ] m o d 2 w = ( x ⋅ y ) m o d 2 w \begin{aligned} x'*^t_wy'&=[(x+x_{w-1}2^w)·(y+y_{w-1}2^w)]\quad mod \quad 2^w \\ &=(x·y+x·y_{w-1}2^w+y·x_{w-1}2^w+x_{w-1}2^w·y_{w-1}2^w)\quad mod\quad 2^w \\ &=[x·y+(xy_{w-1}+yx_{w-1})2^w+(x_{w-1}+y_{w-1})2^w]\quad mod\quad 2^w \\ &=(x·y)\quad mod\quad 2^w \end{aligned} x′∗wty′=[(x+xw−12w)⋅(y+yw−12w)]mod2w=(x⋅y+x⋅yw−12w+y⋅xw−12w+xw−12w⋅yw−12w)mod2w=[x⋅y+(xyw−1+yxw−1)2w+(xw−1+yw−1)2w]mod2w=(x⋅y)mod2w
根据等式(19),两边都应用 T 2 U w T2U_w T2Uw
T 2 U w ( x ∗ w t y ) = T 2 U w ( U 2 T w ( x ⋅ y ) m o d 2 w ) = ( x ⋅ y ) m o d 2 w T2U_w(x*^t_wy)=T2U_w(U2T_w(x·y)\quad mod\quad 2^w)=(x·y)\quad mod\quad 2^w T2Uw(x∗wty)=T2Uw(U2Tw(x⋅y)mod2w)=(x⋅y)mod2w
将上述结果与(15)和(21)结合起来得到 T 2 U w ( x ∗ w t y ) = ( x ′ ⋅ y ′ ) m o d 2 w = x ′ ∗ w t y ′ T2U_w(x*^t_wy)=(x'·y')\quad mod\quad 2^w = x'*^t_wy' T2Uw(x∗wty)=(x′⋅y′)mod2w=x′∗wty′。然后等式两边都应用 U 2 B w U2B_w U2Bw,得到
U 2 B ( T 2 U ( x ∗ w t y ) ) = T 2 B ( x ∗ w t y ) = U 2 B ( x ′ ∗ w t y ′ ) U2B(T2U(x*^t_wy))=T2B(x*^t_wy)=U2B(x'*^t_wy') U2B(T2U(x∗wty))=T2B(x∗wty)=U2B(x′∗wty′)
程序的机器级表示
机器级代码
计算机系统使用了多种不同形式的抽象,利用更简单的抽象模型隐藏实现的细节。对于机器级编程来说,其中两种抽象尤为重要
- 指令集体系结构,他定义了处理器状态,指令的格式,以及每条指令对状态的影响。
- 虚拟内存,机器级程序使用的内存地址是虚拟地址,提供的内存模型看上去是一个非常大的字节数组。
x86-64的机器代码和原始的C代码差别非常大。一些通常对C程序员隐藏的处理器状态都是可见的
- 程序计数器(PC,在x86-64中用%rip表示)给出将要执行的下一条指令在内存中的地址。
- 整数寄存器文件包含16个命名的位置,分别存储64位的值。这些寄存器可以存储地址或整数数据。有的寄存器被用来记录某些重要的程序状态,而其他寄存器被用来保存临时数据,例如过程中的局部变量,以及函数的返回值。
- 条件码寄存器保存着最近执行的算术逻辑或逻辑指令的状态信息。他们用来实现控制数据流的条件变化,比如说用来实现if和while语句。
- 一组向量寄存器可以存放一个或多个整数或浮点数值。
程序内存包含:程序的可执行代码,操作系统需要的一些信息,用来管理过程调用和返回的运行时栈,以及用户分配的内存块。
数据格式
C声明 | Intel数据类型 | 汇编代码后缀 | 大小(字节) |
---|---|---|---|
char | 字节 | b | 1 |
short | 字 | w | 2 |
int | 双字 | l | 4 |
long | 四字 | q | 8 |
char* | 四字 | q | 8 |
float | 单精度 | s | 4 |
double | 双精度 | l | 8 |
当那些复制或生成数据的指令以寄存器为目标时,对于生成小于8字节的指令,有两条规则
- 生成1字节和2字节数字的指令会保持剩下的字节不变(剩下的原来是什么样还是什么样)
- 生成4字节数字的指令会使高4字节置为0
访问信息
一个x86-64的中央处理单元包含一组16个存储64位值的通用目的寄存器。这些寄存器用来存储整数数据和指针。他们的名字都是以%r开头,不过后面还跟着一些不同的命名规则的名字,这是由于指令集历史演化造成的。最初的8086中有8个16位的寄存器。每个寄存器都有特殊的用途。扩展到IA32架构时,这些寄存器也扩展到了32位。扩展到x86-64后,原来的8个寄存器变成了64位。除此之外,还新添了8个新的寄存器。
操作数指令符
大多数指令有一个或多个操作数(operand),指示出执行一个操作中要使用的源数据值,以及放置结果的目的位置。
各种不同的操作数的可能性被分为三种类型
- 立即数(immediate),用来表示常数值。在ATT格式的汇编代码中,立即数的书写方式是“$”后面跟一个用标准C表示法表示的整数,比如$-577或$0x1F。不同的指令允许的立即数值范围不同。
- 寄存器(register),他表示某个寄存器的内容,16个寄存器的低位1字节、2字节、4字节和8字节中的一个作为操作数,这些字节分别对应于8位、16位、32位和64位。
- 内存引用,他根据计算出来的地址(通常称为有效地址)访问某个内存位置。
有很多不同的寻址模式,允许不同形式的内存引用。表中底部用语法 I m m ( r b , r i , s ) Imm(r_b,r_i,s) Imm(rb,ri,s)表示的是最常用的形式。这样的引用有四部分组成:一个立即数偏移 I m m Imm Imm,一个基址寄存器 r b r_b rb,一个变址寄存器 r i r_i ri,一个比例因子 s s s,这里 s s s必须是1、2、4、8。基址寄存器和变址寄存器必须是64位。有效地址被计算为 I m m + r b + r i ⋅ s Imm+r_b+r_i·s Imm+rb+ri⋅s。
类型 | 格式 | 操作数值 | 名称 |
---|---|---|---|
立即数 | $$ Imm$ | I m m I m m Imm | 立即数寻址 |
寄存器 | r a r_a ra | R [ r a ] R[r_a] R[ra] | 寄存器寻址 |
存储器 | I m m I m m Imm | M [ I m m ] M[ I m m ] M[Imm] | 绝对寻址 |
存储器 | ( r a ) (r_a) (ra) | M [ R [ r a ] ] M[R[r_a]] M[R[ra]] | 间接寻址 |
存储器 | I m m ( r b ) I m m( r_b) Imm(rb) | M [ I m m + R [ r b ] ] M[Imm+R[r_b]] M[Imm+R[rb]] | (基址+偏移量)寻址 |
存储器 | ( r b , r i ) (r_b,r_i) (rb,ri) | M [ R [ r b ] + R [ r i ] ] M[R[r_b]+R[r_i]] M[R[rb]+R[ri]] | 变址寻址 |
存储器 | I m m ( r b , r i ) I m m (r_b,r_i) Imm(rb,ri) | M [ I m m + R [ r b ] + R [ r i ] ] M[Imm+R[r_b]+R[r_i]] M[Imm+R[rb]+R[ri]] | 变址寻址 |
存储器 | ( , r i , s ) (,r_i,s) (,ri,s) | M [ R [ r i ] ⋅ s ] M[R[r_i]·s] M[R[ri]⋅s] | 比例变址寻址 |
存储器 | I m m ( , r i , s ) Imm(,r_i,s) Imm(,ri,s) | M [ I m m + R [ r i ] ⋅ s ] M[Imm+R[r_i]·s] M[Imm+R[ri]⋅s] | 比例变址寻址 |
存储器 | ( r b , r i , s ) (r_b,r_i,s) (rb,ri,s) | M [ R [ r b ] + R [ r i ] ⋅ s ] M[R[r_b]+R[r_i]·s] M[R[rb]+R[ri]⋅s] | 比例变址寻址 |
存储器 | I m m ( R [ r b ] , R [ r i ] , s ) Imm(R[r_b],R[r_i],s) Imm(R[rb],R[ri],s) | M [ I m m + R [ r b ] + R [ r i ] ⋅ s ] M[Imm+R[r_b]+R[r_i]·s] M[Imm+R[rb]+R[ri]⋅s] | 比例变址寻址 |
数据传送指令
数据传送指令中有两种把较小的源值复制到较大的目的时使用的指令类
- MOVZ,将做了零扩展的源值传送到目的。
- MOVS,将做了符号扩展的源值传送到目的。
压入和弹出栈数据
最后两个数据传送操作可以将数据压入程序栈中。栈是一种数据结构,可以添加或者删除值,不过要遵循“后进先出”的原则。通过push操作把数据压入栈,通过pop操作删除数据。它具有一个属性:弹出的值永远是最近被压入而且仍然在栈中的值。栈可以实现为一个数组,总是从数组的一端插入和删除 元素。这一端被称为栈顶
算数和逻辑操作
算数和逻辑操作一共有四组:
- 加载有效地址
- 一元操作
- 二元操作
- 移位
加载有效地址
加载有效地址(load effective address)指令leaq实际上是movq指令的变形。它的指令形式是从内存读数据到寄存器,但实际上它根本没有引用内存。它的第一个操作数看上去是一个内存引用,但该指令并不是从指定的位置读入数据,而是将有效地址写入到目的操作数。这条指令可以为后面的内存引用产生指针。它还可以简洁的描述普通的算术操作。编译器经常发现leaq的一些灵活用法,根本就与有效地址计算无关。目的操作数必须是一个寄存器。
实际上个人觉得这个指令为什么能够执行算术运算的原因就在于他是直接往寄存器里面存的地址,就是说他把源操作数的那个值当成了一个地址给到了某一个寄存器里面,就像瞒天过海一样。
控制
到目前为止,我们只考虑了直线代码的行为,也就是指令一条接着一条顺序地执行。C语言中的某种结构,比如条件语句、循环语句和分支语句,要求有条件的执行,根据数据测试的结果来决定操作执行的顺序。机器代码提供两种基本的低级机制来实现有条件的行为:测试数据值,然后根据测试的结果来改变控制流或者数据流。
条件码
除了整数寄存器,CPU还维护着一组单个位的条件码(condition code)寄存器,他们描述了最近的算术或者逻辑操作的属性。最常用的有
- CF:进位标志。最近的操作使最高位产生了进位。可用来检查无符号数的溢出。
- ZF:零标志。最近的操作得出的结果为0。
- SF:符号标志。最近的操作得到的结果为负数。
- OF:溢出标志。最近的操作导致一个补码溢出——正溢出或者负溢出。
有两类指令它们只设置条件码而不改变任何其他寄存器。
- C M P CMP CMP S 1 S_1 S1, S 2 S_2 S2 相当于 S 2 − S 1 S_2-S_1 S2−S1
- T E S T TEST TEST S 1 , S 2 S_1,S_2 S1,S2 相当于 S 1 S_1 S1& S 2 S_2 S2
访问条件码
条件码通常不会被直接读取,常用的使用方法有三种
- 可以根据条件码的某种组合将一个字节设置为0或1。
- 可以条件跳转到程序的某个其他的部分。
- 可以有条件地传送数据。
对于第一种情况,SET指令可以通过条件码的某种组合将一个字节设置为0或1。一条SET指令的目的操作数是低位单字节寄存器元素之一,或是一个字节的内存位置。
跳转指令
正常的执行情况下,指令按照它们出现的顺序一条一条地执行。跳转(jump)指令会导致执行切换到程序中的一个全新的位置。
jmp指令是无条件跳转的。它可以是直接跳转,即目标作为指令的一部分编码的;也可以是间接跳转,即跳转目标是从寄存器或内存位置中读出的。还有一些跳转指令是有条件的——他们根据条件码的某种组合,或者跳转,或者继续执行代码序列中的下一条指令。条件跳转只能是直接跳转。
跳转指令通常有几种不同的编码,但最常用的都是PC相对的(PC-relative)。也就是,他们会将目标地址与紧跟着跳转指令后面的那条指令的地址之差作为编码。这些地址偏移量可以编码为1、、2或4字节。第二种方法是给出“绝对”地址,用四个字节直接指定目标。
条件控制来实现条件分支
根据测试表达式的结果,选择运行的路径。
条件传送来实现条件分支
处理器通过流水线(pipelining)来获得高性能,当遇上条件跳转时,处理器采用精密的分支预测逻辑来判断每条跳转指令是否会执行。如果预测了一个错误的跳转,则要求处理器丢掉它为该跳转指令后所有指令已做的工作,再开始用从正确位置起始的指令去填充流水线。这样会导致程序性能严重下降。
于是在有一些情况下可以使用数据的条件转换。这种方法计算一个条件操作的两种结果,然后根据条件是否满足从中选取一个。这种策略如果可行,就可以用一条简单的条件传送指令(cmov)来实现它
过程
过程是软件中一种很重要的抽象。它提供了一种封装代码的方式,用一组指定的参数和一个可选的返回值实现了某种功能。不同的编程语言中,过程的形式多种多样:函数(function)、方法(method)、子例程(subroutine)、处理函数(handler)等等,但他们都有一些共有的特性。
假设P调用过程Q,Q执行后返回到P。这些动作包括下面一个或多个机制:
传递控制。在进入Q的时候,程序计数器必须被设置为Q的代码的起始地址,然后返回时,要把程序计数器设置为P调用Q后面那条指令的地址。
传递数据。P必须能够向Q提供一个或多个参数,Q必须能够向Q返回一个值。
分配和释放内存。在开始时,Q可能需要为局部变量分配空间,而在返回前,又必须释放这些存储空间。
运行时栈
大多数语言的过程调用机制使用了栈数据结构提供的后进先出的内存管理原则。
当x86-64过程需要的存储空间超过寄存器能够存放的大小的,就会在栈上分配空间。这个部分称为过程的栈帧(stack frame)
转移控制
将控制从函数P转移到Q只需要简单地把程序计数器设置为Q的代码的起始位置。不过,稍后从Q返回的时候,处理器必须记录好它需要继续P的执行的代码位置。在x86-64中,这个信息是用指令call Q调用过程Q来记录的。该指令会把地址A压入栈中,并将PC设置为Q的起始地址。压入的地址A被称为返回地址,是紧跟在call指令后面的那条指令的地址。对应的指令ret会从栈中弹出地址A,并把PC设置为A。
call指令可以是直接的也可以是间接的。
数据传送
参数的传递通过最多为7个特定的寄存器实现,6个向过程传递参数,1个用来传递返回值。如果有大于6
个的参数,则把剩下的那一部分放到栈里面存储。
寄存器中的局部存储空间
根据惯例,%rbx、%rbp和%r12~%r15被划分为被调用者保存寄存器,当过程P调用Q时,Q会把这些寄存器的值压入栈,返回的时候再弹出来,以此来保证本来寄存器的值不变。其他所有寄存器,除了%rsp(栈指针),都分类为调用者保存寄存器。这就意味着任何函数都能修改它们,所以保存好这个数据是P(调用者)的责任。
数组分配和访问
数组是一种将标量数据聚集成更大数据类型的方式。
基本原则
对于数据类型 T T T和整数类型 N N N,声明如下
T T T A [ N ] A[N] A[N];
起始位置表示为 x A x_A xA。这个声明有两个效果。首先,它在内存中分配一个 L ⋅ N L·N L⋅N字节的连续区域,这里 L L L是数据类型 T T T的大小(单位是字节)。其次,它引入了标识符 A A A,可以用 A A A来作为指向数组开头的指针,这个指针的值就是 x A x_A xA。可以用 0 0 0~ N − 1 N-1 N−1的整数索引来访问数组中的元素。数组元素 i i i会被存放在地址为 x A + L ⋅ i x_A+L·i xA+L⋅i的地方。
指针运算
C语言允许对指针进行运算,而计算出来的值会根据该指针引用的数据类型的大小进行伸缩。也就是说,如果 p p p是 一个指向类型为 T T T的数据的指针, p p p的值为 x p x_p xp,那么表达式 p + i p+i p+i的值为 x p + L ⋅ i x_p+L·i xp+L⋅i,这里 L L L是数据类型 T T T的大小。
单操作数“&”和“*”可以产生指针和间接引用指针。也就是说,对于一个表示某个对象的表达式 E x p r Expr Expr, & E x p r \&Expr &Expr是给出该对象地址的一个指针。对于一个表示地址的表达式 A E x p r AExpr AExpr,*AExpr给出该地址处的值。
嵌套的数组
当我们创建数组的数组时,数组分配和引用的一般原则也是成立的。
通常来说,对于一个声明如下的数组:
T T T D [ R ] [ C ] ; D[R][C]; D[R][C];
它的数组元素 D [ i ] [ j ] D[i][j] D[i][j]的内存地址为
& D [ i ] [ j ] = x D + L ( C ⋅ i + j ) \&D[i][j]=x_D+L(C·i+j) &D[i][j]=xD+L(C⋅i+j)
这里, L L L是类型 T T T的大小。
异质的数据结构
C语言提供了两种将不同类型的对象组合到一起创建数据类型的机制:结构(structure),用关键字struct来声明,将多个对象集合到一个单位中;联合(union),用关键字union来声明,允许用集中不同的类型来引用一个对象。
结构
C语言的struct声明创建一个数据类型,将可能不同类型的对象聚合到一个对象中。用名字引用来引用结构的各个组成部分。类似于数组的实现,结构的所有组成部分都存放在内存中一段连续的区域内,儿指向结构的指针就是结构第一个字节的地址。编译器维护关于每个结构类型的信息,指示每个字段(field)的字节偏移。它以这些偏移作为内存引用指令中的位移,从而产生对结构元素的引用。
将一个对象表示为struct
C语言提供的struct数据类型的构造函数(constructor)与C++和Java的对象最为接近。它允许程序员在一个数据结构中保存关于某个实体的信息,并用名字来引用这些信息。
例如,一个图形程序可能要用结构来表示一个长方形;
struct rect { long llx; long lly; unsigned long width; unsigned long height; unsigned color; };
可以声明一个struct rect 类型的变量r,并将它的字段值设置如下:
struct rect r; r.llx = r.lly = 0; r.color = 0xff00ff; r.width = 10; r.height = 20;
这里表达式r.llx就会选择结构r的llx字段。
另外,我们可以在一条语句中既声明变量又初始化它的字段:
struct rect r = {0, 0, 10, 20, 0xff00ff};
将指向结构的指针从一个地方传递到另一个地方,而不是复制它们,这是很常见的。例如,下面的函数计算长方形的面积,这里,传递给函数的就是一个指向长方形struct的指针:
long area(struct rect *rp) { return (*rp).width * (*rp).height; }
表达式(*rp).width间接引用了这个指针,并且选取所得结构的width字段。这里必须要用括号,因为编译器会将表达式 *rp.width 解释为 *(rp.width),而这是非法的。间接引用和字段选区结合起来使用非常常见,以至于C语言提供了一种替代的表示法 ->。即 rp -> width 等价于表达式 (*rp).width。例如,我们可以写一个函数,它将一个长方形顺时针旋转90度:
void rotate_left(struct rect *rp) { long t = rp -> height; rp -> height = rp -> width; rp -> width = t; rp -> llx -= t; }
考虑下面这样的结构声明:
struct rec {
int i;
int j;
int a[2];
int *p;
};
这个结构包括四个字段:两个4字节的int、一个由两个类型为int的元素组成的数组和一个8字节整形指针,总共是24个字节。
联合
联合提供了一种方式,能够规避C语言的类型系统,允许以多种类型来引用一个对象。联合声明的语法与结构的语法一样,只不过语义相差比较大。它们是用不同的字段来引用相同的内存块。
考虑下面的声明;
struct S3 {
char c;
int i[2];
double v;
};
union U3 {
char c;
int i[2];
double v;
};
在一台x86-64 Linux机器上编译时,字段的偏移量、数据类型S3和U3的完整大小如下:
类型 | c | i | v | 大小 |
---|---|---|---|---|
S3 | 0 | 4 | 16 | 24 |
U3 | 0 | 0 | 0 | 8 |
S3中i的偏移量是4不是1,v的偏移量是16而不是9或者12,是因为数据对齐的原因。
对于类型 union U3 * 的指针 p,p -> c、p -> i[0]、p -> v 引用的都是数据结构的起始位置。还可以观察到,一个联合的总的大小等于它最大字段的大小。
在一些上下文中,联合十分有用。但是,它也能引起一些讨厌的错误,因为它们绕过了C语言类型系统提供的安全措施。一种应用情况是,我们实现知道对一个数据类结构中的两个不同字段的使用时互斥的,那么将两个字段声明为联合的一部分,而不是结构的一部分,会减小分配空间的总量。
数据对齐
许多计算机系统对基本数据类型的合法地址做出了一些限制,要求某种类型对象的地址必须是某个值 K K K(通常是2、4、8)的倍数。这种对齐限制简化了形成处理器和内存系统之间接口的硬件设计。
Intel的对齐原则是任何 K K K字节的基本对象的地址必须是 K K K的倍数。