[深入理解计算机系统] 01 计算机系统漫游 笔记

计算机 ComputerScience|计算机原理 ComputerSystem

   
   

@ZYX 写于2020年07月08日

第一章 计算机系统漫游

前言

  • 计算机系统是由硬件和系统软件组成的,它们共同工作来运行应用程序。

1.1 信息就是位+上下文

  • hello程序的生命周期是从一个源程序(或者说源文件)开始的 , 即程序员利用编辑器创 建并保存的文本文件
  • 源程序实际上就是一个由值0和1组成的位(bit) 序列
    • 8个位被组织成一组,称为字节
    • 每个字节表示程序中某个文本字符
      • 大部分系统都使用ASCII来记录字符
      • 这种方式实际上就是用一个单字节大小的整数值来表示每个字符
      • 文本文件:只由ASCII字符构成的文件
        • 二进制文件: 所有其他文件

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

  • hello程序的生命周期
    1. 从一个高级C语言程序开始,这种形式能够被人读懂
    2. 为了在系统上运行hello.c,每条C语句都被转化为低级机器语言指令
    3. 这些指令按照称为可执行目标程序的格式打包,并以二进制磁盘文件的形式存放起来
      • 目标程序也称为可执行目标文件
  • 编译系统: 四个阶段的程序(预处理器编译器汇编器链接器

    1. 预处理阶段
      • 预处理器(cpp) 根据以字符#开头的命令,修改原始的C程序。
      • 结果就得到了另一个C程序,通常是以.1作为文件扩展名
    2. 编译阶段
      • 编译器(cel) 将文本文件hello.i翻译成文本文件hello.S,它包含一个汇编语言程序。
        • 汇编语言非常有用,因为它为不同高级语言的不同编译器提供了通用的输出语言
    3. 汇编阶段
      1. 汇编器(as) 将hello.s翻译成机器语言指令
      2. 把这些指令打包成一种叫 可重定位目标程序(relocatable object program) 的格式
      3. 并将结果保存在目标文件hello.o中。
    4. 链接阶段
      • 链接器(Id) 就负责处理这种合并。
      • 结果就得到hello文件,它是一个可执行目标文件(或者简称为可执行文件)
        • 可以被加载到内存中,由系统执行。

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

  • 有一些重要的原因促使程序员必须知道编译系统是如何工作的,其原因如下:
    1. 优化程序性能
      为了在C程序中做出好的编码选择,需要了解机器代码以及编译器将不同的C语句转化为机器代码的方式
    2. 理解链接时出现的错误
    3. 避免安全漏洞
      e.x 缓冲区溢出错误是造成大多数网络和服务器上安全漏洞的主要原因

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

  • 要想在Unix系统上运行可执行文件,我们将它的文件名输入到称为外壳(shell) 的应用程序中
unix> ./hello
hello, world
unix>
  • shell是一个命令行解释器
    1. 它输出一个提示符
    2. 等待你输入一个命令行
    3. 然后执行这个命令
    • 如果该命令行的第一个单词不是shell命令,外壳就会假设是可执行文件的名字,它将加载并运行这个文件

1.4.1 系统的硬件组成

  1. 总线
    • 贯穿整个系统的是一组电子管道,称做总线
    • 它携带信息字节并负责在各个部件间传递
    • 通常总线被设计成传送定长的字节块,也就是字(word)
      • 字中的字节数(即字长)是一个基本的系统参数
        • 有的是4个字节(32位),有 的是8个字节(64位)
  2. I/O设备
    • 输入/输出(I/O)设备是系统与外部世界的联系通道
    • 我们的示例系统包括4个I/O设备:
      1. 键盘:用户输入
      2. 鼠标:用户输入
      3. 显示器:用户输出
      4. 磁盘驱动器(简单地说就是磁盘): 长期存储数据程序
    • 每个I/O设备都通过一个控制器适配器与I/O总线相连
      • 控制器和适配器之间的区别主要在于封装方式:
        1. 控制器是置于I/O设备本身或者系统的主印制电路板(通常称为主板)上的芯片组
        2. 适配器是一块插在主板插槽上的
  3. 主存
    • 是一个临时存储设备
    • 在处理器执行程序时,存放:
      1.程序和程序处理的数据
    • 由一组动态随机存取存储器(DRAM)芯片组成的
      • 从逻辑上来说,存储器是一个线性字节数组
        • 每个字节都有其唯一的地址(即数组索引),这些地址是从零开始的
        • 一般来说,组成程序的每条机器指令都由不同数量的字节构成
        • 与C程序变量相对应的数据项大小是根据类型变化的
  4. 处理器
    • 中央处理单元(CPU),简称处理器,是解释(或执行)存储在主存中指令的引擎。
    • 核心是一个字长的存储设备(或寄存器),称为程序计数器(PC)。
      • 在任何时刻,PC都指向主存中的某条机器语言指令(即含有该条指令的地址)
    • 从系统通电,到断电,处理器一直在
      1. 执行PC指向的指令,
      2. 再更新程序计数器,使其指向下一条指令。
    • 类似按照一个非常简单的指令执行模型来操作的
      • 这个模型是由指令集结构决定的
      • 指令按照严格的顺序执行,这里“执行”包含执行一系列步骤:
        1. 读取:处理器从PC指向的存储器处读取指令
        2. 解释:解释指令中的位
        3. 执行:执行指令指示的简单操作
        4. 更新:更新PC,使其指向下一条指令
          • 这条指令并不一定与存储器中刚刚执行的指令相邻。
            • 比如 if else之类的工作流
      • 操作是围绕着主存寄存器文件(register file)和算术/逻辑单元(ALU)进行的
        • 寄存器文件是一个小的存储设备
          • 由一些1字长的寄存器组成,每个寄存器都有唯一的名字
        • ALU计算新的数据地址值
    • 但是实际上现代处理器使用了非常复杂的机制来加速程序的执行
      • 因此可以这样区分处理器的指令集结构微体系结构
        • 指令集结构描述的是每条机器代码指令的效果
        • 微体系结构描述的是处理器实际上是如何实现的

1.4.2 运行hello程序

  • 运行hello程序时发生了什么:
    1. shell程序执行它的指令: 等待我们输入一个命令
    2. 输入 “./hello”并回车
    3. shell程序将字符逐一读入寄存器
    4. 最后把寄存器中的字符存放到存储器
      图片

1.5 高速缓存至关重要

  • 复制数据就是开销,减缓了程序“真正的”工作。因此,设计者的一个主要目标就是使这些复制操作尽可能快地完成
  • 根据机械原理,较大的存储设备要比较小的存储设备运行得慢,而快速设备的造价远高于同类的低速设备
    • 一个典型的寄存器文件只存储几百字节的信息,而主存里可存放几十亿字节。
    • 处理器从寄存器文件中读数据的速度比从主存中读取几乎要快100倍
  • 针对这种处理器与主存之间的差异,系统设计者釆用了更小、更快的存储设备,即高速缓存存储器(简称高速缓存)
    • 作为暂时的集结区域,用来存放处理器近期可能会需要的信息
    • L1和L2:
      容量 速度
      L1 数万字节 几乎和访问寄存器文件一样快
      L2 数十万到数百万字节 比访问L1的时间长5倍,比访问主存的时间快5〜10倍
      • L1和L2高速缓存是用一种叫做静态随机访问存储器(SRAM) 的硬件技术实现的
      • 比较新的、处理能力更强大的系统甚至有L3高速缓存
    • 系统可以获得一个很大的存储器,同时访问速度也很快,
      • 原因是利用了高速缓存的局部性原理,即程序具有访问局部区域里的数据和代码的趋势
      • 大部分的存储器操作都能在快速的高速缓存中完成。

1.6 存储设备形成层次结构

  • 在处理器和一个又大又慢的设备(例如主存)之间插入一个更小更快的存储设备(例如高速 缓存)的想法已经成为了一个普遍的观念
  • 每个计算机系统中的存储设备都被组织成了一个存储器层次结构
  • 从上至下,设备变得访问速度越来越慢、容量越来越大,并且每字节的造价也越来越便宜
    Figure 1.9 An example of a memory hierarchy.
  • 存储器层次结构的主要思想是一层上的存储器作为低一层存储器的高速缓存
  • 程序员同样可以利用对整个存储器层次结构的理解来提高程序性能

1.7 操作系统管理硬件

  • shell和hello程序都依靠操作系统提供的服务来访问键盘、显示器、磁盘或者主存
  • 操作系统是应用程序硬件之间插入的一层软件
    • 所有应用程序对硬件的操作尝试都必须通过操作系统。
  • 操作系统有两个基本功能:
    1. 防止硬件被失控的应用程序滥用
    2. 向应用程序提供简单一致的机制来控制复杂而又通常大相径庭的低级硬件设备
      Figure 1.10 Layered view of a computer system.
  • 操作系统通过几个基本的抽象概念进程、虚拟存储器和文件)来实现这两个功能
    • 文件是对I/O设备的抽象表示
    • 虚拟存储器是对主存磁盘I/O设备的抽象表示
    • 程则是对处理器主存I/O设备的抽象表示
      Figure 1.11 Abstractions provided by an operating system.

