第1章 计算机系统漫游

计算机系统是由硬件和系统软件组成的。总的来说,CSAPP就是通过跟踪hello程序,理解系统在执行该程序时做了什么。

/// hello program
#include <stdio.h>

int main()
{
	printf("hello, world\n");
	return 0;
}

1.1 信息就是位+上下文

hello 程序的生命周期从一个源程序开始,即程序员编写并保存的文件名为 hello.c 的文本文件。源程序实际上就是由0和1组成的位(比特)序列,8个位组成一组,称为字节。每个字节表示程序中的某些文本字符。

采用ASCII码用唯一的单字节大小的整数值来表示每个字符。ASCII码可以用man查询。

man ascii

hello.c的ASCII表示方式说明一个基本思想:系统中所有信息都是由一串比特表示的。区别不同数据对象的唯一方法是我们读到这些数据对象时的上下文。

注意

数字的机器表示方式,与实际的整数和实数是不同的,它们是对真值的有限近似值。具体查看第2章。

补充

C语言作为用于Unix系统的程序语言开发出来,其简洁,易于学习。作为系统级编程的首选。

C语言现如今版本有:

  1. K&R C
  2. ANSIC/C89
  3. C99
  4. C11
  5. C17
  6. C23

1.2 程序被其他程序翻译成不同的格式

其他程序(例如:gcc)将hello.c程序转化为一系列低级机器语言指令,然后这些指令按照一种称为可执行目标程序的格式打包,并以二进制磁盘文件存储。

gcc hello.c -o hello

GCC编译器将hello.c翻译为目标文件(可执行目标文件)hello。翻译过程分为:

--- title: gcc translation --- flowchart TD a("开始")--"hello.c\n源程序(文本)"-->b("预处理器\n(cpp)")--"hello.i\n已修改的源程序(文本)"-->c("编译器\n(ccl)")--"hello.s\n汇编程序(文本)"-->d("汇编器\n(as)")--"hello.o\n可重定位目标程序(二进制)"-->e("链接器\n(ld)")--"hello\n可执行目标程序(二进制)"-->f("结束") g("printf.o")-->e
  • 预处理阶段。预处理器(C Preprocessor, cpp)处理源代码中的预处理指令,如 #include#define 等,并进行宏替换、条件编译等操作。例如,将系统头文件stdio. H插入到程序文本中,如果程序中存在注释,则删除注释。
gcc -E hello.c -o hello.i # -E 告诉gcc仅预处理,-o 输出文件名
  • 编译阶段。编译器(C Compiler, ccl)将高级语言翻译为通用的汇编语言。
gcc -S hello.i -o hello.s # -S 告诉gcc只进行编译部分
  • 汇编阶段。汇编器(Assembler, as)将汇编语言翻译为机器指令,这些指令打包为可重定向目标程序(relocatable object program)格式。
gcc -c hello.s -o hello.o # -c 告诉gcc只进行汇编部分
  • 链接阶段。链接器(Link Editor, ld)将多个目标文件和库文件链接在一起,生成可执行文件或者共享库。由于我们使用了printf函数,它是C编译器提供的标准C库中的一个函数。Printf函数存在于一个名为printf. O的单独的预编译好了的目标文件中,所以需要以某种方式合并到我们的hello. O程序中。
gcc hello.o -o hello

最后得到的hello文件就是目标文件,通过加载到内存中,由系统执行。得到

hello, world

补充

