[csapp] 计算机系统漫游


layout: post

title: 计算机系统漫游

编译系统

源程序(文本.c) –>
预处理器(cpp) –> 被修改的源程序(文本.i) –>
编译器(cc1) –> 汇编程序(文本.s) –>
汇编器(as) –> 可重定位的目标程序(二进制.o) –>
链接器(ld) –>
可执行目标程序(二进制)

整个过程分为四个阶段

  1. 预处理阶段
    预处理器cpp会对源程序文本中带有#的行,进行修改。例如,#include <stdio.h>,会将stdio.h文件的内容直接插入到源程序文本中。
    简单来说就是“展开”。
    这个过程没有语法检查。
  2. 编译阶段
    编译器cc1将预处理后的源程序文本,翻译为汇编语言文本
    这个过程存在高级语言的语法检查。
  3. 汇编阶段
    汇编器as将汇编文本翻译成机器语言指令,并打包为可重定位目标程序(relocatable object program,即.o文件),这个文件的是二进制文件。
    汇编翻译为机器指令是特异的,即不同的机器(CPU)会有不同的机器指令。
  4. 链接阶段
    链接器ld将我们程序的.o文件与其他库的.o合并,即可得到可执行目标文件。程序中调用的一些并非自己定义的函数,是由其他库提供的。例如,printf()是由标准C库提供。包含printf()的库文件.o需要以某种形式合并到我们自己程序的.o中。
    可执行文件可以被加载到内存,由系统执行。

系统硬件组成

  1. 总线
    贯穿整个系统的管道,在各个部件间传递信息,总线每次传送一个“字”,一个“字”的大小有4个字节(32位),或8个字节(64位)。
  2. I/O设备
    输入输出设备,包括鼠标、键盘、显示器、磁盘驱动器等。
  3. 主存
    (现在一般称其为“内存”)一个为处理器服务的临时存储设备。处理器执行程序时,用来存放程序和程序处理的数据。
    物理上来说,内存由一组 _动态随机存取存储器_(DRAM)芯片组成。
    逻辑上来说,是一个线性的字节数组,每个字节都有唯一的地址(即数组索引)
  4. 处理器
    中央处理单元(CPU),核心是一个“字”大小的 _寄存器_,称为程序计数器(PC)。在任何时刻PC都指向内存中的某条机器语言指令(即,PC保存着该条指令的内存地址)。 另外两部分是:寄存器文件(register file)和算数逻辑单元(ALU)。
    寄存器文件是由一系列一个“字”大小的寄存器组成,且每个都有自己的名字。
    算数逻辑单元用于计算新的数据和地址值。
    另,关于高速缓存
    处理器的速度是很快的,而相比起来存储设备(内存)就要慢很多;速度越快的存储单元,其成本越高。CPU与内存之间的速度差异,导致CPU性能不能最大限度的发挥,为了解决这个问题,在CPU与内存之间加入了高速缓存,使用的硬件技术是 _静态随机访问存储器_(SRAM)。可以联想到:由CPU的寄存器文件为L0向下、高速缓存L1、高速缓存L2……形成的金字塔形状的存储设备层次结构图。

好啦~试想一下一个hello_world程序是如何在上面这套硬件上运行的。
Shell等待我们输入指令,Shell逐一读入我们输入的字符到CPU寄存器,然后存放到内存中。当我们按回车后,Shell就会了解到我们已经结束了命令的输入,执行一系列命令加载我们的可执行文件——将可执行文件中的代码和数据复制到内存。加载到内存后,处理器开始执行可执行程序中的各条指令,例如,将“Hello world!”从内存复制到寄存器文件,再从寄存器文件复制到显示设备。

操作系统管理硬件

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

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

操作系统通过三个基本抽象概念来实现这两个功能:(由小到大)

  1. 文件
    对I/O设备的抽象表示。
  2. 虚拟存储器
    对内存和磁盘I/O设备的抽象表示;对程序存储器的抽象。
  3. 进程
    对CPU、内存和I/O设备的抽象表示;对一个正在运行的程序抽象。

进程 是操作系统对一个正在运行的程序的一种抽象。每个进程都好像在独占地使用硬件。 一个CPU看上去都像是在并发地执行多个进程,这是通过处理器在进程间切换来实现。操作系统实现这种交错执行的机制称为 上下文切换。操作系统保持跟踪进程运行所需的所有状态信息,这种状态,就是 上下文。它包含PC寄存器、寄存器文件的当前值,以及内存的内容。
一个进程实际上可以由多个称为 线程的执行单元组成。每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。

虚拟存储器 是一个抽象概念,它为每个进程提供了一个假象,即每个进程都在独占地使用内存。其基本思想是把一个进程虚拟存储器的内容存储在磁盘上,然后用内存最为磁盘的高速缓存。每个进程看到的是一致的存储器,称为 虚拟地址空间。从低地址开始,依次是:(可参照书P12)
程序代码和数据对于所有的进程,代码都是从同一个固定地址开始,紧接着的是全局变量对应的数据的位置。
紧跟代码和数据区,大小可以动态改变。(malloc, free)
共享库例如C标准库和数学库等这样的共享库。
位于用户虚拟地址空间顶部的是 _用户栈_,编译器用它来实现函数调用,大小也是动态的。
内核虚拟存储器地址空间顶部是为内核保留的。内核总是驻留在内存中,是操作系统的一部分。不允许应用程序直接读写此区域内容或直接调用内核代码定义的函数。

文件 就是字节序列,仅此而已。每个I/O设备,包括磁盘、键盘、显示器、网络等都被视为文件。向应用程序提供了一个统一的视角。

并发和并行

并发 (concurrency)是一个通用的概念,指一个同事具有多个活动的系统。
并行 (parallelism)指的是用并发使一个系统运行得更快。并行可以在计算机系统的多个抽象层次上运用,按照系统层次结构中由高到低的顺序重点强调三个层次:

  1. 线程级并发
    (不是说的进程里的线程,而是CPU的处理线程)
    单核处理器在它执行的进程间快速切换的方式实现。
    多核处理器将多个CPU集成到一个集成电路芯片上。每个核都有自己的L1和L2高速缓存,但它们共享更高层次的高速缓存,和到内存的借口。
    超线程,有时称为“同时多线程”,是一项允许一个CPU执行多个控制流的技术。它设计CPU某些硬件有多个备份,比如程序计数器和寄存器文件;而其他的硬件部分只有一份,比如执行浮点运算的单元。拥有多个备份硬件,在某个线程需要等待时,CPU就可以选择执行另一个硬件上的线程,而不需要额外的开销。
  2. 指令级并行
    在较低的抽象层次上,现代处理器可以同时执行多条指令的属性称为“指令级并行”。使用 流水线技术,将执行一条指令所需要的活动划分为不同的步骤,将处理器的硬件组织称一系列阶段,每个阶段执行一个步骤。这些阶段可以并行操作,用来处理不同指令的不同部分。
    如果处理器可以达到比一个周期一条指令更快的执行速率,就称之为 超标量处理器。
  3. 单指令、多数据并行
    允许一条指令产生多个可以并行执行的操作,这种方式称为 单指令、多数据,即SIMD并行。例如,并行地对4对单精度浮点数(float)做加法的指令。

END

Written on November 2, 2019