计算机系统漫游
编译过程
1.预先处理阶段
宏展开,#include展开,得到文本表示的.i文件
2.编译阶段
转换为汇编表示的.s文件
3.汇编阶段
转换为二进制机器指令表示的.o文件
4.链接阶段
链接得到可执行二进制指令文件
系统的硬件组成
1.总线
常被设计为传送定长的字节块,称为字。总线有各种类型,不同类型有不同作用。系统总线,内存总线,I/O总线。
2.桥
连接不同类型总线
3.I/O设备
每个I/O设备通过一个设备控制器[嵌入到系统]或适配器[可插拔]与I/O总线相连
4.主存
物理上,由一组动态随机存取存储器芯片组成
逻辑上,一个线性的字节数组,每个字节有唯一地址
5.处理器
解释或执行存储在主存中指令的引擎
处理器按指令模型操作,模型由指令集架构决定。
5.1.处理器位宽,总线位宽
32 位和 64 位 CPU 最主要区别在于一次能计算多少字节数据:
(1).32 位 CPU 一次可以计算 4 个字节;
(2).64 位 CPU 一次可以计算 8 个字节;
这里的 32 位和 64 位,通常称为 CPU 的位宽,代表的是 CPU 一次可以计算(运算)的数据量。
如果计算的数额不超过 32 位数字的情况下,32 位和 64 位 CPU 之间没什么区别的,只有当计算超过 32 位数字的情况下,64 位的优势才能体现出来。另外,由于总线(传输数据,地址,控制信息)位宽一般等于CPU位宽,故32位CPU最大只能寻址4GB内存。而64位CPU寻址范围则很大,理论最大的寻址空间为2^64。
5.2.软件位宽
64 位和 32 位软件,实际上代表指令是 64 位还是 32 位的
(1).如果 32 位指令在 64 位机器上执行,需要一套兼容机制,就可以做到兼容运行了。但是如果 64 位指令在 32 位机器上执行,就比较困难了,因为 32 位的寄存器存不下 64 位的指令;
(2).操作系统其实也是一种程序,我们也会看到操作系统会分成 32 位操作系统、64 位操作系统,其代表意义就是操作系统中程序的指令是多少位,比如 64 位操作系统,指令也就是 64 位,因此不能装在 32 位机器上。
6.寄存器文件
一个小的存储设备,由一些单个字长的寄存器组成,每个寄存器有唯一的名字
指令集架构描述的是每条机器代码指令的效果,微体系结构描述的是处理器实际如何实现
存储器层次结构
操作系统管理硬件
所有应用程序对硬件的操作尝试必须通过操作系统
基本功能
1.防止硬件被失控的应用程序滥用
2.向应用程序提供简单一致的机制来控制低级硬件设备
进程
并发运行:一段时间内交替运行;并行运行:同一时刻,同时运行。
上下文:操作系统保持跟踪进程运行所需的所有状态信息
上下文切换:操作系统把控制权从当前进程转移到另一进程时发生
具体动作:
保存当前进程的上下文
恢复新进程的上下文
将控制权传递到新进程
管理方:
系统内核[操作系统代码常驻内存的部分]
线程
作为执行单元。同一进程的多个线程,在进程上下文运行,共享同样的代码和全局数据
虚拟内存
每个进程有一个虚拟地址空间。mmap堆区域增长方向实际上是可配置的。下图为向上增长。
文件
文件就是字节序列设备都可看成是文件
系统间利用网络通信
多核,多线程
多核:将多个CPU集成到一个集成电路芯片上
超线程/多线程:允许一个CPU执行多个控制流
指令级并行:处理器同时执行多个指令
超标量:处理器可达到比一个周期一条指令更快的执行速率
单指令,多数据并行:一个执行操纵多个目标数据
汇编
不同机器对应不同汇编语言。本部分介绍基于x84-64的汇编语言。
寄存器
1.PC :下条指令在内存中的地址
2.整数寄存器文件:含16个命名的位置,分别存64位值。
3.条件码寄存器
4.一组向量寄存器
16组寄存器介绍
64 | 32 | 16 | 8 | 描述 |
---|---|---|---|---|
%rax | %eax | %ax | %al | |
%rbx | %ebx | %bx | %bl | |
%rcx | %ecx | %cx | %cl | |
%rdx | %edx | %dx | %dl | |
%rsi | %esi | %si | %sil | |
%rdi | %edi | %di | %dil | |
%rbp | %ebp | %bp | %bpl | |
%rsp | %esp | %sp | %spl | |
%r8 | %r8d | %r8w | %r8b | |
%r9 | %r9d | %r9w | %r9b | |
%r10 | %r10d | %r10w | %r10b | |
%r11 | %r11d | %r11w | %r11b | |
%r12 | %r12d | %r12w | %r12b | |
%r13 | %r13d | %r13w | %r13b | |
%r14 | %r14d | %r14w | %r14b | |
%r15 | %r15d | %r15w | %r15b |
指令源数据:
1.常数
2.取自寄存器
3.取自内存
指令结果存储:
1.寄存器
2.内存
寻址模式:Imm(rb, ri, s),对应的有效地址为Imm+R[rb] + R[ri] * s
例子:
操作数 | 数值 | 描述 |
---|---|---|
$Imm | Imm | 数值为Imm |
ra | R[ra] | 数值在寄存器ra内 |
(ra) | M[R[ra]] | 从寄存器ra内取地址,从地址所在内存处取值 |
Imm(rb, ri, s) | M[Imm+R[rb]+R[ri]*s] | 计算地址为:Imm+R[rb]+R[ri]*s,再从地址所在内存处取值 |
程序的用户栈
从栈底逐渐扩展。扩展方向是虚拟地址不断减少的方向。
指向栈顶对象的虚拟地址小于指向栈底对象的虚拟地址。
条件码寄存器
标志位 | 意义 | 描述 |
---|---|---|
CF | 进位标志 | 最近的操作是否让最高位产生了进位 |
ZF | 零标志 | 最近的操作得出的结果为0 |
SF | 符号标志 | 最近的操作得到的结果为负数 |
OF | 溢出标志 | 最近的操作导致一个补码溢出 |
局部数据需要放在内存的情形:
为了提升效率,一般情况下,局部变量存储在寄存器中。
1.寄存器不足以存放所有本地数据
2.对局部数据用了&
3.局部变量是数组或结构
内存对齐
任何k字节的基本对象的虚拟地址需要是k的倍数。汇编代码中使用.align x,指明全局数据所需的对齐。
举例:
struct s2
{
int i;
int j;
char c;
};
struct s2 a;
struct s2 d[4];
为了保证,任意s2类型实例中字段i,j的地址可被sizeof(int)整除,必须要求:
1.对i,对象内偏移为0;对j,对象内偏移为4。
2.对s2的实例对象,对象起始地址需要可被4整除。
进一步,考虑s2类型数组下,每个数组中对象实例起始地址需要可被4整除,所以,每个s2实例实际占据的内存尺寸是12字节。
函数指针的值
是该函数机器代码表示中第一条指令的地址。
指令流水线
指令分解
指令分解 | 描述 |
---|---|
取值 | PC地址处获取指令 |
译码 | |
执行 | |
访存 | 数据写入内存/从内存读数据 |
写回 | 写结果到寄存器文件 |
更新PC |
顺序执行时的问题
一个时钟周期需要足够大,以使信号能在一个周期内传播所有阶段。
流水线
提高了系统单位时间内服务的顾客总数。对单个顾客,可能会增加完成任务所需时间
流水线的目的是每个时钟周期都发射一条新指令。
需在取出当前指令后,马上确定下条指令位置。
数据相关:下条指令会用到上条指令结果
控制相关:下条指令位置由上条指令确定
用暂停来避免数据冒险
转发【用上一指令计算中间数据作为本指令数据来源】
综合使用暂停+转发
插入气泡【让已进行的指令变为无效】
指令执行速度
CPU 的硬件参数都会有GHz这个参数,比如一个1GHz 的CPU,指的是时钟频率是1G,代表着1秒会产生1G次数的脉冲信号,每一次脉冲信号高低电平的转换就是一个周期,称为时钟周期。
对于 CPU 来说,在一个时钟周期内,CPU 仅能完成一个最基本的动作,时钟频率越高,时钟周期就越短,工作速度也就越快。
一个时钟周期一定能执行完一条指令吗?答案是不一定的,大多数指令不能在一个时钟周期完成,通常需要若干个时钟周期。但通过流水线技术基本可以做到每个时钟周期将一个新的指令投入运行。一个老的指令执行完其最后一个周期。
内联
在原本要调用函数的地方,直接展开,消除了函数调用的开销。
提升程序性能
设计时间复杂度良好的算法
避免不必要的多次求值和函数调用
减少不必要的内存访问
对不可预测的分支,采用条件传送代替分支预测
循环展开技术
利用时间局部性,空间局部性