对应《计算机系统基础》、《数字逻辑与计算机组成》两门课程 以计算机系统基础为主,即 i 386
计算机系统概述¶
- 计算机系统包括:配套的硬件设备和软件系统
- 冯诺依曼结构:运算器,控制器,存储器,输入输出设备
- 基本工作方式是:控制流驱动方式
- 软件和硬件具有逻辑上的等价性,硬件的执行速度快,软件更灵活(频繁使用的应该考虑使用硬件实现),功能上可以相互替代
- 存储程序基本思想:将事先编好的程序和原始数据送入主存后才能执行程序,启动执行后,计算机能在不需要操作人员干预下自动完成逐条指令去除和执行的任务
- 根据 PC 取指令(PC-MAR-M-MDR-IR)
- 指令译码 (IR-CU)
- 取操作数,执行指令 (IP-MAR-M)-MDR-ACC)
- 回写结果,修改 PC 的值,执行下一指令
- 存储器:主存储器,外存储器,MAR 存储器地址寄存器(cpu 送地址),MDR 存储器数据寄存器(收集来的信息),地址译码器,(实际上 MAR、MDR 通常集成在 CPU)
- 控制器:IR 指令寄存器,PC 程序计数器,控制单元;IR 的数据来自 MDR,指令送往控制单元,地址码送往 MAR
- IR、MAR、MDR 对程序员透明(包含汇编程序员)
- 计算机软件的分类:
- 按照功能分类:系统软件、应用软件
- 三个级别语言:机器语言、汇编语言、高级语言
- 翻译程序的分类:汇编程序(汇编-机器),编译程序(高级-汇编、机器),解释程序(逐条翻译,立即执行)
- 计算机的层次结构
- 可执行文件的生成
性能指标¶
- 机器字长:计算机进行一次整数运算,能处理的二进制数据的位数;此外字长一般等于通用寄存器位数、ALU 宽度等
- 通常来说一个字的大小等于字长,是计算机处理数据的基本单位
- 机器字长是 CPU 一次性能处理的最大数据位数,影响运算精确度
- 存储字长是计算机内存中一次可存取的表示数据或指令的位数
- 指令字长是每条指令的标准长度。
- 字长足够时可以表示任何一个整数,但是仍然不能精确表示所有小数
- 主存容量:MAR 的位数反映存储单元数目,MDR 位数反映存储单元字长(如 64 K*32 位)
- 计算机性能评估:吞吐量/响应时间
- CPU 的主频和时钟周期呈倒数关系
- CPI 表示执行一条指令需要的时钟周期数(是所有指令的平均值)
- IPS (IPC)每秒执行多少指令,IPS=主频/平均 CPI
- MIPS:每秒执行多少百万条指令
- CPU 执行时间=指令数目*CPI/主频
- 浮点数的运算指标
数据的表示和运算¶
定点数的编码表示¶
- 原码:表示浮点数的小数部分
- 表示整数的范围是 \(-(2^n-1)\to(2^n-1)\)
- 与真值的转化简单,但是难以实现加减法,并且存在两个 0
- 补码:表示整数
- 正数的补码为其自身,负数各位取反末位加一(即模减去负数的绝对值)综合为 \(2^{n+1}+x\)(补码的本质)
- 表示范围为 \(-2^n\to 2^{n}-1\)
- 特殊值
- 0:0000...
- -1:1111...
- -2147483648:1000...
- 2147483647:0111...
- 双符号位(模 4 补码)
- 00:正数,无溢出
- 01:正溢出
- 10:负溢出
- 11:负数无溢出
- 更容易检查加减运算中的溢出问题
- 存储时仍然只有一位,只是送到 ALU 计算时复制为了两份
- 移码:浮点数的阶码
- 译码就是再真值上加一个偏置值 (通常是最大正数的一半) \([x]_{移}=2^n+x(-2^n\leq x<2^n)\) 使得包括负数在内的所有数值的表示都是正的
- 移码和补码表示实际上只差了一个符号位(移码中 0 表示负数,1 表示正数)(移码实际上和补码差一个 \(2^n\))
- +5和-5分别表示为
10000100
(132,即127+5)和01111000
(122,即127-5) - 使用移码作为阶码的好处:比较大小更为方便,检查特殊值(0,max)比较容易
- 无符号数与有符号数
- C 语言中长变短直接截断;短变长对于无符号整数进行零扩展,有符号整数进行符号扩展
运算方法与电路¶
- 运算器由 ALU、移位器、多路选择器、状态寄存器和通用寄存器等组成
- 带标志加法器
- (有符号)溢出标志位\(OF=C_n\bigoplus C_{n-1}\)
- 符号标志 \(SF=F_{n-1}\)
- 零标志位 \(ZF=1 \ iff \ F=0\)
- (无符号)进位、借位标志\(CF=Cout\bigoplus Cin\)
- \(Cin=sub=1\)(这里这个 1 的作用就是减法时的"各位取反,末位加一")
- 移位操作
- 逻辑移位:左移出 1 是发生溢出
- 算术移位:右移补符号,移出 1 会损失精度;左移前后符号位不同,发生溢出
加减法¶
$$ \displaylines{ [X+Y]_补=[X]_补+[Y]_补(mod 2^n)\ [X-Y]_补=[X]_补+[-Y]_补(mod 2^n)\ } $$ - 做加法直接补码相加,做减法将被减数与减数的负数补码相加,最终运算结果的高位丢弃。 - 发生溢出的判断:两个正数相加结果为负数,两个负数相加结果为正数 - 加减法电路 - - sub 为 1 时可以实现 \(X+\overline Y+1\) - 无符号数大小比较 - ZF=1 表示 A=B - ZF=0 CF=0 表示 A>B - ZF=0 CF=1 表示 A\B - OF^SF=1 ZF=0 表示 A\<B - 对于原码加减法,分开处理符号位和数值位
原码定点数乘除法¶
- 乘法原理
- 不溢出的条件(假设 32 位相乘):无符号数高 32 位为 0,有符号数为符号位
- 乘法运算电路
- C 存储加法 p 的进位,每次根据 Y 的最低位决定是否进行加法
- 除法原理
- 首先需要将 n 位的被除数扩展到 2 n 位,小数低位补 0,整数高位补 0
- 用 2n 的被除数除以 n 位除数得到的商位 n+1 位,如果第一次试商的结果为 1 则表示发生了溢出(无法用 n 位进行表示)
- 除法运算电路
- 每次在高位进行试减,根据结果对 Q 左移并上商
- 最终 Q 中存储商,而 R 中存储余数
浮点数的运算与表示¶
- 使用浮点数表示法既扩大了数的表示范围,又保持了数的有效精度
- 符号位为1/0,尾数阶数全为0表示0
- 阶数为0,尾数不为0表示非规格化浮点数,即为了可以表示更小的浮点数,通过去掉前导的1来实现
- 尾数为0,阶数全1表示正负无穷 inf
- 尾数不为零,阶数全为1表示非数字 NaN
- 规格化尾数的要求
- 规格化的目的:可以多表示一位,增加数据的表示精度
- 补码表示尾数要求第一位与补码符号位不同
- 4 为基要求前两位不同
- 浮点数的规格化
- 左规:当尾数的最高位不为 1 时,进行左规,左移 1 位阶码减 1
- 右规:当尾数的有效位进到了小数点前面,需要进行右规,右规只会进行一次,尾数右移阶码加 1(尾数舍入可能导致右规,并且只有右规可能导致舍入,因为左规移出的都是 0)
- 单精度浮点数的偏置常数为 127 而不是 128(因为实际上移码只用了 154 个,剩下 2 个表示特殊含义)
- 浮点数的加减运算
- 对阶:使操作数小数点对齐(阶码相同),将小阶码向大的看齐(尾数右移一位阶码加一)对阶不会导致溢出
- 尾数加减(注意还原隐藏位)
- 尾数规格化:右规/左规
- 舍入:在对阶和右规中需要对尾数右移,为了保证精度,一般先将移出的部分低位保留下来,参加中间过程的运算,最后再进行舍入(定点数不存在舍入)
- 就近舍入:0 舍 1 入
- 正向舍入:取右边最近可表示数
- 负向舍入:取左边最近可表示数
- 截断法:丢弃多余位(向原点舍入)
- 溢出判断
- 指数上溢:右规时发生
- 指数下溢:左规可能导致
大端与小端存储¶
- lsb、msb 表示最低、高有效位
- 如 int:01234567 H 的 MSB=01H LSB=67H
- 大端方式:msb 所在地址为数的地址
- 小端方式:lsb 所在地址为数的地址
- 如:
FF AF 00 23
- 从大端还原:
FF AF 00 23
- 从小端还原:
23 00 AF FF
- 从大端还原:
存储对齐¶
- CPU 访问主存时只能一次读取或写入若干特定位,因此如果数据的存储不满足这个规律则会导致对统一数据需要多次存取才能完整的到数据,会降低效率,因此需要进行对齐
- 结构体对齐
层次存储系统¶
- 按照层次分类
- 主存储器:内存,可以直接访问
- 辅助存储器:外存,调入主存之后才能被 CPU 访问
- 高速缓冲器(Cache):位于主存和 CPU 之间,通常在 CPU 中
- 按照存取方式分类
- 随机存储器 RAM:随机读取,并且时间物理位置无关,用作主存、高速缓冲器
- 只读存储器 ROM:一旦写入就不能更改(断电也不会丢失)
- 顺序存取存储器 SAM:数据按顺序从存储载体的始端读出或写入,因而存取时间的长短与信息所在位置有关。例如:磁带
- 直接存取存储器 DAM:直接定位到读写数据块,在读写数据块时按顺序进行。如磁盘。
- 相联存储器 CAM:按内容检索(而不是根据位置)到存储位置进行读写。例如:快表。
- 串行访问寄存器:按照物理位置顺序寻址 (如硬盘,光盘)
- 信息的可保存性
- 易失性:断电后存储内容消失如 RAM,Cache
- 非易失性:如 ROM
- 层次存储结构
- 将上一层的存储器作为低一层存储器的高速缓存,数据总是在相邻的两层之间传送 (除了 CPU 和主存)
- 时间局部性:刚被访问过的单元可能不久又被访问
- 空间局部性:刚被访问的单元的临近单元可能不久被访问
性能指标¶
- 存储容量
- 存储成本:每位加个=总成本/总容量
- 存储速度
- 存取时间 \(T_{a}\):从 CPU送出内存单元的地址码开始,到主存读出 数据并送到 CPU(或者是把 CPU 数据写入主存)所需要的时间,分读取时间和写入时间
- 存取周期 \(T_{m}\):连读两次访问存储器所需的最小时间间隔,即存取时间+恢复时间
- 存储器带宽 \(B_{m}\):每秒从主存进出信息的最大数量
主存储器¶
- RAM 的分类
- SRAM:双稳态触发器,速度快,非破坏性读出,功耗大价格贵,用于 cache
- DRAM:使用一个晶体管,密度高功耗低,速度慢,需要定时刷新以及读后再生,用于主存
-
二维地址译码系统:
- 对于 DRAM 通常来说 x y 共用地址线(分时复用),因此添加一条地址线寻址范围编程 4 倍(为了更好的进行地址复用,以及减少行刷新的次数,通常 \(l\leq r\))而 SRAM 通常不适用分时复用
- 对于 16 M_DRAM (4*4 M):\(16M=2^{11}2^{11}4\),即 11 根地址线分时复用;需 4 个位平面,对相同行、列交叉点的 4 位一起读/写
-
DRAM 的电容只能维持 1-2 ms,并且独处后也需要再生,因此要进行刷新:每隔一段时间,将原内存的数据读出经过刷新放大器并重新写入,占用一个存取周期
- 集中刷新:在集中刷新期间不能正常访问存储器
- 分散刷新:将存储器系统的工作周期划分,前半部分读写,后半部分进行刷新,没有死区但是存取周期延长
- 异步刷新:将刷新时间除以行数,使得相邻两行之间刷新的时间间隔为他,使得死时间更加分散(这就是为什么 DRAM 要行刷新)
- 传统 DRAM 与 CPU 之间采用异步方式交换数据,CPU 发出地址和控制信号后,经过一段延迟时间,数据才读出或写入。在这段时间里,CPU 不断采样 DRAM 的完成信号,CPU 插入等待状态而不能做其他工作。而 SDRAM 芯片则不同,其读写受外部系统时控制,因此与 CPU 之间采用同步方式交换数据。它将 CPU 或其他主设备发出的地址和控制信息锁存起来,经过确定的几个时钟周期后给出响应。因此,主设备在这段时间内,可以安全地进行其他操作。
内存的物理构成¶
- 行缓冲是对某一行的所有列进行缓冲
- 单个内存条由多个存储芯片共同构成(图中就是 16 个),每次只能对一个芯片进行访问、操作
- 轮流启动方式,一次只能读取一个芯片,假设模块的存取周期为 T,总线周期为 r,那么为了保证高效率的轮流启动,应该保证模块数目 \(m\geq \frac{T}{r}\) 这样读取 m 个字(模块)需要的时间为 \(T+(m-1)r\),如果相邻的 m 次访问出现在同一个模块内,就会发生访存冲突(上一次还没完事)
- 为什么要进行内存对齐
- 一次读取是对内存条中多个存储芯片进行读取,即只能是(116,1732...)
- 交叉编址
- 扩展存储
- 位扩展和字扩展都会增大存储的容量
- 位扩展:若干片位数较少的存储器芯片构成给定字长的存储器。如用 8 片 4 Kx 1 位的芯片构成 4 K x 8 位的存储器,需在位方向上扩展 8 倍,而字方向上无须扩展。不改变控制位的数目 (只能同时读取)
- 字扩展:字扩展是容量的扩充,位数不变。例如,用 16 Kx 8 位的存储芯片在字方向上扩展 4 倍,构成一个 64 Kx 8 位的存储器。字扩展会增加控制位的数目(可以不同时读取)
- 使用字扩展时,地址位数或增加(在高位增加选片信号)
- 选片信号通过译码器输送到芯片(如 01->1101)
- 如 4 个 16 K*8
xx|xxxxxxxxxxxxxx
前两位为片选信号
- 选片方法
- 线选法(相当于译码之后)
- 译码片选法(译码之前的数据),需要的地址线数目更少
主存储器的读取与写入¶
- 对于使用分时复用的 DRAM 芯片:地址引脚数=log (max (行数,列数))
- 行号->行缓冲->列号->读取单元
外部存储器¶
- 柱面是指由同一磁头在同一磁盘上扫描时能够覆盖到的所有磁道的集合。简单来说,柱面就是位于不同磁道但在同一半径位置上的所有磁道的集合。
- 所有磁头同步移动位置相同(不同磁盘处于相同位置的扇区属于同一个柱面,但同时只有一个磁头在使用)
磁盘的管理¶
- 初始化
- 新的磁盘只是磁性记录材料的空盘,存储数据之前要划分为扇区,以便进行读写操作进行物理格式化(低级格式化),每个扇区通常由头部尾部(控制、校验信息等)以及数据区域组成
- 分区
- 分区的起始扇区和大小记录在磁盘主引导记录的分区表中
- 磁盘使用之前先进行分区,然后对物理分区进行逻辑格式化(高级格式化)将初始文件系统数据结构存储到磁盘上
- 引导块
- 完整功能的引导程序一般在磁盘的启动块上
- 引导 ROM 中的代码指示磁盘控制器读入引导块并开始执行,加载运行操作系统
- 坏块
- 坏快会在表中表面,不会被程序使用
磁盘的性能指标¶
- 记录密度:盘片单位面积上记录的二进制信息量
- 分为道密度和位密度
-
磁盘容量
- 与磁表面大小和记录密度密切相关
- 磁盘的格式化后容量(数据区的大小)小于为格式化容量(总容量)
- 记录面数*柱面数*每条磁道的磁化单元数
- 记录面数*柱面数*每条磁道的磁化单元数
-
磁盘的读取速度
- T=磁盘控制器时间+寻道时间+旋转等待时间+数据传输时间
- 寻道时间:磁头移动到指定柱面的时间(5 ms)
- 旋转等待:扇区旋转到来的时间(4-6 ms)由磁盘的转速决定
- 数据传输时间(0.01 ms/扇区)
-
磁盘的读取步骤
- 磁头内外移动到指定的柱面
- 选择磁头
- 等待磁头目标扇区的到来
- 读写扇区数据
- 磁盘的地址表示:
柱面|盘面(磁头)|扇区
- 磁盘控制器是一个内置固件的硬件设备,它能将主机送来的请求逻辑块号转换为磁盘的物理地址(柱面号、磁头号、扇区号),并控制磁盘驱动器进行相应的动作。
磁盘性能改进¶
常见磁盘调度算法 - 根据读取同样数据下磁头移动磁道数目比较调度算法性能 - FCFS:根据进程访问磁盘的先后顺序进行调度,由于空间的局部性通常效率还可以 - 最短寻道时间优先 SSTF:每次调度选择当前磁头最近的磁道,使每次的寻道时间最短。(存在饥饿问题,可能总是在一个小范围反复移动) - 扫描算法 SCAN:为了避免 SSTF 的饥饿问题,规定只有磁头移动到最外侧磁道时才能向内移动,移动到最内侧磁道时才能向外移动,即规定了移动方向的 SSTF,页叫做电梯调度算法。 - 对最近扫描过的区域不公平,访问局部性方面相对较差 - 循环扫描 C-SCAN:在 SCAN 的基础上福鼎磁头只能单向移动来提供服务,另一方向只快速移动至起始端,而不在这个过程处理请求。 - LOOK/C-LOOK:改进 SCAN 移动到需要扫描的最里和最外(而不是严格的磁盘边界)
- 改善对棋盘扇区的编号改善旋转等待
- 由于磁头读入一个扇区后需要短暂处理,因此无法读取两个连续的扇区,因此可以对扇区间隔排号,这样就可以实现连续读取
- 此外由于盘面是同步转动的,因此 0 号 7 后面要接 1 号的 0,与上一条同理,也要实现间隔排列
高速缓存 Cache¶
- 时间的空间局部性:刚被访问过的单元的邻近单元很可能不久被访问(数组)
- 时间局部性:刚被访问过的单元很可能不久又被访问(循环)
- cache 以块为单位(如 64 字节)进行存取,主存和 cache 是被分成大小相同的块,缓存就是指主存中的块送到 cache 中
缓存的映射方式¶
- 直接映射:每个主存块映射到 cache 的固定行
- 依据规则进行映射:加入 Cache 有 16 行,那么第 100 块主存应该映射到
4=100 mod 16
- 缺点:频繁换进换出,命中率第,cache 利用率也低(空闲)
- 若 cache 有 \(2^c\) 行,主存有 \(2^m\) 块,块大小大小为 \(2^b\) 字节。即编码中 cache 行号有 \(c\) 位,主存块号有 \(m\) 位,块内地址 b 位。标记字段长度 \(t=m-c\)(块群的数目)
- 主存地址
0000001 0010 000001100
->块号00000010010
(群号0000001
行号0010
)块内地址000001100
- cache 比哦啊汇总只需要存储 tag 就可以判断这一行对应的主存块
- 此外 cache 还存在一个有效位 V 表示块内是否有缓存
- 从 cache 中读取数据,找到对应的行,用群号比较 tag 并检查 v 是否有效从而判断是否命中
- 其中块的大小为 DATA 的大小,完整大小还要加上 V 以及 Tag
- 命中时间最短(固定位置,比较快)
- 依据规则进行映射:加入 Cache 有 16 行,那么第 100 块主存应该映射到
- 全相联映射:任一行
- tag 中存储完整的主存块号
- 查询时需要与所有 cache 块的 tag 进行比较,才能知道是否命中,需要大量的比较器进行比较,时间硬件的开销比较大
标记|快内地址
- 组相联映射:映射到 cache 固定组中的任一行
- 对 cache 分组,一个主存块只能映射到固定组的任一行中
- 通常来说使用 2/4 路组相联映射
- 若 cache 有 \(2^c\) 行,分成 \(2^q\) 组,即每组 \(2^{c-q}\) 行,主存有 \(2^m\) 块,块大小大小为 \(2^b\) 字节。即编码中 cache 行号有 \(c\) 位,主存块号有 \(m\) 位,块内地址 b 位。标记字段长度 \(t=m-q\)
标记|组号|块内地址
- 寻找一个块时到对应的组中并行比较 tag
- 需要的比较器数目等于一个组内的 cache 数目 (比如直接映射为 1,全相联为 n),比较器的位数就是 tag 的位数
- 性能评估:
- 命中率: \(t=at_c+(1-a)(t_c+t_m)=t_c+(1-a)t_m\)
- 平均消耗时间等于命中率乘以命中时间加上缺失率乘以命中时间加上缺失损失
- 对于有多级缓存的情况,所有缓存都没命中才是没命中(任一级命中了就是命中)
- 关联度:一个主存块映射到 cache 中时可能存放的位置个数
- 命中率: \(t=at_c+(1-a)(t_c+t_m)=t_c+(1-a)t_m\)
Cache 替换算法¶
- FIFO
- LRU
- LRU 具体实现时,并不是通过移动块来实现的,而是通过给每个 cache 行设定一个计数器,根据计数值来记录这些主存块的使用情况。这个计数值称为LRU 位。
- 最上面表示输入,红色表示 LRU 位,黑色表示块号
写缓存的一致性问题¶
- 因为 Cache 中的内容是主存块副本,当对 Cache 中的内容进行更新时,就存在 Cache 和主存如何保持一致的问题。
- 全写法:写 cache 命中时,必须将数据同时写入到 cache 和主存,降低了效率
- 通过写缓冲改善
- 回写法:
- 写命中时只把数据写入 cache 而不是立即写入到主存,当此块被替换(离开 cache)时再写主存(使用脏位记录是否需要回写)
- 写不命中:
- 写分配法:更新主存单元之后调入 cache
- 非写分配法:只更新主存单元,不调入 cache
虚拟存储器&地址转换¶
- 把地址空间和主存容量的概念区分开来。程序员在地址空间里编写程序,而程序则在真正的内存中运行。使得程序员。可以在比实际主存空间大得多的逻辑地址空间中编写程序
- 指令执行时,通过硬件将逻辑地址(也称虚拟地址或虚地址)转化为物理地址(也称主存地址或实地址)
- 在发生程序或数据访问失效 (缺页)时,由操作系统进行主存和磁盘之间的信息交换
分页式¶
- 由于磁盘很慢,因此缺页的代价很大,为了提高命中率使用全相联映射,并且页必须采用回写法。(同理页的大小也大于 cache 的块)
- 操作系统会为每个进程生成页表,通过页表实现逻辑地址到物理地址转换
- 页表:
- 装入位表示是否为空,修改位(脏位,是否修改了数据需要回写),替换控制位(如 LRU 标记位),其他(如访问权限、禁止缓存等)
- 快表 TLB:
- 采用组相联映射(页表在主存,TLB 没有命中相当于 cache 没有命中,代价没那么大)
- 为了减少访问主存的次数,把经常要查的页表项放到 Cache 中
- 先在快表中查找,找不到则到慢表中查找
- 快表需要 tag 以及比较器是因为不是按顺序排列并且存在缺号,而慢表中不需要使用 tag 因为是按顺序连续存储的,可以直接通过 index 进行访问
- CPU 的访存过程
- 不在主存中则也不在 cache 中
- 不在 TLB 中则也不在页表中
- 不在页表中则也不在主存中,更不在 cache 中
- TLB 以及 cache 的缺失都可以由硬件解决,但是缺页由操作系统解决
- 对地址组成的进一步分解
- \(VA=VPN+VPO=(TLBT+TLBI)+VPO\)
- \(PA=PPN+PPO=(CT+CI)+CO\)
- 例子
段页式¶
-
分段式:
- 不等长分配,按照需求进行分配
- 如代码段,只读数据段等不同区域
- 段的信息存储在段表中
- 地址要加上段内偏移量
-
段页式:程序的虚拟地址空间按模块分段、段内再分页,进入主存仍以页为基本单位
- 使用页来实现虚拟内存的管理
- 使用段来实现权限管理(至少分为管理模式和用户模式)
-
段页式的地址转化(IA 32)
- 逻辑地址<--->线性地址(分段)<--->物理地址(分页)
- 逻辑地址由 48 位组成,包含 16 位段选择符(使用段寄存器获取到段选择符,再获取(段描述符)段表项中关于段的信息)和 32 位段内偏移量
- 线性地址 32 位(其位数由虚拟地址空间大小决定)
- 这里页目基址是 20 位,表相中存储的页表项地址,和物理块地址也都是 20 位
- 物理地址 32 位(其位数由存储器总线中的地址线条数决定)
指令系统-I386 *指令系统-RISC-5¶
- 预处理:在高级语言源程序中插入所有用 # include 命令指定的文件,展开用 # define 声明指定的宏。
- 编译:将预处理后的源程序文件编译生成相应的汇编语言程序。
- 汇编:由汇编程序将汇编语言源程序文件转换为可重定位的机器语言目标代码文件。
- 链接:由链接器将多个可重定位的机器语言目标文件以及库例程 (如 printf ()库函数)链接起来,生成最终的可执行目标文件。
- ISA 规定的内容
- 指令格式:寻址方式,操作类型
- 操作数的类型:存执方式,存储方式(大小端)
- 程序可访问的寄存器编号个数及位数
- 指令执行过程的控制方式,程序计数器条件码定义等
- Tip:控制信号属于微架构层面的实现细节,不是 ISA 层面的抽象定义
指令系统¶
- 指令=操作码+操作数,指令的长度记为指令字长,指令字长等于机器字长的称为单字长指令,此外指令长度通常也应该为字节的整数倍
寻址方式¶
- 操作数的位置:指令中(立即数),寄存器中,主存中
- 指令寻址:
- 顺序寻址: PC 自增
- 跳跃寻址:跳跃指令,修改 PC
- 操作数寻址:
- 多样的寻址方式是为了能够方便的访问数组、结构、联合等复合数据类型
- 寄存器寻址分为直接和间接
- 相对寻址是相对下一条指令的位置,因为执行指令时 PC 已经完成了自增 8
- 其他寻址方式
- 隐含寻址:一些运算使用约定的固定寄存器
- 堆栈寻址:如读写堆栈都隐含了 SP 的信息
常用指令¶
- 传送
- MOV:一般传送
- PUSH/POP:入栈/出栈
- LEA:地址传送指令
- LEA:加载有效地址,如 leal (%edx,%eax),%eax”的功能为 R[eax]←R[edx]+R[eax](mov 为:R[eax]←M (R[edx]+R[eax]),差一个 M)
- 因此也可以用 lea 指令替代加法
- 定点算数
- 位运算
- TEST:做“与”操作测试,仅影响标志
- 控制转移
- JMP DST:无条件转移到目标指令 DST 处执行
- Jcc DST:cc 为条件码,根据标志(条件码)判断是否满足条件,若满足,则转移到目标指令 DST 处执行
- SETcc DST:将条件码 cc 保存到 DST
- CALL DST:返回地址 RA 入栈,转 DST 处执行
- 将返回地址(call 指令下面一条地址)入栈
- 跳转到指定地址处执行
- leave: 将 esp 移至 ebp,然后从栈中 pop 到 ebp(回复 ebp 和 esp)
- RET:从栈中取出返回地址 RA 到 eip 中,转到 RA 处执行
补充:RISC-V¶
- 扩展(变长)操作码编码
- 设某指令系统指令字是 16 位,每个地址码为 6 位。若二地址指令 15 条,一地址指令 34 条,则剩下零地址指令最多有多少条?
- 不同的指令之间不能互为前缀和
- 二地址指令中地址占据 12 位,故剩下 4 位作为操作码,即 15 种(实际使用 0000~1110)
- 一地址指令有 10 位可以作为操作码,以二地址不使用的 1111 作为前缀
- 11110+(0000~11111) 32 种
- 11111+(00000~00001) 2 种
- 零地址,使用 16 位作为操作码
- 11111+(00010~11111)+(000000~111111)
- \(30*2^6\) 种
C 语言的机器级表示¶
过程调用¶
- 调用者过程: - 准备入口参数(按照从右到左压栈) - 保存返回地址,将控制转移到被调用者(CALL) - 被调用者过程: - 初始化栈帧,将 ebp 旧值 push,将 ebp 指向 esp,再将 esp 偏移到新的栈顶 - 保存被调用者保存寄存器,为自己的非静态局部变量分配空间 - 执行函数体 - 恢复调用者的现场,释放局部变量空间,恢复栈帧,esp 回到 ebp,从栈中取出 ebp 的旧值(leave) - 取出返回地址,控制转移到调用者(ret)
-
调用者保存寄存器:调用在调用其他函数之前自行保存,结束后自己恢复
-
被调用者保存寄存器:如果使用了这些寄存器,应该先进行压栈保存,在函数返回之前进行还原
-
值传递与地址传递(指针)
- 值传递使用 move 指令
- 地址传递使用 lea 指令
- 递归函数:每增加一次调用,就需要添加一系列的额外指令用于处理栈帧
- 循环与条件语句
- 使用条件跳转指令和标志号实现
- switch 使用跳转表表示取值与跳转标号之间的对应关系
复杂数据结构¶
- 数组可以定义为静态存储型 (static)、外部存储 (extern)、自动存储型 (auto),或者定义为全局静态区数组,其中,只有 auto 型数组被分配在栈中,其他存储型数组都分配在静态数据区。
- 全局数组
- 局部变量数组
- 结构体
- 与数组类似,获取成员也是通过基址加偏移量的方式
数据对齐¶
- CPU 访问主存时只能一次读取或写入若干特定位。因此如果数据的存储不满足这个规律则会导致对统一数据需要多次存取才能完整的到数据,会降低效率,因此需要进行对齐
- linux:short 型为 2 字节边界对齐,char 不对齐,其他(包括自定义数据类型如 struct)的如 int、double、long double 和指针等类型都是 4 字节边界对齐(即为 4的倍数)。
CISC 与 RISC 对比¶
- CISC 的特点
- 指令系统复杂庞大,一般为 200 条以上
- 长度不固定,指令格式多,寻址方式多
- 任何指令都可以访存
- 各种指令使用频率相差较大,并且执行时间的差距也较大(大多需要多个时钟周期)
- 控制器大多使用微程序控制,有些指令非常复杂,难以使用硬连线控制
- 难以优化编译生成高效的目标代码程序
- RISC 的特点
- 选取使用频率最高的一些简单指令,使用简单指令组合来实现复杂指令
- 指令长度固定,指令格式、寻址方式少
- 只有 LOAD 和 STORE 指令才能访存,其他指令只能访问寄存器
- CPU 中通用寄存器数目较多
- 采用指令流水线技术,大部分指令在一个时钟周期完成
- 以硬布线为主,较少使用微程序
- 中序编译优化工作
- 对比
- RISC 更充分利用芯片面积 (控制器硬布线,占用小)
- RISC 能提高运算速度,大多指令一个时钟周期完成
- RISC 便于设计,成本较低,并且可靠性高。
- RISC 有利于进行编译优化
程序的链接¶
- 为什么要进行链接:
- C 语言是分文件编译的,在分别进行处理得到可重定位文件后需要通过链接将符号替代为对应的地址得到可执行二进制文件
- 使用链接可以更好的实现模块化、高效(并行)编译等
目标文件格式¶
- 可重定位目标文件 (. o)
- 其代码和数据可和其他可重定位文件合并为可执行文件
- 每个. o 文件由对应的. c 文件生成
- 每个. o 文件代码和数据地址都从 0 开始
- 可执行目标文件 (默认为a.out)(exe)
- 包含的代码和数据可以被直接复制到内存并被执行
- 代码和数据地址为虚拟地址空间中的地址
- 共享的目标文件 (. so)(dll)
-
特殊的可重定位目标文件,能在装入或运行时被装入到内存并自动被链接,称为共享库文件
-
ELF 文件
- 提供一种灵活、跨平台的方式来存储程序、库和其他二进制文件的代码和数据。它定义了文件的结构,包括文件头、段和程序头表,这样的结构使得 ELF 文件可以被用作多种类型的二进制文件。
- 上面提到的.o 文件和.so 文件都是遵循 ELF 标准的目标文件格式。
- ELF 的两种视图:链接视图、执行视图
链接视图:可重定位文件¶
- 将文件划分为不同的节
- bss 无需放初始值,只要说明未来执行时每个变量占据多少空间(在未来执行时预留相应的空间),实际上bss 不占用磁盘空间,提高磁盘利用率
- ELF 头
- 给出文件类型、机器结构、目标文件版本、起始虚拟地址(重定位文件都是 0),头大小,节头表偏移量,节头表项数、一项的大小等
- 节头表
- 描述具体每个节的节名,在文件中的偏移量、大小、对齐方式等信息
执行视图:可执行文件¶
- 定义的所有变量和函数已有虚拟地址
- 符号引用处已被重定位,以指向所引用的定义符号
- 可被 CPU 直接执行,指令地址和指令给出的操作数地址都是虚拟地址
-
多了一个程序头表(段头表)
- 由功能类似的节合并得到段:ELF 头、程序头表、. init 节、. fini 节、. text 节和. rodata 节合起来可构成一个只读代码段;. data 节和. bss 节合起来可构成一个可读写数据段。在可执行文件启动运行时,这两个段必须装入内存且需要为之分配存储空间
- 段头表中的表项就描述了数据段到存储空间的映射关系,以及首字节偏移量,首字节虚拟地址,字节数,对齐方式等信息
- 由功能类似的节合并得到段:ELF 头、程序头表、. init 节、. fini 节、. text 节和. rodata 节合起来可构成一个只读代码段;. data 节和. bss 节合起来可构成一个可读写数据段。在可执行文件启动运行时,这两个段必须装入内存且需要为之分配存储空间
-
可执行文件的存储器映像
- 只读代码段总是映射到从虚拟地址为 0x8048000 开始的一段区域;
- 可读写数据段映射到只读代码段后面按 4 KB 对齐的高地址上,其中. bss 节所在存储区在运行时被初始化为 0 。
- 运行时堆则在可读写数据段后面 4 KB 对齐的高地址处,通过调用 malloc 库函数动态向高地址分配空间
- 而运行时用户栈则是从用户空间的最大地址往低地址方向增长。
- 堆区和栈区中间有一块空间保留给共享库目标代码,栈区以上的高地址区是操作系统内核的虚拟存储区。
- 可执行文件的加载过程
- 当启动可执行文件时,先使用常驻内存的加载器根据程序头表将可执行文件中的只读代码段和可读写数据段通过页表建立映射,然后启动可执行目标文件中的第一条指令执行
- 可执行文件都统一映射到虚拟地址空间,不需要关心具体存放位置,这简化了连接器的设计
- 加载时,只读代码段和可读写数据段对应的页表项都被初始化为“未缓存页" 。因此,程序加载过程中,实际上并没有真正从磁盘上加载代码和数据到主存,而是仅仅创建了只读代码段和可读写数据段对应的页表项。只有在执行代码过程中发生了"缺页“异常时,才会真正从磁盘加载代码和数据到主存。
符号解析¶
- 确定标号引用关系
符号表与符号类型¶
- 符号的分类
- 全局符号:非 static 的函数和全局变量(可以被其它文件引用)存储在 .symtab 节中
- 外部符号:其它模块定义的全局变量,被引用
- 局部符号:static 定义的定义的具有文件作用域的变量及函数(不含局部变量)
- .symtab 表项
- name:符号的字符串在 strtab 节中的偏移量
- value:对应 text/data/bss 节中的偏移量
- size:大小
- type:符号对应的类型,数据、函数、节等
- binding:符号类别,全局符号、局部符号、弱符号等
- 此外 ABS 表示不该重定位,UND 表示未定义,COM 表示未初始化
- 函数名和已初始化的全局变量名是强符号
- 强符号不能多次定义,强符号只能被定义一次,否则链接错误
- 若一个符号被定义为一次强符号和多次弱符号,则按强定义为准,对弱符号的引用被解析为其强定义符号
- 未初始化的全局变名、函数声明是弱符号
- 若有多个弱符号定义,则任选其中一个,并输出警告信息
静态链接库¶
- 将所有相关的目标模块(. o)打包为一个单独的库文件 (. a),称为静态库文件,也称存档文件
- 在构建可执行文件时只需指定库文件名,链接器会自动到库中寻找那些应用程序用到的目标模块,并且只把用到的模块从库中拷贝出来
- 链接&解析过程
- E:将被合并以组成可执行文件的所有目标文件
- U:当前所有未解析的引用符号
- D:当前所有定义符号的集合
- 开始时 EUD 为空,按照顺序对文件进行扫描,将 main 加入 D,将其中未定义符号加入 U,main. c 加入 E 继续扫描后面的文件
- 如果当前文件中有和 U 中符号匹配的项,那么将这个项移入 D,将这个文件加入到 E(否则丢弃),并将这个文件中未定义的符号加入到 U
- 之后扫描默认库文件仅需进行解析
- 全部结束后,U 一定为空,D 中符号唯一,E 中就是要被合并的文件
- 由于链接解析是按照顺序进行,因此命令中的文件顺序也会影响解析的成功与否
- 统一个库也可以在文件中重复出现
重定位¶
- 合并. o 文件;确定标号地址;在指令中填入新地址;
- 将可执行文件中符号引用处的临时地址修改为重定向后的地址信息
- 目标文件中哪些引用符号需要重定位、所引用的是哪个定义符号等,这些称为重定位信息,放在重定位节 (. rel. text 和. rel. data) 中。
- 步骤
- 合并相同的节
- 对定义符号重定位,确定在虚拟地址空间中的新地址
- 对引用符号进行重定位:修改. text 节和. data 节中对每个符号的引用(需要用到在. rel_data 和. rel_text 节中保存的重定位信息)将符号(临时地址)替换为具体的地址(相对/绝对)
- .rel 节的结构
重定位节 '.rel.text' at offset 0x380 contains 2 entries: 偏移量 信息 类型 符号值 符号名称 00000007 00000301 R_386_32 00000000 .data 00000010 00000e02 R_386_PC32 00000000 puts 重定位节 '.rel.data' at offset 0x390 contains 2 entries: 偏移量 信息 类型 符号值 符号名称 00000098 00000601 R_386_32 00000000 .rodata 0000018c 00000d01 R_386_32 00000000 do_phase
- 这里的偏移量对应的就是节内偏移量(text/data)
- 这里第 10 行的 call 就对应 .rel.text 中的 puts
重定位的具体过程¶
相对位置重定位 R_386_PC_32¶
- 对于这样的汇编
- 在 rel.text 中可以找到对应的表项
- 根据 r_sym 从符号表得到信息 (swap 函数)
- 假设 main 的起始位置为
0x8048380
转移的目标地址就是PC+偏移地址
PC=0x8048380+0x07-0xfcffffff=0x804838b
(就是节开始位置,减去符号偏移量减去长度)得到的就是执行这条指令时 PC 的值(已完成自增)- 假设 swap 紧跟在 main 后面 (4 字节对齐),并且 main 大小 0x12B 那么重定位值
偏移量=0x8048394-0x804838b=0x9
绝对位置重定位 R_386_32¶
- 直接修改为对应定义的绝对地址即可
动态链接¶
- 静态库的缺点
- 库函数被包含在每个运行进程的代码段中,对于并发运行上百个进程的系统,造成极大的主存资源浪费
- 程序员需关注是否有函数库的新版本出现,必须定期下载、重新编译和链接,更新困难、使用不便
- 共享库是一个目标文件,包含代码和数据
- 从程序中分离出来,磁盘和内存中都只有一个备份
- 可以动态地在装入时或运行时被加载并链接
- 在内存中只有一个备份,被共享,节省内存空间
- 共享库升级时,被自动加载到内存和程序动态链接
- 加载时链接:程序启动时,操作系统加载器自动将程序依赖的动态共享库加载到内存中的过程。
- 运行时链接:运行时链接是指程序在执行过程中,根据需要动态加载和链接共享库的过程。这通常通过程序调用特定的 API(如 UNIX/Linux 下的
dlopen
,Windows 下的LoadLibrary
)实现。- 这种方式为程序提供了更大的灵活性,允许它只在需要时加载特定的库,甚至可以在运行时决定加载哪个版本的库。
共享库的链接与位置无关代码 PIC¶
- 共享库代码是一种 PIC
- 共享库代码的位置可以是不确定的
-
即使共享库代码的长度发生变化,也不影响调用它的程序
-
模块内过程调用:PC 相对寻址
- 模块内数据访问(如模块内的变量)
- 模块外的数据访问
- 模块外的过程调用
- 也可以使用类似的方法(加载时进行重定位)
- 延迟绑定(在第一次函数调用时执行重定位)
- 延迟绑定技术的开销主要在第一次过程调用,需要额外执行多条指令,而以后每次都只是多执行一条指令,这对于同一个外部过程被多次调用的情况非常有益。
中央处理器¶
- MAR、MDR、IR 是 CPU 内部工作寄存对汇编程序员也是不可见的
- 基址寄存器、标志寄存器、PC 以及通用寄存器组是可见的
- 指令执行过程(指令周期)
- 取指周期:取指令,PC+1,指令译码
- 间址周期:如果使用间接寻址,那么需要获得操作数的有效地址
- 执行周期:取操作数,进行运算,存结果
- 中断周期:检测 CPU 中断中断请求,是够需要关中断、保存断点并转向中断服务程序
- CPU 由执行部件(数据通路)和控制部件组成
- 执行部件:操作元件 ALU,状态存储元件
- 控制部件:译码部件(指令寄存器、程序计数器等)、控制信号生成部件
单周期处理器¶
- 单周期处理器的 CPI 为1,但是由于以最复杂的指令的执行时间作为时钟周期,频率比较低(很多指令本来可以以更短的时间完成,这造成了浪费)
单周期数据通路¶
- 存储原件
- 两个读口(组合逻辑操作):busA 和 busB 分别由 RA 和 RB 给出地址。地址 RA 或 RB 有效后,经一个“取数时间 (AccessTime)”,busA 和 busB 有效。
- 一个写口(时序逻辑操作):写使能为 1 的情况下,时钟边沿到来时,busW 传来的值开始被写入 RW 指定的寄存器中。
- 对于 RISC-V 来说数据通路需要的组件为:立即数扩展器,ALU,取指令部件
- 立即数扩展器:对指令中的立即数进行拼接和扩展得到 32 位立即数
- 算术逻辑部件:使用 opctr 以及减法标志位等表示匀速那类型,进行不同匀速那
- 取指令部件:先取指令,之后进行 PC 自增
- 专用数据通路方案:
- 单总线方式:
- 单周期 CPU 不能采用这种方式,因为任何一个时期只能有一个部件把信息送到总线上,因此使用了大量的三态门及暂存器
多周期处理器¶
- 单周期的 CPI 为 1,但是时钟周期很长(等于最费时的 load),总体上执行较慢;多周期的 CPI 较大,但是时钟周期较短(等于最复杂的阶段),注意多周期的性能不一定更好,还是看划分是否均匀
- 规定每个阶段最多只能完成 1 次访存或寄存器堆读/写或 ALU 运算
- 除了时钟周期较短外,多周期还支持重复使用功能部件
控制器的实现¶
- 本质上一个阶段就对应一组控制信号,控制器就是在这一系列状态之间进行转移
- 单周期 CPU 在指令执行过程中控制信号不发生变化,但是多周期不同阶段具有不同的控制信号
有限状态机 PLA¶
- RISC 常用
- 使用硬连线实现
- 由时钟、当前状态和操作码确定下一状态。不同状态输出不同控制信号值。下一状态是当前状态和操作码的函数。每来一个时钟,当前状态变到下一个状态
- 速度快适用于简单/规整的指令系统
- 但是结构非常复杂,对于复杂指令系统结构庞杂很难实现、修改、维护、
微程序控制器¶
- CISC 常用
- 使用 ROM 存储微程序
- 每个指令对应一个微程序
- 一个微程序包含多条微指令对应不同状态
- 一条微指令对应多条微命令,一条微命令对应一个控制信号
- 微程序的可维护性、灵活性较好,但是速度较慢
- 机器指令的执行就是取出对应的微程序,并执行其中的各条微指令,进而译码得到控制信号
- 下条微地址的确定方式
- 增量法:隐含在微程序计数器(即自增)
- 在本条微指令中明显指定下一条微指令的地址
- 微指令的编码方式:
- 直接编码:一位对应一个控制信号
- 字段直接编码:把控制字段划分,互斥性微命令放在同一字段,(就是通过编码,将 n->logn), 并且之后还需要译码,因此效率较低
- 间接编码:一些微命令需要另一个字段的某些微命令解释
- 后继地址的形成:
- 在微指令中添加后继地址字段(断定法)
- 根据机器指令操作码行程 (如首地址)
- 增量计数器
- 分支转移
- 水平微指令:在单个微指令中并行指定多个未操作,执行速度快,但是微指令宽度大(微指令长,微程序端)
- 垂直微指令:一个微指令只指定几个操作,性能交叉,但是成本低(控制存储器等结构更简单)(微指令短,微程序长)
- 一次只能完成一个基本操作
- 多了操作码字段,因为多次才能指出,需要明确告诉当前信号是什么
流水线处理器¶
阶段 | 操作 | 元件 |
---|---|---|
Ifetch | 取指令,PC 自增 | 指令寄存器,adder |
Reg/Dec | 取数,译码 | 寄存器堆读口,指令译码器 |
Exec | 进行计算 | 立即数扩展器,ALU |
Mem | 从数据存储器读写 | 数据存储器(主存) |
Wr | 写寄存器 | 寄存器堆写口 |
- 时钟周期等于最费时的阶段的时间长度 t | ||
- 那么执行 N 条指令需要的时间(5 阶段流水线)为(5+(N-1))*t | ||
- 每个时钟周期每个阶段都有不同的指令在执行,也就是说元件都在被占用,理论上 CPI=1 | ||
- | ||
- Ifetch 是所有类型指令共有的阶段,但是有些阶段如 Mem 不是共有的,可以使用 Nop 填充空指令 | ||
- 对于跳转类型指令 beq 和 j-type 不需要写 mem,但是会在此阶段将转移地址写入到 PC;尽早做出决定可以避免执行不必要的指令 | ||
#### 流水线数据通路 | ||
- | ||
- 使用流水段寄存器,保存每个周期执行的结果,属于内部寄存器,对程序员透明 | ||
- 不同阶段还有各自的控制信号 | ||
- 控制信号是在 ID 阶段解析得到的,之后缓存在流水段寄存器中并逐渐传递并使用 | ||
- | ||
#### 冒险与处理 | ||
##### 结构冒险 | ||
- 同一个部件可能被不同阶段同时使用,比如寄存器可能同时被 ID 读,被 WR 写 | ||
- 修改寄存器,实现上半周期写,后半周期读 | ||
- 内存也可能被同时使用(读取指令和数据),可以对指令和数据划分区域,使得互不干扰 | ||
##### 数据冒险 | ||
- 流水线执行时,前面指令执行完成之前后面指令就开始执行(比如后面的指令依赖 load 的结果,但是此时 load 还没完成 wr) | ||
- ![image.png | 500](https://thdlrt.oss-cn-beijing.aliyuncs.com/20240101192049.png) | |
- 基本流水线只会遇到 RAW 写后读问题 | ||
- 最小化冲突条数 | ||
- 如上图最初会冲突 3 条 | ||
- 使用上半周期写下半周期读能减少一条(Wr、ID 可以在同一阶段) | ||
- 转发技术:数据计算出来要早于存储,希望计算出来之后尽快使用结果,把数据从流水段寄存器中直接取到 ALU 的输入端 | ||
- | ||
- 途中给出了三种转发形式(将数据向前供 ex 使用),要注意的是 load 只有在 ME 之后才得到值,因此 load 无法做到完美转发,中间可能还是需要等待一条指令,称为 load-use | ||
- 阻塞指令执行 | ||
- 硬件阻塞 | ||
- 软件插入 NOP | ||
- ![image.png | 450](https://thdlrt.oss-cn-beijing.aliyuncs.com/20240101201801.png) | |
- 蓝色使用前半后半解决,绿色使用转发解决,红色使用 loaduse 解决(需要等,不能完美解决) | ||
- 此外使用编译优化调整指令顺序也能解决一部分的冲突 | ||
##### 控制冒险 | ||
- beq/jmp 等跳转指令造成的延迟,会在真正跳转执行在原先的 PC 继续错误执行几条指令 | ||
- ![image.png | 500](https://thdlrt.oss-cn-beijing.aliyuncs.com/20240101192256.png) | |
- 未优化之前需要等待三条指令,使用分支预测进行优化 | ||
- 静态预测 | ||
- 总是简单的预测条件不满足,或者使用一些简单的启发式规则 | ||
- 为了尽可能减少等待,将预测提前到 ID 阶段,如果预测失败也只需要等待一个时钟周期 | ||
- 动态预测 | ||
- 利用最近转移发生的情况,来预测下一次可能转移还是不转移 | ||
- 从 BHT 表中寻找,看这条分支指令以前是否执行过,如果没有则插入新的表项到 BHT (默认设置为顺序取),如果找到了则直接使用预测表中的转移目标地址进行转移(预测发生时,选择“转移取“;预测不发生时,选择“顺序取”) | ||
- 预测位 | ||
- ![image.png | 450](https://thdlrt.oss-cn-beijing.aliyuncs.com/20240101211516.png) | |
- ![image.png | 450](https://thdlrt.oss-cn-beijing.aliyuncs.com/20240101212036.png) | |
- JMP 指令会回造成一个时钟周期的延迟,即在第二个时钟周期跳转(不能放在第一个周期,不然耗时太长,拖慢总体上的时钟) | ||
- 编译优化:延迟分支 | ||
- 将与分支无关的指令转移到分支指令后面执行,填充延迟时间,不够用时再使用 nop 进行填充 | ||
- | ||
#### 流水线的性能指标 | ||
- 吞吐率 | ||
- 单位时间内流水线完成任务的数量 | ||
- \(TP=\frac{n}{(k+n-1)t_{clk}}\to \frac{a}{t_{clk}}\) | ||
- 加速比 | ||
- 不用流水线和用流水线的时间之比 | ||
- \(S=\frac{kn}{k+n-1}\to k\) | ||
#### *高级流水线技术 | ||
- 超标量流水线字数:每个时钟周期内并发多条独立指令,并行操作的方式将多条指令编译并执行,结合动态流水线调度技术,不按顺序执行 | ||
- ![image.png | 400](https://thdlrt.oss-cn-beijing.aliyuncs.com/20240412162817.png) | |
- 超长指令字技术:将多条能并行的指令组合成有多个操作码的超长指令字 | ||
- 超流水线技术:流水香功能段划分越多,时钟周期越短,通过提高流水线主频来体恒流水线性能,但是这样流水寄存器 | ||
- ![image.png | 425](https://thdlrt.oss-cn-beijing.aliyuncs.com/20240412162908.png) | |
### 不同 cpu 模式的效率对比 | ||
- 主要功能操作时间:存储单元 200 ps;ALU 和加法器 100 ps; 寄存器堆读写 50 ps. | ||
- 假设 MUX、控制单元、PC、传输线路都没有延迟 | ||
- 指令组成:25%取数、10%存数、52%ALU、11%分支、2%跳转 | ||
- ![image.png | 500](https://thdlrt.oss-cn-beijing.aliyuncs.com/20240101213840.png) | |
#### 单周期 | ||
- 最长的为 600 ps,因此 N 条指令执行时间为 \(600N\) | ||
#### 多周期 | ||
- 指令时钟周期数:取数-5,存数-4,ALU-4,分支-3,跳转-3 | ||
- 平均 CPU 时钟周期数目:\(5*25\%+4*10\%+4*52\%+3*11\%+3*2\%=4.12\) | ||
- N 条指令执行时间为 \(4.12*200N=824 N\) | ||
#### 流水线 | ||
- 5 阶段划分 | ||
- 假定没有结构冒险,数据冒险采用转发处理(假设 loaduse 出现概率为 50%),分支延迟槽为 1,预测准确率为 75%;无条件跳转指令的更新地址工作也在 ID 段完成。不考虑流水段寄存器延时,不考虑异常、中断和访存缺失引起的流水线冒险。 | ||
- Load 指令平均执行为 1.5 时钟(有一半几率发生 load-use) | ||
- store、alu 一个时钟 | ||
- branch 指令:预测成功时一个周期、预测失败时两个时钟 \(0.75*1+0.25*2=1.25\) | ||
- jump 指令:2 个时钟(总要等到译码阶段结束才能得到转移地址) | ||
- 平均 CPI \(1.5*25\%+1*10\%+1*52\%+1.25*11\%+2*2\%=1.17\) | ||
- N 条指令执行时间为 \(1.17*200*N=234N\) | ||
### *多处理器 | ||
#### 根据指令流与数据流的多处理系统分类 | ||
- SISD 标准流水线处理机 | ||
- 常规单处理器,串行计算机结构 | ||
- 一个处理器一个存储器,处理器在一段时间内只执行一条指令 | ||
- SIMD 并行/阵列/向量处理机 | ||
- 一个指令流对多个数据流进行处理,即数据级并行 | ||
- 每个处理单元执行相同的指令但是有各自的地址 | ||
- MISD | ||
- 多个指令处理统一数据,并不存在 | ||
- MIMD | ||
- 多指令处理不同数据,常规的多处理器计算机 | ||
- 每个节点有自己的存储器以及地址空间 | ||
- 线程级或以上并行 | ||
#### 硬件多线程 | ||
- 传统 CPU 中线程的切换影响性能,支持硬件多线程的 CPU 为每个线程提供单独的寄存器组,计数器等,减少了线程切换的开销 | ||
- 细粒度多线程 | ||
- 多个线程之间轮流交叉执行指令(可以乱序并行),处理器能在每个时钟周期切换线程 | ||
- 粗粒度多线程 | ||
- 连续几个时钟周期执行同一线程的指令,只有当前线程出现较大开销的阻塞时再切换(如 cache 缺失)。当发生流水线阻塞时需要清除,才能执行新的线程指令,开销比细粒度更大 | ||
- 以上两种实现了指令级并行而不是线程并行 | ||
- 同时多线程(英特尔超线程)SMT | ||
- 一个时钟周期内发射多个线程中的多条指令执行 | ||
- 实现了线程级的并行 | ||
- 一个处理器核中有多套线程状态部件 | ||
#### 多核处理器 | ||
- 将多个处理单元(核)集成到一个 CPU | ||
- 采用多线程执行,让每个核在同一时可都有任务执行,才能有效的提高效率 | ||
#### 共享内存多处理器 SMP | ||
- 具有共享的单一物理地址空间的多处理器 | ||
- 每个 CPU 都有都有独立的内存控制器,每个 CPU 都独立的链接到一部分内存(本地内存),本地内存的访问比其他远程内存的访问更快 | ||
- UMA 统一存储访问:每个处理器对所有存储单元的访问时间大致相同 | ||
- NUMA 非统一存储访问:某些存储器访问较快,主存被分割分配给了不同的处理器 | ||
## 中断与异常 | ||
### 控制流 | ||
- 通过顺序执行指令或跳转(CALL、Jcc、JMP)转移执行两种方式为正常控制流 | ||
- 由于某些特殊情况引起用户程序的正常执行被打断所形成的意外控制流称为异常控制流。(中断、异常处理,上下文切换,线程调度都属于议程异常控制流) | ||
- 对于单处理器系统,进程会轮流使用处理器,即处理器的物理控制流由多个逻辑控制流组成。 | ||
- 在时间上有交错或重叠的情况被称为并发,并行是并发的特例,两个同时执行的进程是并行的(运行在不同的处理器核) | ||
- 逻辑控制流不会因被其他进程打断而改变,还能回到原被打断的“断点”处继续执行 | ||
### 程序和进程 | ||
- 程序是一种静态的概念,而进程具有动态的含义,一个进程处理不同数据就是不同进程 | ||
- 进程有自己独立的逻辑控制流以及私有的虚拟地址空间,使得程序员可以在程序执行的过程中"独占"处理器及存储器,简化了程序员的编程及语言处理系统 | ||
- 使用一个状态位表示处理器的运行状态(用户态与内核态) | ||
- 用户模式下不允许使用特权指令 | ||
- 内核模式可以使用(如停机、开关中断、cache 冲刷等) | ||
#### 进程的上下文 | ||
- OS 通过处理器调度让处理器轮流执行多个进程。实现不同进程中指令交替执行的机制称为进程的上下文切换 | ||
- 处理器调度等时间就会打断用户的进程,进行了上下文切换 | ||
- ![image-20230527001426160 | 275](https://thdlrt.oss-cn-beijing.aliyuncs.com/image-20230527001426160.png) | |
- 上下文: | ||
- 进程的上下文包括进程的物理实体(代码和数据)和支持进程运行的环境 | ||
- 用户级上下文:进程的程序块、数据块、运行时堆及用户栈等组成的用户空间信息 | ||
- 系统级上下文:进程标识信息,进程线程信息(含寄存器信息),进程控制信息,系统内核栈等内核空间信息 | ||
- 用户级上下文和系统级上下文共同构成了进程的存储器映像 | ||
#### 进程的存储器映像 | ||
- 存储器映射:将进程虚拟地址空间中的一个区域与硬盘上的一个对象建立关联(通过页表建立关联,先不加载) | ||
- 映射对象的类型: | ||
- 私有对象:写时拷贝,可执行代码中的只读代码区域 | ||
- 共享对象:对应共享库文件中的信息 | ||
- 匿名对象:页框用 0 初始化并驻留内存(不与任何文件关联) | ||
- | ||
- 共享库代码在内存和硬盘都只需要一个副本 | ||
- 如果共享库已经在内存中了,那么直接将内容填到页表即可 | ||
- 对于可读可写数据区(私有),开始时一样,但是如果一个进程对其进行了修改,这不应该影响另一个线程的数据,因此不能原地修改,可以通过写时拷贝实现 | ||
- ![image.png | 271](https://thdlrt.oss-cn-beijing.aliyuncs.com/20240413185825.png) | |
- 一个可执行文件运行的过程:构造参数->fork 创建进程->execve 装载映射->运行 main | ||
- ![image-20230527005805222 | 350](https://thdlrt.oss-cn-beijing.aliyuncs.com/image-20230527005805222.png) | |
### 异常与中断 | ||
- CPU中止原来正在执行的程序,转到处理异常情况或特殊事件的程序去执行,结束后再返回到原被中止的程序处(断点(发生异常的下一条指令的位置))继续执行。 | ||
- 内部异常:在 CPU 内部发生的意外或特殊事件 | ||
- 硬故障中断:电源掉电硬件线路异常 | ||
- 程序性中断:溢出、缺页、越权、除 0 等 | ||
- 外部中断:CPU 外部发生特殊事件,通过中断请求信号向 CPU 请求处理 | ||
- Ctrl-C、打印缺纸、DMA 结束等 | ||
- ![image-20230527094811608 | 500](https://thdlrt.oss-cn-beijing.aliyuncs.com/image-20230527094811608.png) | |
#### 程序异常 | ||
- 异常的分类 | ||
- 故障:执行指令引起的异常事件(如溢出缺页等可以补救的问题),该指令出现故障,解决后需要重新执行指令 | ||
- 自陷:预先安排的事件,如单步跟踪、断电、系统调用等,是自愿的中断,解决后从下一条指令开始执行 | ||
- 终止:硬故障事件,机器将终止,将会调出中断服务程序来重启操作系统(除数为 0,非法指令等无法补救的问题) |
故障实例-页故障 - 页故障可能得情况:缺页;地址越界;越级(用户访问内核);越权(修改只读) - - 执行到第 5 行时代买页面早就进入主存,不会因为代码缺页 - 对于 a[10] 会发行缺页,会将其所在页加载到主存 - 对于 a[1000] 虽然越界,但是不会发生缺页(并且由于不检查越界不会被法线错误) - 对于 a[10000] 很可能已经超出了可读写区范围,因此可能发生段错误
陷阱自陷 - 执行陷阱指令时 CPU 会调出特定程序进行处理 - 陷阱系统为用户程序和内核之间提供了一个过程一样的接口(系统调用),用户程序利用这个接口可以方便地使用操作系统提供的一些服务 - 用于调试或者由特定事件(如指令执行)触发的同步中断 -
程序终止 -
中断¶
- 每执行完一条指令,CPU 就查看中断请求引脚,若引脚的信号有效,则进行中断响应:将当前 PC(断点)和当前机器状态保存到栈中(push),并“关中断”(防止处理过程中再次被中断导致保存的数据被破坏),然后,从数据总线读取中断类型号,根据中断类型号跳转到对应的中断服务程序执行。中断检测及响应过程由硬件完成。
- 可屏蔽中断:可以通过关中断进行屏蔽
- 不可屏蔽中断:非常紧急的硬件故障,不论是够关中断都必须进行处理
- 识别中断:
- 软件识别:使用异常状态寄存器记录异常原因,操作系统使用统一的异常处理程序,按照优先级顺序查询异常状态寄存器处理异常事件
- 硬件中断(向量中断):用专门硬件查询电路按照优先级顺序识别异常得到终端类型号,再到中断向量表中读取对应的中断服务程序的入口地址
中断的响应过程(向量中断)¶
- 每个异常和中断有与其对应的异常处理程序或中断服务程序,其入口地址放在一个专门的中断向量表(实模式使用)或中断描述符表中(保护模式使用)。前 32 个类型(0~31)保留给 CPU 使用,剩余的由用户自行定义 (指操作系统,用于可屏蔽中断,部分用于软中断)
-
通过执行 INT n 使 CPU 自动转到 OS 给出的中断服务程序执行
-
软中断:指令 INT n 被设定为一种陷阱异常
- 软中断是由软件程序显式地触发的,而陷阱是由程序执行期间发生的特定条件触发的。
- 软中断通常用于操作系统内部的功能调用和服务请求。陷阱通常用于处理异常情况,如非法操作、错误或其他不正常情况以及用于调试程序
-
实地址模式下的中断向量表
- 实地址模式是 Intel 为 80286 及其之后的处理器提供的一种 8086 兼容模式。中断向量表位于 0000H~03FFH。共 256 组,每组占四个字节
- 开机过程中,需要先准备在实模式下的中断向量表和中断服务程序。通常,由固化在主板上一块 ROM 芯片中的 BIOS 程序完成
- BIOS 包含各种基本设备驱动程序,通过执行 BIOS 程序,基本设备驱动程序以中断服务程序的形式被加载到内存,以提供基本 I/O 系统调用。(如用 INT 指令把 OS 加载到内存)
- 一旦进入保护模式,就不再使用 BIOS。
-
保护模式下的中断描述符表
- 中断描述符表 IDT 是 OS 内核中的一个表,共有 256 个表项,每个表项占 8 个字节,IDT 共占用2KB,IDTR 中存放 IDT 在内存的首地址
- 每一个表项是一个中断门描述符、陷阱门描述符或任务门描述符
多重中断¶
- 中断的优先级分为响应优先级和处理优先级
- 响应优先级由硬件排队器决定
- 处理优先级由屏蔽字决定
- 在一个中断处理(即执行中断服务程序)过程中,若又有新的中断请求发生,而新中断优先级高于正在执行的中断,则应立即中止正在执行的中断服务程序,转去处理新的中断。这种情况为多重中断
IA 32 中的异常与中断¶
- 中断门:DPL=0,TYPE=1110 B。激活所有中断
- 系统门:DPL=3,TYPE=1111 B。激活 4、5 和 128 三个陷阱异常,分别对应指令 into、bound 和 int $0 x 80 三条指令。(可在用户态使用)
- 用户态代码可以访问、使用系统调用
- 使用其他的 int 指令会激活 int 13 保护异常,防止用户执行非法的操作系统级操作
- 系统中断门:DPL=3,TYPE=1110 B。激活 3 号中断(即调试断点),对应指令 int 3。
- 陷阱门:DPL=0,TYPE=1111 B。激活所有内部异常,并阻止用户程序使用 INT n(n≠128 或 3)指令模拟非法异常来陷入内核态运行。
- 只有内核代码才能访问陷阱门
- 任务门:DPL=0,TYPE=0101 B。激活 8 号中断(双重故障)。
- 当用户态代码执行系统门(DPL=3)指令时,处理器会检查系统门描述符,并根据其中的入口地址跳转到内核中相应的系统调用处理程序。在内核态中,系统调用处理程序会执行特定的系统调用逻辑,完成请求的操作。处理完系统调用后,控制权会返回到用户态代码,继续执行原始的用户态指令。与执行中断门不同,不会发生特权级别的转换。相反,系统门提供了一种特权级别之间的跳转机制,允许用户态代码直接跳转到特定的系统调用处理程序。
中断响应¶
- 确定中断类型号 i,从 IDTR 指向的 IDT 中取出第 i 个表项 IDTi。
- 根据 IDTi 中段选择符,从 GDTR 指向的 GDT 中取出相应段描述符,得到对应异常或中断处理程序所在段的 DPL、基地址等信息。
- 如果CPL小于DPL,或者执行了一个不合法的操作,则发生 13 号异常(立刻切换到内核态),防止恶意程序模拟 INT n 陷入内核进行破坏性操作。
- CPL>=DPL,并且当前操作需要从较低特权级(用户态)切换到较高特权级(内核态),处理器将通过任务状态段(TSS)进行栈的切换。
- 读取 TR 寄存器以访问当前运行的用户进程的 TSS。将 TSS 中保存的内核栈的段选择符和栈指针装入 SS 和 ESP 寄存器,并在内核栈中保存原来用户栈的 SS 和 ESP。在当前栈中保存处理器状态,包括 EFLAGS、CS 和 EIP 寄存器。
- 将 IDTi 中的段选择符装入 CS,将 IDTi 中的偏移地址装入 EIP,从 CS: EIP 指向的地址开始执行异常处理或中断服务程序。
- 如果在处理中断或异常时保存了硬件错误码,
IRET
指令首先从栈中弹出这个错误码。IRET
从栈中依次恢复 EIP(返回地址)、CS(代码段选择符)和 EFLAGS(标志寄存器)。这些值在中断或异常发生时被自动压栈。 IRET
检查当前的 CPL 是否与栈中保存的 CS 的特权级相等。如果不等,说明中断或异常发生前的代码运行在不同的特权级,此时IRET
还需要从栈中恢复原来的 SS(栈段选择符)和 ESP(栈指针),以返回到适当的用户栈。- 在恢复执行之前,系统需要检查数据段寄存器(DS、ES、FS、GS),如果这些寄存器中的任何一个指向的段描述符的 DPL 小于当前的 CPL,那么这些寄存器将被清零。
-
完成上述所有步骤后,
IRET
指令执行,CPU 控制权返回到发生中断或异常的代码处,继续执行。 -
因为中断事件可能与当前执行的进程可能没有关系,因此不像异常处理那样讲信号发送给进程
- 可编程中断控制器 PIC
- 有许多 IRQ 引脚
- 可以屏蔽,也可以设置优先级
异常处理¶
-
对于异常处理,通常不需要特权级的变更,因为异常多数在相同的特权级别内部处理。
-
准备阶段:在内核栈保存通用寄存器现场
- 处理阶段:采用 C 函数进行具体处理。函数名由 do_前缀和处理程序名组成,如 do_overflow 为溢出处理函数。
- 大部分函数的处理方式:保存硬件出错码(如果有的话)和异常类型号,然后,向当前进程发送一个信号。
- 当前进程接受到信号后,若有对应信号处理程序,则转执行;若没有,则调用内核 abort 例程执行,以终止当前进程。
- 恢复阶段:恢复保存在内核栈中的各个寄存器的内容,切换到用户态并返回到当前进程的断点处继续执行。
响应系统调用¶
- 操作系统提供了中断指令 int 0 x 80来主动进入内核,这是用户程序发起的调用访问内核代码的唯一方式(使用其他指令都会因为权限问题被 int 13 接管,只有 int 80 会响应请求;即使没有发生 13 异常,还是需要从用户态转化到内核态,只不过系统会响应功能)
- 当应用程序执行
int 0x80
指令时,处理器会触发一个软中断,控制权转移到操作系统的中断处理程序。操作系统会根据中断号和参数来确定执行的具体系统调用
输入输出 IO¶
- IO 设备的分配:
- 块设备(以数据块为单位交换、传输速率较高,可寻址);字符设备(传输满、不可寻址)
- 存储设备、输入输出设备
- 独占设备、共享设备(一段时间内运行多个进程进行访问、必须是可寻址可随机访问的)、虚拟设备
IO 子系统¶
-
所有 IO 操作通过读写文件,所有外设,网络等都被看成文件,对这些设备的访问就像访问磁盘文件一样
- 键盘和显示器构成中端,属于标准输入输出文件,磁盘等是普通文件(二进制存储)
- 标准输入 (fd=0)、标准输出 (fd=1)和标准错误 (fd=2)
系统级 IO(系统调用)¶
- 指定文件的打开方式,使用文件描述符指定文件,进行打开
- 文件描述符是操作系统为每一个打开的文件分配的唯一的整数值
- 是不带缓冲的读写,直接对磁盘进行操作
- 有更大的灵活性,但是使用较为困难
标准库 IO 与缓冲区¶
- C 标准库是基于系统调用实现的
- 文件指针:高级语言中用于访问文件的数据结构,记录程序读写到的位置,通过移动指针来操作文件的不同部分
- 读数据
- 受限从文件中读入 1024 字节到缓存,再按需从缓冲区获取
-
写数据
- 先按需不断地向缓存写字节,遇到换行符\n 或缓存被写满 1024(缓冲大小 BUFSIZ=1024)个字节,则将缓存内容一次写入文件 fp 中
-
高级语言标准库(API)的 IO 函数效率高,安全,但是功能受限,功能更广泛、抽象程度更高
总线¶
- 总线是一组能为多个部件分时和共享的传送线路
- 分时:同一时刻只允许有一个部件向总线发送信息
- 共享:总线上可以挂载多个部件
-
现在多使用点对点传输、异步、串行
-
总线事务
- 请求阶段:主设备发出总线传输请求,获得总线控制权
- 仲裁阶段:总线仲裁机构决定将下一个传输周期的总线使用权授予某个申请者
- 寻址阶段:主设备通过总线给出要访问的从设备地址,启动从模块
- 传输阶段
- 释放阶段:主模块相关信息撤除,让出主线使用权
分类¶
- 功能层级:
- 片内 (cpu 内部)总线,用于 CPU 内部各寄存器之间及寄存器与 ALU 的连接
- 系统总线:功能部件之间相互连接的总线,分为数据、地址、控制总线
- 数据总线:传输数据、指令、中断类型号等,双向传输总线,位数表示一次能传送的数据位数
- 地址总线:指出数据所在的主存单元以及 IO 端口等信息,单向传输总线(从 CPU 到内存或外设),位数表示寻址空间
- 控制总线(+状态总线):传输命令反馈以及定时信号,控制信号等
- IO 总线:控制中低速 IO 设备(如 USB,PCI)
- 通信总线:计算机系统与其他系统之间传送信息的总线(外部总线)
- 时序控制方式:
- 同步总线:通过统一的时钟同步,在规定的时钟节拍进行规定的总线操作
- 异步总线:没有统一始终,以信号握手来协调各部件或设备的信息传输
- 传输方式
- 串行总线:只有一条双向传输的数据线,数据按比特位串行顺序传输,适合长距离通讯
- 并行总线:多条双向传输数据线,可以多比特位同时传输,其效率比串行总线更高,但是由于延迟,干扰可能存在传输错误,并且位与位需要同步,适合短距离传输
- 传送方式
- 非突发:每个事务都是先发地址,之后进行一次数据传送
- 突发:先发一个地址,后传送多次数据,后序数据地址为前面地址自动增量(一组数据传输完成之后再释放总线)
总线的结构¶
- 单总线结构
- 将所有设备都挂在一组总线上,不同设备之间可以直接交换信息
- 细分为:地址、数据、控制总线
- 双总线结构
- 一条主存总线,用于 CPU 、主存和通道之间传送数据
- 另一条 IO 总线,用于在多个外部设备与通道之间传送数据
- 三总线结构
- IO 总线、主存总线、DMA 总线,提高了 IO 设备的性能
性能指标¶
- 宽度:数据线数目,总线能同时传输信息的位数
- 工作频率:每秒传送次数,现在一般是时钟频率的 2~4 倍
- 总线带宽:一秒内传输的数据量
- \(B=\frac{W*F}{N}\)
- W 为宽度,F 为时钟频率,N 为一次数据传输使用的时钟周期
总线的具体标准¶
- FSB 总线:早起用于 CPU 芯片和北桥芯片的互连
- QPI 总线:用于 CPU 内核之间,芯片之间,CPU 与 IOH 芯片之间的链接
- 包交换,串行,点对点
- 一次栓送 20 位(16 位数据加上 4 位校验),每个时钟周期传送两次
- 每秒可以接受&发送各两次
- 内存频率表示适配的总线工作频率
- 带宽就是内存控制器数目(通道数)*位数*工作频率
- PCIE 总线:IO 总线
- 两个链路包含多条通路,可以为 1,2,4,8,16 等
- PCIE*n 就是有 n 条通路的 PCIE 链路
- 除以 10 时因为 8 数据位+2 校验位
IO 接口¶
-
功能:
- 进行地址译码和设备选择
- 主机和外设通信联络控制
- 数据缓冲
- 信号格式的转换
- 传送控制命令和状态信息
-
IO 接口有:打印机适配器,网络适配器,可编程中断控制器等,但是磁盘驱动器不是
-
设备控制器
- 数据线:传递读写信息,状态信息等(命令字、状态字、中断类型等)
- 地址线:传送要访问接口中寄存器的地址
- 控制线:传送读写控制信号(仅用于读写控制)
-
对 I/O 端口读写就是向 I/O 设备送出命令或从设备读状态或读/写数据
- I/O 设备的寻址方式就是 I/O 端口的编号方式
- 统一编址方式
- 与主存空间统一,主存单元和 IO 端口在同一个地址空间
-
独立编址方式
- 单独编址,独立的 IO 地址空间,因而需要使用专门的 io 指令来访问
- 程序清晰,便于理解,但是增加了指令,增大了控制的复杂性
-
驱动程序
- 将控制命令送到控制寄存器来启动外设工作
- 读取状态寄存器谅解外设的状态
- 放温暖;数据缓冲寄存器进行数据的输入和输出
IO 控制方式¶
- 程序直接控制
- 无条件传送:对简单外设定时进行数据传送
- 条件传送:CPU 主动查询(轮询),必须够频繁,在接口缓冲区满之前取出数据
- 中断方式
- 如果一个 Io 设备需要 CPu 干预,就通过中断通知
- CPU 调出 OS 进行处理
- 处理结束之后再继续执行原先的程序
- DMA
- 磁盘等高速外设成批的和主存数据交换
- 使用专门的 DMA 控制器控制总线,数据传输过程不需要 CPU 参与
程序查询方式¶
- IO 设备机爱你个自己的状态存储到状态寄存器,OS 阶段性进行查看,判断是否就绪,如果就绪了就进行处理,未就绪就进行等待
中断方式¶
- 当外设准备好时,便向 CPU 发中断请求,CPU 响应后,中止现行程序的执行,转入“中断服务程序”进行输入/出操作。“中断服务程序”执行完后,返回原被中止的程序断点处继续执行。此时,外设和 CPU 并行工作。
- 虽然由于中断程序的额外开销导致传输速度有所下降,但是 CPU 的占用大幅下降
DMA 方式¶
- 在高速外设和主存间直接传送数据由专门硬件(即:DMA 控制器)控制总线进行传输;成批数据交换,且数据间间隔时间短,一旦启动,数据连续读写
-
过程
- CPU 对 DMA 初始化(传送方向,数据个数,地址等)
- 磁盘控制器读磁盘,DMA 控制器将数据送主存,此时 CPU 执行其他任务
- DMA 传送结束后向 CPU 发送中断信号,要求 CPU 进行后处理
- CPU 只需要在初始化和后处理时介入,CPU 的开销较小
- 中断方式中使用中断请求是为了传送数据,DMA 中只是向 CPU 报告数据传输结束
-
CPU 在指令执行结束后检查中断;在每个总线机器周期结束检查 DMA 请求,优先级高于中断;
内核空间的 IO 软件¶
- 内核空间 I/O 软件实现相应系统调用的服务功能
- 设备无关 IO 层
- 设备驱动统一接口,抽象文件
- 内核缓冲处理
- 负责设备文件的代开及关闭,比设计具体操作
- 设备驱动程序
- 设备控制器
- 三种 IO 控制方式
- 中断服务层
- 中断控制方式、DMA 控制方式