《深入理解计算机系统》读书笔记
深入理解计算机系统(原书第3版)
★★★★★9.9
Randal E.Bryant David O'Hallaron / 2016 / 机械工业出版社
第一部分 计算机系统漫游
1.2 编译系统如何工作
[](javascript:;)[](javascript:;)[](javascript:;)
[删除](javascript:;)[编译系统](javascript:;)
1. 预处理 宏定义等文件 —> .i文件
2. 编译阶段 .i文件 (翻译)—> .s文件 (汇编指令)
3. 汇编阶段 .s文件 (汇编指令 —> .o 文件(机器指令)
4. 链接阶段 .o 文件 (链接器) —> 可执行文件
1.4 系统硬件构成
[](javascript:;)[](javascript:;)[](javascript:;)
[删除](javascript:;)[系统硬件构成](javascript:;)
总线
I/O设备
主存
主存是一个临时的存储设备,在处理器执行程序时候,用来存放程序和程序处理的数据。
存储器是一个线性的字节数组,每个字节都有其唯一的地址(数组索引)
数据类型 | 字节数
short | 2
int / float | 4
long / double | 8
4. 处理器
解释(或执行)在主存中的指令的引擎
[](javascript:;)[](javascript:;)[](javascript:;)
[删除](javascript:;)[从键盘读取hello命令](javascript:;)
1.7.1 进程
进程是操作系统对正在运行的程序的一种抽象,一个系统可以同时运行多个进程,而每个进程
都好像单独地使用硬件,而并发运行。一个进程的指令和另一个进程的指令是交错执行的。单核多核
系统中,CPU看起来都像是并发地处理多个进程,这是通过处理器在多个进程间切换来实现的。这个
机制叫做上下文切换。(操作系统保持跟踪进程运行所需的所有状态信息成为上下文)。
[](javascript:;)[](javascript:;)[](javascript:;)
[删除](javascript:;)[进程的上下文切换](javascript:;)
[](javascript:;)[](javascript:;)[](javascript:;)
[删除](javascript:;)[进程的逻辑控制流](javascript:;)
1.7.2 线程
一个进程可以有多个成为线程的执行单元组成,每个线程都运行在进程的的上下文中,
共享同样的代码和全局数据。多线程之间比多进程之间更容易共享数据,也因为线程一般
比进程更加高效。
[](javascript:;)[](javascript:;)[](javascript:;)
[删除](javascript:;)[进程的虚拟地址空间](javascript:;)
● 程序代码和数据。对所有的进程来说,代码是从同一固定地址开始,紧接着的是C全局变量相对应的数据位置。代码和数据区是直接按照可执行目标文件的内容叔始化的,在示例中就是可执行文件he11o。在第7章我们研究链接和加载时,你会学习更多有关地址空间的内容。
● 堆。代码和数据区后紧随着着的是运行时堆。代码和数据区在进程一开始运行时就被指定了大小,与此不同,当调用像mal1oC和free这样的的C标准库函数时,堆可以在运行时动态地扩展和收缩。在第9章学习管理虚拟内存时,我们将更详细地研究堆。
● 共享库。大约在地址空间的中间部分是一块用来存放像C.标准库和数学库这样的共享库的代码和数据的区域。共享库的概念非常强大,也相当难懂。在第7章介绍动态链接时,将学习共享库是如何工作的
● 栈。位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数调用。和堆样,用户栈在程序执行期间可以动态地扩展和收缩。特别地,每次我们调用一个函数时,栈就会增长;从一个函数返回时,栈就会收缩。在第3章中将学习编译器是如何使用的。
● 内械虚拟内存。地址空间顶部的区域是为内核保留的。不允许应用程序读写这个区域的内容或者直接调用内核代码定义的函数。相相反,它们必须调用内核来执行这些操作。
虚拟内存的基本思想是把一个进程虚拟内存的内容存储在磁盘上,作为主存
磁盘的高速缓存。
文件
文件就是字节序列,
并发&并行
并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
总结:链接器把程序的各个部分联合成一个文件处理器可以将这个文件加載到内存,并且执行它。现代操作系统与硬件合作,为每个程序提供一种幻象,好像这个程序是在独占地使用处理器和主存,而实际上,在任何时刻,系统上都有多个程序在运行。
第二部分 在系统上运行程序
7. 链接
链接( linking)是将各种代码和数据片段收集并组合成为一个单一文件的过程件可被加载(复制)到内存并执行。
链接可以执行于:
编译时( compile time) —— 源代码被翻译成机器代码时;
加载时( load time) —— 在程序被加载器加载到内存并执行时;
运行时( run time) —— 由应用程序来执行时;
链接器在软件开发中扮演着一个关键的角色,因为它们使得分离编译( separate compilation)成为可能。我们不用将一个大型的应用程序组织为ー个巨大的源文件,而是可以把它分解为更小、更好管理的模块,可以独立地修改和编译这些模块。当我们改变这些模块中的一个时,只需简单地重新编译它,并重新链接应用,而不必重新编译其他文件。
动态链接
动态链接(Dynamic Linking) 要解决空间浪费和更新困难这两个问题,最简单的办法就是把程序的模块相互划分开来,形成独立的文件,而不再将他们静态的链接在一起。简单地讲,就是不对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接。也就是说,把链接这个过程推迟到了运行时再进行,这就是动态链接(Dynamic Linking)的基本思想。
动态链接,在可执行文件装载时或运行时,由操作系统的装载程序加载库。大多数操作系统将解析外部引用(比如库)作为加载过程的一部分。在这些系统上,可执行文件包含一个叫做import directory的表,该表的每一项包含一个库的名字。根据表中记录的名字,装载程序在硬盘上搜索需要的库,然后将其加载到内存中预先不确定的位置,之后根据加载库后确定的库的地址更新可执行程序。可执行程序根据更新后的库信息调用库中的函数或引用库中的数据。这种类型的动态加载成为装载时加载 ;
静态链接
优点:① 代码装载速度快,执行速度略比动态链接库快;缺点:使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费;
动态链接
优点:①更加节省内存并减少页面交换,因而极大地提高了可维护性和可扩展性;③不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数;④适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。缺点:速度比静态链接慢。当某个模块更新后,如果新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件,统统撕掉。
7.6.1 连接器如何解析多重定义的全局符号
链接器的输入是一组可重定位目标模块。每个模块定义一组符号,有些是局部的(只对定义该符号的模块可见),有些是全局的(对其他模块也可见)。如如果多个模块定义同名的全局符号,会发生什么呢?下面是 Linux编译系统采用的方法。
在编译时,编译器向汇编器输出每个全局符号,或者是强( strong)或者是弱(weak),而汇编器把这个信息隐含地编码在可重定位目标文件的符号表表里。函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。
根据强弱符号的定义, Linux链接器使用下面的规则来处理多重定义的符号名:
● 规则1:不允许有多个同名的强符号。
● 规则2:如如果有一个强符号和多个弱符号同名,那那么选择强符号。
● 规则3:如如果有多个弱符号同名,那么从这些弱符号中任意选择一个。
7.7 重定位
一旦链接器完成了符号解析这一步,就把代码中的每个符号引用和正好一个符号定义(即它的一个输入目标模块中的一个符号表条目)关联起来。此时,链接器就知道它的输人目标模块中的代码节和数据节的确切大小。现在就可以开始重定位步骤了,在这个步骤中,将合并输人模块,并为每个符号分配运行时地址。
重定位由两步组成:
● 重定位节和符号定义。在这一步中,链链接器将所有相同类型的节合并为同一类型的新的聚合节。例如,来自所有输入模块的.data节被全部合并成一个节,这个节成为输出的可执行目标文件的.data节。然后,链接器将运行时内存地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输人模块定义的每个符号。当这一步完成时,程序中的每条指令和全局变量都有唯一的运行时内存地址了
● 重定位节中的符号引用。在这一步中,链接器修改代码节和数据节中对每个符号引用,使得它们指向正确的运行时地址。要执行这一步,链接器依赖于可重定位月标模块中称为重定位条目( relocation on entry)的数据结构。
7.10 动态链接共享库
共享库( shared library)是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来。这个过程称为动态链接( dynamic linking),是由一个叫做动态链接器( dynamic linker)的程序来执行的。共享库也称为共享目标( shared object),在 Linux系统中通常用.so后级来表示。微软的操作系统大量地使用了共享库,它们称为DLL(动态链接库)
[](javascript:;)[](javascript:;)[](javascript:;)
[删除](javascript:;)[动态链接共享库](javascript:;)
8 异常控制流
系统必须能够对系统状态的变化做出反应,这些系统状态不是被内部程序变量的,而且也不一定要和程序的执行相关。比如,一个硬件定时器定期产生信号,这个事须得到处理。包到达网络适配器后,必须存放在内存中。程序向磁盘请求数据,然然后,直到被通知说数据已就绪。当子进程终止时,创造这些子进程的父进程必须得到通知现代系统通过使控制流发生突变来对这些情况做出反应。一般而言,我们把这些突变为异常控制流( Exceptional control flow,ECF)。
异常控制流发生在计算机系统的各个次。比如,在硬件层,硬件检测到的事件会触发控制突然转移到异常处理程序。在操作系统层,内核通过上下文切换将控制从一个用户进程转移到另一个用户进程。在应用层,个进程可以发送信号到另一个进程,而接收者会将控制突然转移到它的一个信号处处理程序。一个程序可以通过回避通常的栈规则,并执行到其他函数中任意位位置的非本地跳转来对错误做出反应
● 理解ECF将帮助你理解并发。ECF是计算机系统中实现并发的基本机制。在中的并发的例子有:中断应用程序执行的异常处理程序,在时间上重叠执行的边和线程,以及中断应用程序执行的信号处理程序。理解ECF是是理解并发的第我们会在第12章中更详细地研究并发
[](javascript:;)[](javascript:;)[](javascript:;)
[删除](javascript:;)[(选填) 图片描述](javascript:;)
[](javascript:;)[](javascript:;)[](javascript:;)
[删除](javascript:;)[(选填) 图片描述](javascript:;)
[](javascript:;)[](javascript:;)[](javascript:;)
[删除](javascript:;)[(选填) 图片描述](javascript:;)
8.2.2 并发流
[](javascript:;)[](javascript:;)[](javascript:;)
[删除](javascript:;)[图8-12](javascript:;)
计算机系统中逻辑流有许多不同的形式。异常处理程序、进程、信号处理程序、线程和Java进程都是逻辑流的例子。
一个逻辑流的执行在时间上与另一个流重叠,称称为并发流( concurrent flow),这两个流被称为并发地运行。更准确地说,流X和Y互相并发,当且仅当X在Y开始之后和Y结束之前开始,或者Y在X开始之后和X结束之前开始。例例如,图8-12中,进进程程A和B并发地运行,A和C也一样。另一方面,B和C没有并发地运行,因为B的最后一条指令在C的第一条指令之前执行。
多个流并发地执行的一般现象被称为并发( concurrency)。一个进程和其他进程轮流运行的概念称为多任务( multitasking)。一个进程执行它的控制流的一部分的每一时间段叫做时间片( time slice)。因比,多任务也叫做时间分片( time slicing)。例如,图8-12中,进程A的流由两个时间片组成。
注意,并发流的思想与流运行的处理器核数或者计算机数无关。如如果两个流在时间上重叠,那么它们就是并发的,即使它们是运行在同一个处理器上。不过,有时我们会发现确认并行流是很有帮助的,它是并发流的一个真子集。如果果两个流并发地运行在不同的处理器核或者计算机上,那么我们称它们为并行流( parallel flow),它们并行地运行( running in parallel) 且并行地执行( parallel execution)。
8.2.5 上下文切换
操作系统内核使用一种称为上下文切换( context switch)的较高层形式的异常控制流来实现多任务。上下文切换机制是建立在8.1节中已经讨论过的那些较低层异常机制之上的。内核为每个进程维持一个上下文( context)。上下文就是内核重新启动一个被抢占的进程所需的状态。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用用户栈、状态寄存器、内核栈和各种内核数据结构,比如描述地址空间的页表、包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程。这种决策就叫做调度( scheduling),是由内核中称为调度器( scheduler)的代码处理的。当内核选择一个新的进程运行时,我们说内核调度了这个进程。在内核调度了一个新的进程运行后,它就抢占当前进程,并使用一种称为上下文切换的机制来将控制转移到新的进程,
[](javascript:;)[](javascript:;)[](javascript:;)
[删除](javascript:;)[(选填) 图片描述](javascript:;)
上下文切换
1)保存当前进程的上下文,
2)恢复某个先前被抢占的进程被保存的上下文,
3)将控制传递给这个新恢复的进程。