深入理解计算机系统
第一章 计算机系统漫游
- 预处理器->编译器->汇编器->链接器
- 硬件组成:
- 总线:在各个部件之间传递信息
- I/O设备:系统与外部世界的联系通道,与I/O总线相连
- 主存:临时存储设备
- 处理器:解释存储在主存中指令的引擎
- 意识到告诉缓存存在的程序员可以利用高速缓存将程序的性能提高一个数量级
寄存器-L1高速缓存-L2高速缓存-L3高速缓存-主存-本地二级存储-远程二级存储 - 进程:系统对一个正在运行的程序的一种抽象
- 内存空间:
内核虚拟存储器
栈
共享库
堆
程序代码和数据 - 驱动进步的两个需求:
- 想要计算机做的更多
- 想要计算机运行的更快
- 超线程
第二章 信息的表示和处理
- 三种数字表示
- 无符号:传统的二进制表示法
- 补码:表示有符号整数的最常见方法
- 浮点数:表示实数的科学计数法的以为为基底的版本
结果太大,超出位数的限制时——溢出
- 字节是最小的寻址单位,字长指明整数和指针数据的标称大小(限定虚拟地址空间的最大大小)
- 大端/小端:按照从最低有效位到最高有效位的顺序存储对象为小端法,从最高有效位到最低有效位存储对象为大端法
- 逻辑右移和算数右移: 逻辑右移在左端补0,算数右移在左端补最高有效位的值。无符号数右移必须是逻辑右移,有符号数没有要求,但是基本实现算术右移。
- 实现中当左移或者右移超过数据位数时,对移位数量取余,但标准没有规定。
- 补码:最高位权值为负,100000为最小的数,11111111为-1。
- 无符号加法等价于计算和mod 2^w。 s = x + y,当且仅当s < x或s < y时发生溢出。
- 补码加法,负溢出得到正数,正溢出得到负数。
- 补码的非,x>-2^(w-1) 时为-x,x=-2^(w-1)时为它本身。求补码的非可以采用两种方法:
- 取反加一
- 将最右边1的左边所有位取反
- 无符号乘法:xy = (xy)mod(2^w)。
- 整数乘法代价很高,编译器会尝试将乘法优化为加法和位移来提高性能。例如x14=(x<<3)+(x<<2)+(x<<1),或者x14=(x<<4)-(x<<1)
第三章 程序的机器级表示
- 机器级代码的两个抽象:
- 指令集体系结构:处理器状态、指令的格式以及每条指令对状态的影响。
- 虚拟存储器地址
- 机器代码展示的处理器状态:
- 程序计数器(PC):指示下一条指令在存储器中的地址
- 整数寄存器:8个32位的值,可以存储地址或整数数据
- 条件码寄存器:实现控制或数据流中的条件变化
- 浮点寄存器:存放浮点数据
- IA32指令:
- 长度从1到15个字节不等,常用指令字节数少,不常用指令字节数多
- 设计指令格式的方式:从某个给定位置开始,可以将字节唯一地解码成机器指令(如:只有push %ebp是以字节值55开始的)
- 反汇编器基于机器代码文件中的字节序列来确定汇编代码
- 寄存器:
- eax、ecx、edx、ebx、esi、edi 通用寄存器
- esp 栈指针
- ebp 帧指针
- 条件码寄存器:记录最近的算数或逻辑操作的属性
- CF:进位标志,最近的操作使最高位产生了进位,可以检查无符号操作数的溢出
- ZF:零标志,最近的操作得出的结果为0
- SF:符号标志,最近的操作得到的结果为负数
- OF:溢出标志,最近的操作导致一个补码溢出(正溢出或负溢出)
- 汇编指令:
- pushl:将寄存器内容压入栈
- popl:出栈到寄存器
- mov:复制
- movs:符号扩展复制
- movz:零扩展复制
- cmp/test:只设置条件码,不更新寄存器
- set:根据条件码的某个组合将一个字节设置为0或1
- jmp:跳转到label
- call:过程调用
- ret:从过程调用中返回
- 栈帧结构:
- 用栈来传递过程参数、存储返回信息、保存寄存器用于以后恢复以及本地存储
- 为每个过程分配的栈称为栈帧
- 过程P调用过程Q:
- Q的参数放在P的栈帧中
- P的返回地址被压入栈中,放在P的栈帧的末尾
- 寄存器可以分为调用者保存和被调用者保存,以防止丢失寄存器信息
第五章 优化程序性能
你能获得的对程序的最大的加速比就是当你第一次让它工作起来的时候。
- 如何提高程序的性能:
- 合适的算法和数据结构
- 编译器能够有效优化以转换成高效可执行代码的源码
- 并行计算
- 程序优化:
- 消除不必要的内容:不必要的函数调用、条件测试和存储器引用
- 利用处理器提供的指令级并行能力,同时执行多条指令
第七章 链接
将各种代码和数据部分收集起来并组合成为一个单一文件的过程
- 链接器的两个主要任务:
- 符号解析:将符号引用和符号定义联系起来
- 重定位:把每个符号定义与一个存储器位置联系起来,然后修改每个符号引用,使他们指向这个存储器位置
- 目标文件的三种形式:
- 可重定位目标文件:包含二进制代码和数据,可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。(编译器和汇编器生成)
- 可执行目标文件:包含二进制代码和数据,可以被直接拷贝到存储器并执行。(链接器生成)
- 共享目标文件:特殊类型的可重定位目标文件,可以在加载或运行时动态地加载到存储器并链接。
- ELF:Executable and Linkable Format
- ELF头:描述了生成该文件的系统的字的大小和字节顺序,还有ELF头的大小、目标文件的类型、机器类型、节头部表的文件偏移,以及节头部表中的条目大小和数量
- .text:已编译程序的机器代码
- .rotate:只读数据
- .data:已初始化的全局C变量
- . bss:未初始化的全局C变量,不占据实际空间
- .symtab:符号表,存放在程序中定义和引用的函数和全局变量的信息
- .rel.text:一个.text节中位置的列表,链接时需要修改这些位置
- .rel.data:被模块引用或定义的任何全局变量的重定位信息
- .debug:符号调试表,程序中定义的局部变量和类型定义,程序中定义和引用的全局变量以及原始的C源文件
- .line:原始C源程序中的行号和.text节中机器指令之间的映射
- .strtab:字符串表,包括.symtab和.debug节中的符号表,以及节头部中的节名字
- 当编译器遇到不是在当前模块定义的符号时,会假设该符号是在其他某个模块定义的
- 强符号:函数和已出初始化的全局变量
- 弱符号:未初始化的全局变量
- 处理多重定义符号的3原则:
- 不允许有多个强符号
- 1个强符号和多个弱符号,则选择强符号
- 多个弱符号,则任意选一个
- 静态库:将所有相关的目标模块打包成为一个单独的文件,可以用作链接器的输入,只拷贝静态库里被应用程序引用的模块
- 重定位:
- 重定位节和符号定义(全局):将所有模块中相同的节合并,并计算运行时存储器地址
- 重定位节中的符号引用:修改代码节和数据节中对每个符号的引用,指向正确的运行时地址
- 重定位条目:当汇编器遇到对最终位置未知的目标引用,会生成一个重定位条目,告诉链接器将目标文件合并成可执行文件时如何修改这个引用(代码重定位.rel.text,已初始化数据重定位.rel.data)
重定位条目格式
typedef struct { int offset; int symbol: 24; type: 8; } Elf32_Rel;
- 程序运行时要执行的第一条指令的地址由ELF头部的entry point指定
- 动态链接库:在运行时可以加载到任意的存储器地址,并和一个程序链接起来