这篇是笔者在初读CSAPP时的读书笔记,写的比较简陋,如果有掘友感兴趣我会继续完善,欢迎交流~
深入理解计算机系统
系统的四大硬件组成
处理器CPU 解释执行主存中指令的引擎
主存RAM 临时存储设备,存放程序和数据
I/O设备 与外部设备连通的通道,如硬盘、显示器
系统总线BUS 在系统各个部件传递信息(字)
指令集架构和微体系结构
指令集架构关注每条机器指令实现的效果,而微体系结构关注处理器如何实现。
处理器体系结构
核心:运算+控制
指令集体系结构ISA:指令+指令的编码
微体系结构:处理器如何实现
CPU基本组成
-
ALU算术逻辑单元
-
PC程序计数器 正在运行的指令的主存地址
-
寄存器组REG
- 通用寄存器
- 特殊寄存器
-
指令寄存器IR
-
存储器数据寄存器MDR、存储器地址寄存器MAR
-
PSW程序状态字寄存器:
-
条件码 运算结果的保存 ,如进位标志、零标志、符号标志
-
状态码 控制指令正常/异常,如中断、溢出
-
-
-
总线接口
以CISC 复杂指令集计算机为例:
RISC和CISC区别
指令数量 早期RISC不到100个,现在妥协
指令编码长度,RISC为定长
寻址方式 RISC只支持简单寻址
操作对象 RISC只能对寄存器进行,必须先从内存中载入(被称为Load/Store提携结构),并且没有状态码,需要用测试指令进行。
指令类型
-
数据传送指令:立即数 寄存器 内存 mov
-
运算指令(整数、浮点数、逻辑)add sub
-
跳转指令 jmp
-
条件传送 cmov
-
函数调用返回 call ret、入栈出栈、结束
**寻址方式(**寻址:本质是寻数据)
立即数:指令中直接给出数据,传送到寄存器中
寄存器寻址:给出寄存器名,用寄存器的值进行操作
寄存器间接:给出寄存器名,取出寄存器中的存储的地址上的数据
寄存器偏移寻址:给出寄存器名,在数据移动前先进行算术偏移
基址寻址:给出基址寄存器,以及偏移量,但通常基址不可变
变址寻址:基址可变的基址寻址,如采用PC作为基址。
对于CISC,其还可以直接访问内存:
直接寻址:给出操作数在内存的地址
间接寻址:给出操作数内存地址的地址
执行过程
取指、fetch
译指、decode
执行、execute
访存、写回:memory 从内存读取或写入数据
中断检查:
更新PC
流水线
流水线即不同阶段并行操作
处理一条指令时,将其划分为一个个环节,在完成一个环节后,进入下一条指令,而无需等待一整个指令处理完成后才进入下一条。
流水线性能
冒险或冲突:前后的依赖性导致流水线计算错误。
数据冒险:由于延迟导致还未写回时就被下一条读取。解决:数据旁路
控制冒险:转移指令等导致PC值断流。解决:预测PC而不保存
资源冲突:多条指令同时访问某个互斥资源
周期
- 指令周期 执行一条指令的完成时间
- 取指 PC-》IR
- 间址 取操作数
- 执行
- 中断
- 机器周期 定义读取一个指令的时间
- 时钟周期 最小单位,T周期或节拍,晶振频率的倒数
Clock Per Instructions,CPI:执行一条指令所需时钟周期数
MIPS:每秒多少百万条指令=主频/CPI
存储器层次结构
主要思想:上一层的存储器都是下一层存储器的缓存。
结构
CPU寄存器 L1-L3高速缓存(SRAM)主存DRAM 辅存 (磁盘) 网盘
随机访问存储器
SRAM 采用双稳态触发器、速度快、不敏感
DRAM 用电容来存储、需要不断刷新、速度较慢、对光和电噪声敏感
局部性原理
时间 空间
高速缓存
高速缓存基本结构
对于一个存储器位宽为m的,共有个存储单元,缓存通过类似哈希的结构(索引+数据)进行映射。
高速缓存结构(S,E,B)的主要组成:
组S:多个行构成一组,共有组(组的一级索引)。
按照组数和行数的关系,缓存可分为:
- 直接映射高速
- 组相连映射
- 全相联映射
行E:一行中包括有效位、标记位(组内的二级索引)和块(数据)。
块B:一个块中有多个字。
直接映射高速缓存
每组只有一行。
-
寻址方法
- 三步走:组选择、行匹配、块抽取
将m位存储器地址划分为三个部分:
组索引位于地址的中部,而不是首部。
原因:如果用首部作索引,那么存储器连续的一段地址是冲突的,这样连续访问时每次都不命中,而用中间位使得连续的一段地址映射到不同的组,进而彼此不冲突,可以连续读完一段后再替换,缓存命中率更高。
组相连高速缓存
-
替换策略
直接映射由于组内只有一行,因此直接替换即可。而组相连则设计组内选择哪一行替换。
最不常使用LFU:替换时间窗口内使用次数最少
最近最少使用LRU:替换上一次访问最久远的那个。
全相连映射
组索引退化,只剩标记位。
写回策略
需要更新时,如何通过缓存进而写主存。
- 直写write-through:有更改时立即写入,问题时耗费总线资源过大。
- 写回write-back:尽可能推迟更新,该快要被替换前才写入,问题是需要引入额外的修改位。
- 写不命中时,直写通常采用写分配(读-更改-写),而写回则采用非写分配(直接写存储器)。
操作系统管理硬件
虚拟内存空间:给每个进程提供了一种假象,每个进程都独占一个连续空间,因此无需关注(物理)寻址问题,由操作系统进行虚拟地址和物理地址的转换。
这个空间根据进程切换来从磁盘到主存之间进行缓存。
-
进程的虚拟地址空间,从上到下依次为:
内核
栈区
共享库的内存(给库函数分配的)
堆区
读写数据
只读代码和数据
IO设备
总线
程序运行
信息表示和处理
信息 = 位(bit串) + 上下文信息(到底是整数、字符串还是指令)
程序机器级表示
程序编译过程
编译系统
1 预处理器:插入头文件 .c->.i
2 编译器:将程序转换为汇编程序 ->.s
3 汇编器:将汇编程序转换为机器语言(二进制),可重定位目标程序
4 链接器:将库函数程序段合并进来
经历上述步骤后,可执行文件储存在磁盘中
处理器读取磁盘中的程序
链接
将各种代码和数据片段合成一个单一文件
编译时
加载时
运行时
编译器驱动程序
执行预处理、编译器、汇编、链接四个步骤,如果最终需要被执行,加载器加载到内存中
链接器的输入是多个已编译好的可重定位目标文件.o文件,输出为可执行目标文件
如何进行链接?
静态链接
将可重定位目标文件 生成 可执行目标文件
链接器的两个任务(步骤)
- 符号解析 将符号定义(函数变量名的定义)和符号引用(函数变量的引用)关联起来。
- 重定位 将符号定义和内存地址关联起来,使得所有的符号引用指向到该内存地址
目标文件
链接器的输入或输出。纯粹是字节块的集合,包含程序和数据。
- 可重定位目标文件 编译时静态地连接建立
- 共享目标文件 特殊的可重定位,可以在加载时和运行时动态地
- 可执行目标文件 包含二进制程序和数据,可直接复制到内存中执行
目标文件格式有很多,现代系统使用的是可执行可连接格式(ELF)。
可重定位目标文件
ELF标准格式。
包含程序机器代码、变量、只读数据。
符号和符号表
.symtab
根据谁定义、谁引用,划分为三种不同的符号:全局符号、局部符号
区分局部链接器符号和局部变量。
static创建的静态局部变量将不在
符号解析
-
对全局符号的解析
- 多符号重名时,有限选择强符号的定义(被初始化过的值)
-
与静态库链接 func.o -> lib.a
-
静态库:将相关的目标文件打包成一个单独的文件
-
使用库可以节约内存、节约编译时间、减少手动链接错误
-
在链接时,链接器只复制被程序调用到的目标模块到内存
-
如何用静态库解析引用
-
维护可重定位目标文件集合E 待解析符号U 已解析D
-
对于所有输入文件f,如果是目标文件E,就添加到E并且修改UD
-
如果是库,则对于库中的每一个文件m,如果其中有解析U中的引用,则将m加入E并修改UD
-
-
-
重定位
解析后,知道了代码和数据的大小,重定位中进行模块合并,并分配运行地址。
从大到小两步走:
-
合并同一类型的节并分配地址 聚合节,最终每条指令和全局变量有了地址
-
依赖于重定位条目,修改节中每个符号的引用为各自的地址。
可执行目标文件
同样是ELF文件头
中断和异常
计算机正常的控制流是连续的,而为了使计算机具备能够响应外部事件,调用跳转以及处理故障等功能,出现了异常控制流ECF,异常就是控制流中的突变。
异常发生的过程:由事件触发,控制转移到异常处理程序,最后异常返回(也可能不返回)。
系统启动时,初始化异常表,其中存储的是异常处理程序的入口地址,对于中断而言,是中断向量。
异常分为中断、陷阱、故障和中止。中断是异步发生的,即外部信号导致。而后三者是同步的,由指令执行导致。
中断
由硬件触发异常,软件进行响应。
-
当指令执行时CPU的中断引脚的电平发生变化(中断信号产生)
-
在指令执行完后,从系统总线读取异常号
-
根据异常号来寻找中断服务程序的地址(中断向量表),并保存断点(PC压栈)
- 调用前保护现场,即将当前程序压栈(PC、PSW和寄存器数据)
- 跳转到中断服务程序的第一句
- 恢复现场
-
注意,在执行中断控制操作时,必须要关中断,防止现场保护恢复不完整
-
中断屏蔽
在处理器处理关键事件时,中断允许寄存器复位,不响应某些中断。
-
中断嵌套
中断运行时,又遇到中断,此时根据优先级来决定是否响应,处理完高优先级后再处理低优先级
陷阱
有意的异常,用于请求系统服务调用。
故障
执行错误(可恢复),缺页中断等
中止
执行错误(不可恢复),内存错误
虚拟内存
(未完待续)