GNU(GNU's Not Unix)项目由Richard Stallman发起的一个免税慈善项目。其目标是开发出来一个包含Unix操作系统的所有主要部件的环境,但内核除外(由Linux项目独立发展)。GNU环境包括EMACS编辑器、GCC编译器、GDB调试器、汇编器、链接器等。

现代开放源码运动的思想起源是GNU项目中的自由软件(free software)概念。

1.3 了解编译系统如何工作是大有益处的

以下原因促使程序员们必须知道编译系统是如何工作的。

  • 优化程序性能
  • 理解链接时出现的错误
  • 避免安全漏洞

1.4 处理器读并解释存储在内存中的指令

shell,一个命令行解释器。

1.4.1 系统的硬件组成

为了理解hello程序在执行过程中做了什么,需要了解一个典型系统的硬件组织。

  1. 总线
    贯穿整个系统的一组电子管道。其携带的信息字节负责在系统中各个部件间传递。通常总线被设计成传递定长的字节块,也就是(word)。字的字长是一个基本系统参数,在32位机器上为4字节,在64位机器上为8字节。

  2. I/O设备
    作为系统与外部世界的联系通道。例如:鼠标、键盘、磁盘等。每个I/O设备通过一个控制器或适配器(二者区别在于封装方式)与I/O总线相连。控制器是I/O设备本身或者系统主板上的芯片组(例如:USB控制器),适配器则是插在主板上的卡(例如:视频采集卡)。

  3. 内存
    临时存储设备。在处理器执行程序时,存储程序和程序处理数据。物理上,为动态随即存取存储器(DRAM)芯片组成。逻辑上,为线性字节数组,每个字节都有其唯一的地址。

  4. 处理器
    解释(或执行)存储在主存中指令的引擎。处理器的核心是一个大小为一个字的存储设备(或寄存器),称为程序计数器(PC),PC指向主存中某条机器语言指令。

    处理器可以视为一个非常简单的指令执行模型,这个模型由指令集架构决定。在这个模型下,指令按照严格的顺序执行,执行一条指令包含执行一系列的步骤。处理器从PC指向的内存处读取指令,解释指令中的位,执行该指令指示的简单操作,最后更新PC,使其指向下一条指令。

    这些简单操作围绕着主存、寄存器文件和算术/逻辑单元进行。

    指令集架构描述每条机器代码指令的效果,微体系结构描述处理器实际上是如何实现机器指令。

1.4.2 运行hello程序

  1. shell程序执行它的指令,等待我们输入一个命令。当我们从键盘输入字符串“./hello”后,shell程序将字符逐一读入寄存器,再把它存放在主存中。
  2. 敲击回车键后,shell程序受到我们结束命令的输入,然后shell执行一系列指令来加载可执行的hello文件。这些指令将hello目标文件中的代码和数据从磁盘复制到主存中。

利用直接存储器存取(DMA)技术,数据则可以不通过处理器直接从磁盘到达主存。

  1. 目标文件hello中的代码和数据被加载到主存,处理器就开始执行hello程序的main程序中的机器语言文件,再从寄存器文件中复制到显示设备,最终显示在屏幕上。

1.5 高速缓存至关重要

上述示例揭示一个重要问题,即系统花费大量的时间把信息从一个地方挪动到另一个地方。

较大的存储设备要比较小的存储设备运行得慢,而快速设备的造价远高于同类的低速设备。为了提高处理器读取数据的速度,处理器并不会从主存中直接读取数据,而是通过高速缓存存储器(cache memory)作为暂时的集结区域,以间接的方式读取数据。

通常在处理器内部集成有L1~L3高速缓存,其以静态随机访问存储器(SRAM)的硬件技术实现。利用高速缓存的局部性原理,从L3到L1访问速度逐步加快。

补充

局部性原理。程序具有访问局部区域里的数据和代码的趋势。通过让高速缓存里存放可能经常访问的数据,大部分的内存操作都能在快速的高速缓存中完成。

1.6 存储设备形成层次结构

存储器层次结构的主要思想是上一层的存储器作为低一层存储器的高速缓存。

1.7 操作系统管理硬件

操作系统有两个基本功能:

  1. 防止硬件被失控的应用程序滥用。
  2. 向应用程序提供简单一致的机制来控制复杂而又通常不大相同的低级硬件设备。

操作系统通过抽象的概念:进程、虚拟内存和文件来实现这两个功能。文件是对I/O设备的抽象表示,虚拟内存是对主存和磁盘I/O设备的抽象表示,进程则是对处理器、主存和I/O设备的抽象表示。

补充

最初Unix中包含很多思想,如层次文件系统、shell概念等。由加州大学伯克利分校在Unix的一系列发行版中增加虚拟内存和Internet协议,称其为Unix 4.x BSD(Berkeley Software Distribution),同时贝尔实验室发行的System V Unix,加之其他厂商各种版本。

众多组织通过加入新的、不兼容的特性使得他们的Unix与众不同,IEEE开始努力标准化Unix开发,后由Richard Stallman命名的“Posix”,该一系列标准就被成为Posix标准。

1.7.1 进程

进程是操作系统对一个正在运行的程序的一种抽象。一个系统上可以运行多个进程,进程间并行运行,让一个CPU看上去在并发地执行多个进程,实际上是处理器通过在进程间切换实现。操作系统实现这种交错执行的机制称为上下文切换

操作系统保持跟踪进程运行所需的所有状态信息。这种状态,称为上下文。任一时刻,单处理器系统只能执行一个进程的代码。当操作系统决定要把控制权从当前进程转移到某个新进程时,就会进行上下文切换(保留当前进程的上下文,恢复新进程的上下文),然后将控制权传递到新进程。

进程间的转换有操作系统内核(kernel)管理,内核是操作系统代码常驻主存的部分。注意,内核不是一个独立的进程,它是系统管理全部进程所用代码和数据结构的集合。

1.7.2 线程

一个进程实际上可以由多个称为线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全家数据。

线程愈加成为重要的编程模型,因为多线程间比多进程间更容易共享数据,线程一般都比进程更高效。

1.7.3 虚拟内存

虚拟内存为每个进程提供一种假象,每个进程都在独占地使用主存。每个进程看到的内存都是一致的,称为虚拟地址空间

每个进程看到的虚拟内存地址空间由大量准确定义的区构成,每个区都有专门的功能。

其基本思想是把一个进程虚拟内存的内容存储在磁盘上,然后用主存作为磁盘的高速缓存。

1.7.4 文件

文件就是字节序列。

1.8 系统之间利用网络通信

在现今,系统不再是孤立的硬件和软件的集合体。对于一个单独系统,网络可以视为一个I/O设备,用于信息字节的互传。

1.9 重要主题

软件是硬件和软件相互交织的集合体,它们必须共同协作以达到运行应用程序的最终目的。

1.9.1 Amdahl 定律

Amdahl 定律的主要思想是,当我们对系统的某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速程度。

若系统执行某应用程序需要时间为 \(T_{old}\) ,假设系统某部分所需执行时间与该时间的比例为 \(\alpha\) ,而该部分性能提升比例为 \(k\) 。即该部分初始所需时间为 \(\alpha T_{old}\) , 现在所需时间为 \((\alpha T_{old})/k\) 。因此,总的执行时间为

\[T_{new}=(1-a) T_{old}+(\alpha T_{old})/k=T_{old}[(1-\alpha)+\alpha/k] \]

那么,计算加速比 \(S=T_{old}/T_{new}\)

\[S=\frac{1}{(1-\alpha)+\alpha/k} \]

举例,系统的某个部分初始耗时比例为 60%,其加速比例因子为 3,则我们可以获得的加速比为 \(1/[0.4+0.6/3]=1.67\) 倍。虽然我们对系统的一个主要部分做出重大改进,但是获得的系统加速比却明显小于这部分的加速比。

Amdahl 定律的主要观点——要想显著加速整个系统,必须提升全系统中相当大的部分的速度。

补充

表示相对性能。最好的表示方式是用比例形式 \(T_{old}/T_{new}\) ,如果有所改进,那么比值应该大于 1。我们用后缀“X”来表示比例。例如,“2.2 X”读作“2.2 倍”。

采用百分比方式,这种适用于变化小的情况,但是定义是模糊的。

1.9.2 并发和并行

数字计算机的整个历史中,有两个需求是驱动进步的持续动力:一个是我们想要计算机做得更多,另一个是我们想要计算机运行得更快。

  • 并发(concurrency)是一个通用的概念,指一个同时具有多个活动的系统。
  • 并行(parallelism)指的是用并发来使一个系统运行的更快。
  1. 线程级并发
    多进程执行,即出现并发。在单处理器系统下,这种并发执行只是模拟出来的。但是在由单操作系统内核控制的多处理组成的系统(多处理器系统),并发才真正实现。

    多核处理器。将多个 CPU 集成到一个集成电路芯片上,每个核心都有自己的寄存器、L1 和 L2 高速缓存以及共享 L3 统一缓存。

    超线程。也称同时多线程(simultaneous multi-threading),允许一个 CPU 执行多个控制流的技术。简单来说,其设计在 CPU 中对某些硬件有多个备份,例如 PC 和寄存器文件,使得超线程 CPU 可以在单个周期的基础上决定要执行哪一个线程。

  2. 指令级并行
    处理器可以同时执行多条指令的属性称为指令级并行

    如果处理器可以达到比一个周期一条指令更快的执行速率,就称为超标量(super scalar)处理器。

  3. 单指令、多数据并行
    允许一条指令产生多个可以并行执行的操作,这种方式称为单指令多数据,即 SIMD 并行。

1.9.3 计算机系统中抽象的重要性

抽象的使用是计算机科学中最为重要的概念之一。第四个抽象概念:虚拟机,是对整个计算机的抽象。

1.10 小结

计算机系统是由硬件和系统软件组成的,它们共同协作以运行应用程序。计算机内部的信息被表示为一组组的位,它们依据上下文有不同的解释方式。程序被其他程序翻译成不同的形式,开始时是 ASCII 文本,然后被编译器和链接器翻译成二进制可执行文件。

处理器读取并解释存储在主存里的二进制指令。因为计算机花费了大量的时间在内存、I/O 设备和 CPU 寄存器之间复制数据,所以将系统中的存储设备划分成层次结构——CPU 寄存器在顶部,接着是多层的硬件高速缓存存储器、DRAM 主存和磁盘存储器。在层次模型中,位于更高层的存储设备比底层的存储设备要更快,单价比特造价也更高。层次结构中较高层次的存储设备可以作为较低层次设备的高速缓存。通过理解和运用这种存储层次结构的知识,程序员可以优化 C 程序的性能。

操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象:

  1. 文件是对 I/O 设备的抽象。
  2. 虚拟内存是对主存和磁盘的抽象。
  3. 进程是对处理器、内存和 I/O 设备的抽象。

同时引出新的抽象:虚拟机是对整个计算机的抽象。

最后,网络提供了计算机系统之间通信的手段。从特殊系统的角度来看,网络就是一种 I/O 设备。

练习题


1.1 假设你是个卡车司机,要将土豆从爱达荷州的 Boise 运送到明尼苏达州的 Minneapolis,全程 2500 公里。在限速范围内,你估计平均速度为 100 公里/小时,整个行程需要 25 个小时。

A. 你听到新闻说蒙大拿州刚刚取消了限速,这使得行程中有 1500 公里卡车的速度可以为 150 公里/小时。那么这对整个行程的加速比是多少?

B. 你可以在 www.fasttrucks.com 网站上为自己的卡车买个新的涡轮增压器。网站现货供应各种型号,不过速度越快,价格越高。如果想要让整个行程的加速比为 1.67X,那么你必须以多快的速度通过蒙大拿州。

A. 首先,加速比 \(S=\frac{1}{(1-\alpha)+\alpha/k}\) ,其中 \(\alpha\) 为蒙大拿州行程与整个行程之比, \(k\) 为未限速速度与平均速度之比,计算得

\[S=\frac{1}{1-\frac{1500}{2500}+\frac{1500}{2500}*\frac{100}{150}}=\frac{5}{4}=1.25X \]

B. 现在得到 \(S=1.67X\approx5/3\) ,那么全程花费的时间 \(T_{new}=T_{old}/S=15\) ,但是在蒙大拿州之外必须花费 \(T_{M}=\frac{2500-1500}{100}=10\) 小时,所以在蒙大拿州间所花费的时间仅有 5 小时,其速度 \(\frac{1500}{5}=300\) 公里/小时。

1.2 公司的市场部向你的客户承诺,下一个版本的软件性能将改进 2X。这项任务被分配给你。你已经确认只有 80%的系统能够被改进,那么,这部分需要被改进多少(即 \(k\) 取何值 )才能达到整体目标?

已知 \(S=2\)\(\alpha=0.8\) ,求 \(k\)

\[\begin{align} 2&=\frac{1}{0.2+0.8/k} \\ k&=\frac{8}{3}\approx2.67 \end{align} \]

这部分需要提升 2.7X 才能达成目标。

posted @ 2024-06-03 14:33  木木亚伦  阅读(5)  评论(0编辑  收藏  举报