《深入理解计算机系统(第三版)》读书笔记

delims 于 2022-03-26 发布

第二部分

第 7 章 链接

链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可以被加载到内存并执行。

链接可以执行于编译时、加载时甚至运行时。

7.1 编译器驱动程序

shell 调用操作系统中一个叫做加载器的函数,它将可执行文件中的代码和数据复制到内存,然后将控制权移动到这个程序的开头。

7.2 静态链接

静态连接器(static linker) 以一组可重定位目标文件和命令行参数作为输入,生成一个完全连接的、可以加载和运行的可执行目标文件作为输出。连接器的主要任务:

7.3 目标文件

目标文件有三种形式:

Linux 和 Unix 系统使用可执行可连接格式(Executable and Linkable Format,ELF)作为可执行文件格式。

7.4 可重定位目标文件

文件头部信息以一个16字节的序列开始。包含的节有:

7.5 符号和符号表

每个可重定位模块都有一个符号表,包含它定义和引用的符号信息。在连接器的上下文中有三种不同的符号:

连接器符号不包括临时变量,临时变在运行时栈中被管理。

在C语言中,源文件扮演模块的角色,任何只在本模块内使用的函数或者全局变量应该声明为 static ,这样起到保护作用,就像 C++ 中的 private 一样。

7.6.1 连接器如何解析多重定义的全局符号

连接器的输入是一组可重定位目标模块。每个模块定义一组符号,有些是局部的只对当前模块可见,有些是全局的对所有模块可见。如果有多个模块定义同名全局符号,会发生什么?

符号分为强符号和弱符号。函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。链接处理多重符号的规则:

  1. 不允许有多个同名强符号。
  2. 如果一个强符号和多个弱同名,选择强符号。
  3. 如果有多个同名弱符号,则任选一个弱符号。

7.6.2 与静态库链接

7.7 重定位

完成符号解析后,连接器知道它的输入目标模块中代码节和数据节的大小。就可以进行重定位步骤,合并输入模块为每个符号分配运行时地址。

7.7 重定位条目

目标文件并不知道数据和代码最终放在内存什么位置。也不知道引用外部符号的位置。代码生成一个重定位条目放在 .rel.text 中。数据生成重定位条目放在 .rel.data 中。

  1. 汇编器生成一个目标模块时从地址0开始生成代码和数据节 ,它并不知道数据和代码最终将放在内存的什么位置,也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。
  2. 链接器在重定位步骤中,合并输入模块并将运行时地址赋给输入模块定义的每个节、符号。当这一步完成时,程序中的每条指令和全局变量才拥有唯一的运行时内存地址。

7.8 可执行目标文件

可执行目标文件包括程序的入口点,包含 .init 节,这个节中定义了一个小函数 _init。程序的初始化代码会调用它。可执行文件已完全链接,即已重定位,所以不需要 rel 节。

7.9 加载可执行目标文件

Linux 程序都可以通过调用 execve 函数来调用加载器。

7.10 动态链接共享库

共享库(shared library)在运行时可以加载到任意地址,并和内存中的程序链接,称为动态链接。

// 通过 -fpic 指示编译器生成位置无关代码
// -shared 指示编译生成共享目标文件
gcc -shared -fpic -o lib.so a.c b.c

// 使用共享库,这样生成的可执行文件含所有 .interp 节。
// 这一节包含动态库的路径名
gcc -o a.out main.c ./lib.so

动态连接器本身就是一个共享目标,

Java 本地接口(Java Native Interface, JNI )通过 dlopen 接口加载C/C++ 生成的动态库。

7.12 位置无关代码

可以加载而无需重定位的代码称为位置无关代码(Position Independent Code)。

PIC数据引用

在内存中任意位置加载一个目标模块,数据段和代码段的距离总是保持不变。

在数据段开始地方创建一个表,叫做全局偏移量表(GLobal Offset Table,GOT)。每个被这个模块引用的全局数据都有一个8字节条目。

PIC函数调用

延迟绑定 (lazy binding)将过程地址绑定推迟到第一次调用该过程时。

7.13 库打桩机制

使用打桩机制,可以追踪某个库函数的调用次数,验证和追踪输入输出值,甚至可以替换成一个不同的实现。

7.14 处理目标文件的工具

7.15 小结

链接可以在编译时由静态连接器来完成,也可以在加载时和运行时由动态连接器来完成。

第 8 章 异常控制流

异常发生在计算机系统的各个层次,在硬件层,硬件检测到事件会触发控制突然转移到异常处理程序。在操作系统层,内核通过上下文切换将控制从一个用户进程转移到另一个用户进程。

8.1 异常

异常是异常控制流的一种形式,它一部分由硬件实现,一部分由操作系统实现。

异常就是控制流中的突变,用来响应处理器状态中的某些变化。

8.1.1 异常处理

系统中所有可能的每种类型的异常都分配了一个唯一的非负整数的异常号。当系统启动时,操作系统分配和初始化一张称为异常表的跳转表。使得表目 k 包含 k 的处理程序地址。

8.5 信号

一个信号就是一条消息,通知进程系统中发生了一个某种类型的事件。

第 9 章 虚拟内存

虚拟内存是硬件异常、硬件地址翻译、主存、磁盘文件和内核软件的完美交互,它为每个进程提供了一个大的、一致的私有地址空间。

虚拟内存提供了三个重要能力:

  1. 把内存看成是磁盘的高速缓存,在内存中保存活动区域,并根据需要在内存和磁盘之间来回传送数据。
  2. 为每个进程提供一致的地址空间,从而简化了内存管理。
  3. 保护了每个进程的地址空间不被其他进程破坏。