1.7.1 进程

  • 进程是操作系统对一个正在运行的程序的一种抽象。
  • 在一个系统上可以同时运行多个进程,而每个进程都好像在独占地使用硬件
  • 并发运行,则是说一个进程的指令和另一个进程的指令是交错执行的
  • 一个CPU看上去都像是在并发地执行多个进程
    • 这是通过处理器在进程间切换来实现的。
    • 操作系统实现这种交错执行的机制称为上下文切换
  • 上下文
    • 定义:操作系统保持跟踪进程运行所需的所有状态信息
    • 上下文切换:
      • 当操作系统决定要把控制权从当前进程转移到某个新进程时,就会进行上下文切换,即:
        1. 保存当前进程的上下文
        2. 恢复新进程的上下文
        3. 然后将控制权传递到新进程。
  • 从一个进程到另一个进程的转换是由操作系统的内核kernel管理的
    • 内核是操作系统代码常驻主存的部分
    • 当应用程序需要操作系统的某些操作时,他就执行系统调用system call指令,将控制权传递给内核。然后内核执行被请求的操作返回应用程秀
      Figure 1.12 Process context switching.

1.7.2 线程

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

1.7.3 虚拟内存

  • 虚拟内存为每个进程提供了一个假象,即每个进程都在独占地使用主存
  • 每个进程看到的是一致的存储器,称为虚拟地址空间
    Figure 1.13 Process virtual address space.
    请注意,图中的地址是从下往上增大的

  • 在Linux中,地址空间
    • 最上面的区域是为操作系统中的代码和数据保留的,这对所有进程来说都是一样的。
    • 底部区域存放用户进程定义的代码和数据
  • 每个进程看到的虚拟地址空间由大量准确定义的构成,每个区都有专门的功能。
    1. 程序代码和数据:
      • 代码是从同一固定地址开始,紧接着的是和C全局变量相对应的数据位置
      • 代码和数据区是直接按照可执行目标文件的内容初始化的
    2. 堆:
      • 当调用如malloc和free这样的C标准库函数时,可以在运行时动态地扩展和收缩
    3. 共享库:
      • 大约在地址空间的中间部分是一块用来存放像C标准库和数学库这样共享库的代码和数据的区域
    4. 栈:runtime stack
      • 编译器用它来实现函数调用
      • 用户栈在程序执行期间可以动态地扩展和收缩
    5. 内核虚拟存储器:
      • 内核总是驻留在内存中,是操作系统的一部分。地址空间顶部的区域是为内核保留的,
      • 不允许应用程序读写这个区域的内容或者直接调用内核代码定义的函数。
      • 基本思想是把一个进程虚拟存储器的内容存储在磁盘上,然后用主存作为磁盘的高速缓存

