第3章 程序的机器级表示
3.7 过程
过程执行的机制:传递控制、传递数据、分配和释放内存
第7章 链接
链接(linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程,这个文件可被加载(复制)到内存并执行。
7.1 编译器驱动程序
预处理、编译、汇编、链接、加载
7.2 静态链接
静态链接必须完成两个主要任务:符号解析和重定位
符号解析:目标文件定义和引用符号,每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用正好和一个符号定义关联起来
重定位:编译器和汇编器生成从地址0开始的代码和数据节。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些细节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。链接器使用汇编器产生的重定位条目的详细指令,不加甄别地执行这样的重定位
7.3 目标文件
目标文件有三种形式:可重定位目标文件、可执行目标文件和共享目标文件
可重定位目标文件:包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件
可执行目标文件:包含二进制代码和数据,其形式可以被直接复制到内存并执行
共享目标文件:一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载进内存并链接
可执行可链接格式(Executable and Linkable Format, ELF)
7.4 可重定位目标文件
典型的ELF可重定位目标文件:
7.5 符号和符号表
7.8 可执行目标文件
典型的ELF可执行目标文件:
ELF可执行文件被设计得很容易加载到内存,可执行文件的连续的片(chunk)被映射到连续的内存段。程序头部表(program header table)描述了这种映射关系。
程序头部表中有以下信息:
off:目标文件中的偏移
vaddr/paddr:内存地址
align:对齐要求
filesz:目标文件中的段大小
memsz:内存中的段大小
flags:运行时访问权限
7.9 加载可执行目标文件
在程序头部表的引导下,加载器将可执行文件的片(chunk)复制到代码段和数据段。Linux x86-64运行时内存映像如下:
![](https://i-blog.csdnimg.cn/blog_migrate/abb64f47986a201c497f69281e389e39.png)
第8章 异常控制流
异常控制流(Exceptional Control Flow, ECF)
8.1 异常
![](https://i-blog.csdnimg.cn/blog_migrate/75c84d095ae2afff25596bda15585935.png)
在任何情况下,当处理器检测到有事件发生时,它就会通过一张叫做异常表(exception table)的跳转表,进行一个间接过程调用(异常),到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序)。
系统中可能的每种类型的异常都分配了一个唯一的非负整数的异常号(exception number)
异常可以分为四类:中断(interrupt)、陷阱(trap)、故障(fault)和终止(abort)
![](https://i-blog.csdnimg.cn/blog_migrate/e5607dd7b503ca1cd66716c8ce0cd0d9.png)
硬件中断的异常处理程序常常称为中断处理程序
I/O设备,例如网络适配器、磁盘控制器和定时器芯片,通过向处理器芯片上的一个引脚发信号,并将异常号放到系统总线上,来触发中断,这个异常号标识了引起中断的设备。
陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。为了允许对内核服务的受控的访问,处理器提供了一条特殊的syscall n
指令,当用户程序想要请求服务n时,可以执行这条指令。
故障由错误情况引起,它可能能够被故障处理程序修正。一个经典的故障示例是缺页异常
终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如DRAM或者SRAM位被损坏时发生的奇偶错误
8.2 进程
异常是允许操作系统内核提供进程(process)概念的基本构造块
并发流:一个逻辑流的执行在时间上与另一个流重叠
并行流:两个流并发地运行在不同的处理器核或者计算机上
上下文切换
8.3 系统调用错误处理
8.4 进程控制
一个终止了但还未被回收的进程称为僵死进程(zombie)
8.5 信号
一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件
8.6 非本地跳转
C语言提供了一种用户级异常控制流形式,称为非本地跳转(nonlocal jump),它将控制直接从一个函数转移到另一个当前正在执行的函数,而不需要经过正常的调用-返回序列。非本地跳转是通过setjmp
和longjmp
函数来提供的。
8.7 操作进程的工具
STRACE、PS、TOP、PMAP、/proc
第9章 虚拟内存
虚拟内存提供了三个重要的能力:
- 它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域
- 它为每个进程提供了一致的地址空间,从而简化了内存管理
- 它保护了每个进程的地址空间不被其他进程破坏
9.1 物理和虚拟寻址
像异常处理一样,地址翻译需要CPU硬件和操作系统之间的紧密合作。CPU芯片上叫做内存管理单元(Memory Management Unit, MMU)的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理。
9.2 地址空间
9.3 虚拟内存作为缓存的工作
虚拟内存被分割为虚拟页(Virtual Page, VP),物理内存被分割为物理页(Physical Page, PP),大小与虚拟页相同,物理页也被称为页帧(page frame)
任意时刻,虚拟页面的集合都分为三个不相交的子集:未分配的、缓存的、未缓存的
页表、页命中、缺页
在磁盘和内存之间传送页的活动叫做交换(swapping)或者页面调度(paging)
调用malloc
时,虚拟页面在磁盘上创建空间并更新PTE,使它指向磁盘上这个新创建的页面
9.4 虚拟内存作为内存管理的工具
VM简化了链接和加载、代码和数据共享,以及应用程序的内存分配
9.5 虚拟内存作为内存保护的工具
通过在PTE添加标志位
9.6 地址翻译
CPU中的一个控制寄存器,页表基址寄存器(Page Table Base Register, PTBR)指向当前页表
结合高速缓存和虚拟内存
CPU产生一个虚拟地址,MMU就必须查询一个PTE,每次访存,代价是几十到几百个周期,如果PTE碰巧缓存在L1中,那么开销就下降到1个或2个周期。为了试图消除这样的开销,在MMU中设置了一个关于PTE的小的缓存,称为翻译后备缓冲器(Translation Lookaside Buffer, TLB)
多级页表
9.7 案例研究:Intel Core i7/Linux内存系统
Linux虚拟内存系统
![](https://i-blog.csdnimg.cn/blog_migrate/b0e80d27d774bfd9bca123dd699fc2c3.png)
9.8 内存映射
Linux通过将一个虚拟内存区域与一个磁盘上的对象(object)关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射(memory mapping)。虚拟内存区域可以映射到两种类型的对象中的一种:1)Linux文件系统中的普通文件;2)匿名文件
虚拟页面被初始化后,在一个由内核维护的专门的交换文件(swap file)之间换来换去,又称交换空间或交换区域,交换空间限制着当前运行着的进程能够分配的虚拟页面的总数
一个对象可以被映射到虚拟内存的一个区域,要么作为共享对象,要么作为私有对象。
9.9 动态内存分配
分配器有两种基本风格:显式分配器(explicit allocator),如C/C++的malloc/free和new/delete;隐式分配器(implicit allocator),又称垃圾收集器。
9.10 垃圾收集
Mark&Sweep
9.11 C语言中常见的与内存有关的错误
第10章 系统级I/O
输入/输出(I/O)是在主存和外部设备(例如磁盘驱动器、终端和网络)之间复制数据的过程。输入操作是从I/O设备复制数据到主存,而输出操作是从主存复制数据到I/O设备
10.1 UNIX I/O
所有的I/O设备都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。
Linux shell创建的进程开始时都有三个打开的文件,标准输入(描述符为0),标准输出(描述符为1)和标准错误(描述符为2)
10.2 文件
普通文件、目录、套接字、命名管道、符号链接、字符和块设备
10.3 打开和关闭文件
int open(char *filename, int flags, mode_t mode);
int close(int fd);
10.4 读和写文件
ssize_t read(int fd, void *buf, size_t n);
ssize_t write(int fd, const void* buf, size_t n);
10.5 用RIO包健壮地读写
RIO包会自动处理不足值,在像网络程序这样容易出现不足值的应用中,RIO包提供了方便、健壮和高效的I/O。RIO提供了两类不同的函数:无缓冲的输入输出函数、带缓冲的输入函数
10.6 读取文件元数据
int stat(const char *filename, struct stat *buf);
int fstat(int fd, struct stat *buf);
10.7 读取目录内容
第11章 网络编程
11.1 客户端-服务器编程模型
11.2 网络
一个插到I/O总线扩展槽的适配器提供了到网络的物理接口。从网络上接收到的数据从适配器经过I/O和内存总线复制到内存,通常是通过DMA传送。相似地,数据也能从内存复制到网络。
第12章 并发编程
现代操作系统提供了三种基本的的构造并发程序的方法:进程、I/O多路复用 和线程
12.1 基于进程的并发编程
进程的优劣:共享文件表,但是不共享用户地址空间。独立的地址空间使得进程共享状态信息变得更加困难
12.2 基于I/O多路复用的并发编程
假设要求你编写一个echo服务器,它也能对用户从标准输入键入的交互命令做出响应。在这种情况下,服务器必须响应两个相互独立的I/O事件:(1)网络客户端发起连接请求;(2)用户在键盘上键入命令行。如果在accept中等待一个连接请求,我们就不能响应输入的命令。类似的,如果在read中等待一个输入命令,我们就不能响应任何连接请求。
针对这种困境的一个解决办法就是I/O多路复用技术。基本的思想就是使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序
事件驱动设计的一个优点是,它比基于进程的设计给了程序员更多的对程序行为的控制