第1章 计算机系统漫游
图1 编译系统
GCC(GNU Compiler Collection)
预处理 -E
只执行到预编译,默认输出至控制台
gcc -E main.c -o main.i
编译 -S
只执行到编译,默认生成汇编文本文件,后缀.s
gcc -S main.i
gcc -S main.c
汇编 -c
只执行到汇编,默认输出二进制目标文件,后缀.o
gcc -c main.s
gcc -c main.c
链接
生成可执行文件
gcc main.o -o main
gcc main.c -o main
// 自动优化编译
gcc -O main.c -o main
程序编译
预处理: 加载头文件至原始程序;
编译: 编译器将文本文件hello.i翻译成汇编程序文本文件hello.s;
汇编: 汇编器将汇编程序文件翻译成机器语言指令;
链接: 链接将已经编译好的目标文件(后缀.o)合并,生成可执行文件;
程序运行
获取指令: 键盘输入"./hello",字符读入寄存器,再存放至内存;
加载文件: 利用直接存储器存取技术(DMA),目标文件可不通过CPU直接从磁盘加载到主存;
程序执行: 机器指令从主存复制到寄存器并执行,执行结果从寄存器复制到显示设备,打印输出;
高速缓存cache
内存读取速度比磁盘读取快约1000万倍;
寄存器读取速度比主存读取快约100倍;
高速缓存L1和L2是一种静态随机访问存储器(SRAM)。L1高速缓存读取速度约等于寄存器,比L2高速缓存快5倍; L2高速缓存读取速度比主存读取快5~10倍;
操作系统
基本功能
防止硬件被失控的程序滥用;
向应用程序提供简单一致的机制控制复杂的低级硬件设备;
注:基于进程、虚拟内存、文件实现
进程
进程
是操作系统对正在运行的程序的一种抽象。
并发
运行是指一个进程的指令和另一个进程的指令交错执行,多个进程同时活动。一个CPU通过上下文切换
实现并发执行多个程序。
线程
一个进程由多个线程
组成,每个线程都运行在进程的上下文中,共享同样的代码和数据。
虚拟内存和虚拟内存空间
虚拟内存是主存和I/O设备
的抽象,它为每个进程提供一种独立占用内存的假象。
每个进程看到的内存都是一致的,称为虚拟地址空间。
虚拟地址空间包括:只读的代码和数据、读/写数据、堆、共享库、栈、内核虚拟内存。
文件
文件就是字节序列,每个I/O设备,包括磁盘、键盘、显示设备,甚至网络都可看为文件。
并发与并行
并发是同时具有多个活动的系统,并行是用并发使系统运行的更快。
线程级并发: 超线程,又称为同时多线程,允许单CPU执行多个控制流的技术;
指令集并发: 早期处理器需3~4个机器周期处理一条指令,现在CPU利用流水线技术,将一条指令划分为不同步骤。这些阶段可并行操作,用来处理不同指令的不同部分。若处理器一个机器周期可执行一条以上的指令,称之为超标量处理器。
单指令、多数据并行: 特殊的硬件结构允许一条指令产生多个并行执行的操作,称之为单指令、多数据(SIMD)
并行。如并行对8对单精度浮点数做加法。
第2章 信息的表示和处理
2.1 信息存储
字长
字长指明指针数据的标称大小,虚拟地址以一个字长编码。字长决定了最大的寻址空间,即决定了虚拟地址空间的有效值。
如对于32位计算机,最大寻址空间
232bytes=4GB。
寻址和字节顺序
大端/小端法分别以最高/最低有效字节在最前面(地址较小)的方式存储数据。
假设
int型变量
x位于地址
0x100处,其十六进制值为
0x12345678,以大端法和小端法的存储格式分别如下:
移位运算
逻辑右移
左端补0,算数右移
左端补符号位。
如有符号数-8,补码表示为1111 1000,算数右移1位(等价于除2),得到1111 1100,即-4。
2.2 整数表示
编码
对向量
x
=[xw−1,xw−2,⋯,x0],则位向量到整数的映射
无符号编码(
binarytounsigned):
B2Uw(x
)=i=0∑w−1xi2i
反码编码(
binarytotwo′scomplement),正数反码与原码相同,负数反码是对其原码除符号位逐位取反,则:
B2Ow(x
)=−xw−1(2w−1−1)+i=0∑w−2xi2i
补码编码(
binarytotwo′scomplement),正数补码与原码相同,负数补码为其补码加1,则:
B2Tw(x
)=−xw−12w−1+i=0∑w−2xi2i
类型转换
对整数
x,其向量表示
x
=[xw−1,xw−2,⋯,x0],有以下转换:
补码转为无符号数
由
B2Uw(x
)−B2Tw(x
)=xw−12w,且
B2Tw(x
)=x,得
B2Uw(x
)=xw−12w+x,得
T2Uw(x)=B2Uw(x
)=xw−12w+x
无符号数转为补码
由
B2Uw(x
)−B2Tw(x
)=xw−12w,且
B2Uw(x
)=x,得
B2Tw(x
)=x−xw−12w,得
U2Tw(x)=B2Tw(x
)=x−xw−12w
C语言中有符号数与无符号数运算时,有符号数会强制转为无符号数,比较大小时可能出错!负值赋给无符号数,自动执行T2U转换。
short v1 = -12345;
unsigned short u = (unsigned short)v1;
short v2 = (short)u;
cout << u << ' ' << v2 << endl;
unsigned int a = 0;
int b = -1;
cout << (a > b) << endl;
cout << a - 1 << endl;
位扩展与截断
无符号数扩充位使用零扩展,补码数扩充位使用符号扩展。
位扩展时,先转换大小再转换符号,如short v = -12345,则(unsigned)v = 4294954951。
无符号数截断时,丢弃截断位;补码数截断时,丢弃截断位并将结果进行
U2Tk转换。
short v = -12345;
unsigned u1 = v;
unsigned u2 = (unsigned)(int)v;
unsigned u3 = (unsigned)(unsigned short)v;
cout << u1 << ' ' << u2 << ' ' << u3 << endl;
cout << (short)u1 << ' ' << (short)u2 << ' ' << (short)u3 << endl;
int v1 = (1<<16) + (1<<15);
cout << short(v1) << endl;
2.3 整数运算
无符号数加法
x+wuy={x+y,x+y−2w,x+y<2wx+y≥2w正常溢出
补码加法
x+wty=⎩⎪⎨⎪⎧x+y−2w,x+y,x+y+2w,x+y≥2w−1-2w−1≤x+y<2w−1x+y<−2w−1正溢出正常负溢出
无符号数乘法
x∗wuy=(x⋅y)mod2w
补码乘法
x∗wty=U2Tw((x⋅y)mod2w)
无符号和补码乘法的位级等价性
由
x′=T2Uw(x)=x+xw−12w,
y′=T2Uw(y)=y+yw−12w,故
(x′⋅y′)mod2w=[(x+xw−12w)⋅(y+yw−12w)]mod2w=[x⋅y+(xw−1y+yw−1x)2w+xw−1yw−122w])mod2w=(x⋅y)mod2w
带权重
2w和
22w的项取模运算均为0。
乘法优化
整数乘法指令相当慢,约10个机器周期,一般分解为加法、减法、位级运算和移位运算。
对于乘法x*K,K的二进制是一组0和1交替的序列,如14可写为[0…01110]。
考虑一组从位置n到m的连续1(对于14,n=3,m=1),可用下列方式计算乘积:
形式A:(x << n) + (x << (n-1) + ··· + (x << m))
形式B:(x << (n + 1) - (x << m))
如x * 14 = (x << 4) - (x << 1),只需要两次移位和一次减法。
整除舍零偏置
整数除法比整数乘法更慢,约30个机器周期。
向下舍入: 对于任意实数a,定义
⌊a⌋为唯一整数a’,使得
a′≤a<a′+1。
如
⌊3.14⌋=3,
⌊−3.14⌋=−4
向上舍入: 对于任意实数a,定义
⌈a⌉为唯一整数a’,使得
a′−1≤a<a′。
如
⌈3.14⌉=4,
⌈−3.14⌉=−3
向上舍入与向下舍入的关系:
⌈x/y⌉=⌊(x+y−1)/y⌋
证明:令
x=qy+r,其中
0≤r<y,则
⌊(x+y−1)/y⌋=q+⌊(r+y−1)/y⌋。当
r=0时,
⌈x/y⌉=q;当
r>1时,
⌈x/y⌉=q+1。因此,实现了向上舍入。
除以2的幂除法(右移)
无符号除法(向下舍入): 对于无符号数
xw和
k,且
0≤k<w,则逻辑移位
x>>k=⌊x/2k⌋。
证明:
令
x=[xw−1,xw−2,⋯,x0]、
x′=[xw−1,xw−2,⋯,xk]、
x′′=[xk−1,xk−2,⋯,x0],其中
x、
x′、
x′′均为无符号数。
则
x=2kx′+x′′,
0≤x′′<2k,得
x>>k=⌊x/2k⌋=x′。
补码除法(向下舍入): 对于补码
xw和无符号数
k,且
0≤k<w,则算术移位
x>>k=⌊x/2k⌋。
证明:
令
x=[xw−1,xw−2,⋯,x0]、
x′=[xw−1,xw−2,⋯,xk]、
x′′=[xk−1,xk−2,⋯,x0],其中
x、
x′为补码数,
x′′为无符号数。
则与无符号数类似,有
x=2kx′+x′′,
0≤x′′<2k,得
x>>k=⌊x/2k⌋=x′。
补码除法(向上舍入): 对于补码
xw和无符号数
k,且
0≤k<w,则算术移位
(x+(1<<k)−1)>>k=⌈x/2k⌉。
证明:
由
⌈x/y⌉=⌊(x+y−1)/y⌋,令
y=2k,得
⌊(x+2k−1)/2k⌋=⌈x/2k⌉
即
(x+(1<<k)−1)>>k=⌈x/2k⌉。
2.4 浮点数
IEEE浮点表示
V=(−1)s×M×2E
符号s
决定正负数;尾数M
是二进制小数,范围是
1∼2−ϵ 或
1∼1−ϵ;阶码E
用于对浮点数加权。
对于单精度32位浮点数,符号s占1位、阶码占k=8位、尾码占n=23位。
8位阶码字段
exp=e7⋯e0编码阶码
E,23位小数字段
frac=f22⋯f0编码尾数M。
图2 单精度浮点数IEEE标准格式
根据阶码域exp的不同,可分为下列情况:
-
非规格化(阶码全0)
阶码
E=1−(2k−1−1)=−2k−1+2,尾数
M=f,用来表示0以及靠近0的数。
最小非规格数尾码
M=2−n,
V=2−n−2k−1+2。
最大非规格数尾码
M=1−2−n,
V=(1−2−n)×2−2k−1+2。
-
规格化值(阶码非全0或非全1)
阶码
E=exp−(2k−1−1),尾数
M=1+f,其中尾数加1用于获得额外精度。
最小规格化数阶码
E=−2k−1+2 、尾码
M=1,值
V=2−2k−1+2。
最大规格化数阶码
E=2k−1−1、尾码
M=2−2−n,值
V=(1−2−n−1)×22k−1。
-
特殊值(阶码全1)
尾数域全0时,无穷大;尾数域非0时,NaN(Not a Number)。
图3 8位浮点数非负值示例(k=4, n=3)
非规格化数分布在0附近,浮点数并非均匀分布,约靠近原点处约稠密。
整数
12345转为单精度浮点数,二进制数为【11 0000 0011 1001】,小数表示为
1.1000000111001×213,因此存储为:
- 符号域(1位),【0】;
- 尾码域(23位)丢弃开头的1,并在后面补10个0,【100 0000 1110 0100 0000 0000】;
- 阶码域(8位)为13加上偏置127,【1000 1100】;