1.7.4 文件

  • 文件就是字节序列,仅此而已
  • 系统中的所有输入输出都是逋过使用一小组称为Unix-I/O的系统函数调用读写文件来实现的

1.8 系统之间利用网络通信

  • 实际上,现代系统经常通过网络和其他系统连接到一起。
  • 从一个单独的系统来看,网络可视为一个I/O设备

  • 随着Internet出现,将一台主机的信息复制到另外一台主机已经成为计算机系统最重要的用途之一

  • telnet

1.9 重要主题

总结一下我们旋风式的系统漫游: 系统是硬件和系统软件互相交织的集合体,它们必须共同协作以达到运行应用程序的最终目的

1.9.1 Amdahl 定律

  • 主要思想:
    • 当我们对系统的某个部分加速时,其对系统整体性能的影响取决于-该部分的重要性+加速程度
    • 参数:
      1. $T_{old}$ : 系统执行某应用程序的时间
      2. α : 该程序可提升部分所需时间的占比
      3. k :该部分可提升的比例
    • 可以推出:
      1. $alpha T_{old}$ :可提升部分所需时间
      2. $(alpha T_{old})/k$ :提升后,该部分所需时间
    • 提升后的整个程序所需时间:
      $T_{new} = (1-alpha)T_{old} + (alpha T_{old})/k = T_{old}[(1-alpha)+alpha /k]$
    • 加速比:
      $S=frac{1}{(1-alpha)+alpha/k}$
    • 推论
      • 要想显著加速整个系统,必须提升系统中相当大的部分的’速度

1.9.2 并发和并行

  • 有两个需求是驱动进步的持续动力:
    1. 一个是我们想要计算机做得更多,
    2. 另一个是我们想要计算机运行得更快
  • 并发(concurrency)是一个通用的概念,指一个同时具有多个活动的系统
  • 并行(parallelism)指的是用并发使一个系统运行得更快
    • 可以在系统的多个抽象层次上运用。(这里强调三个层次)
  1. 线程级并发
    • 构建在进程这个抽象之上,我们能够设计出同时执行多个程序的系统,这就导致了并发。
    • 单处理器系统
      • 即使处理器必须在多个任务间切换,大多数实际的计算也都是由一个处理器来完成的系统不
    • 多处理器系统
      • 一个由单操作系统内核控制的多处理器组成的系统
      • 随着多核处理器和超线程(hyperthreading)的出现,这种系统才变得常见。
      • 多核处理器是将多个CPU(称为“核”)集成到一个集成电路芯片上
        • 每个核都有自己的L1和L2高速缓存
        • 但是它们共享更高层次的高速缓存,以及到主存的接口。
      • 超线程有时称为同时多线程(simultaneous multi-threading)
        • 是一项允许一个CPU执行多个控制流的技术
        • 它涉及CPU某些硬件有多个备份:比如 PC和寄存器文件
        • 而其他的硬件部分只有一份,比如执行浮点算术运算的单元。
        • 超线程的处理器可以在单个周期的基础上决定要执行哪一个线程
          • 这使得CPU能够更好地利用它的处理资源
      • 多处理器的使用可以从两个方面提高系统性能:
        1. 它减少了在执行多个任务时模拟并发的需要
        2. 它可以使应用程序运行得更快
  2. 指令级并行
    • 在较低的抽象层次上,现代处理器可以同时执行多条指令的属性称为指令级并行
    • 其实每条指令从开始到结束需要长得多的时间,大约20个或者更多的周期,但是是处理器使用了非常多的聪明技巧来同时处理多达100条的指令。
    • 如果处理器可以达到比一个周期一条指令更快的执行速率,就称之为超标量 (superscalar) 处理器
  3. 单指令、多数据并行
    • 允许一条指令产生多个可以并行执行的操作,这种方式称为单指令、多数据,即SIMD并行。
    • 提供这些SIMD指令多是为了提高处理影像、声音和视频数据应用的执行速度

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

  • 抽象的使用是计算机科学中最为重要的概念之一