HOME
BLOG
TAGs
Notes for csapp
6月 17 2023

1 计算机系统漫游

计算机系统是由硬件和系统软件组成。

1.1

源文件是位序列

ASCII字符构成的文件称为文本文件,其它的为二进制文件

区分不同数据对象的唯一办法是读取这些数据对象的上下文

数字的机器表示方法,与实际的整数和实数是不同的,它们是对真值的有限近似值

C语言是系统级编程的首选,同时它也非常适用于应用级程序的编写,但C语言缺乏对非常有用的抽象的显性支持,例如类、对象和异常。像C++和Java这样针对应用级程序的新程序语言解决了这些问题

1.2

高级C语言程序->低级机器语言指令->可执行目标程序的格式

编译器驱动程序完成这个过程

分为四个阶段:

源文件.c

  • 预处理器(cpp)
    • .i 修改了的源文件
  • 编译器(ccl)
    • .s 汇编程序
  • 汇编器(as)
    • .o 可重定位目标程序
  • 链接器(ld)
    • 可执行目标程序

GUN环境+Linux内核 = 类UNIX系统

1.3

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

1.4

shell是一个命令行解释器,它输出一个提示符,等待输入一个命令行,然后执行这个命令

系统的硬件组成

  • 总线
    • 字长时一个基本的系统参数
  • I/O设备
    • 每个I/O设备都通过一个控制器或适配器与I/O总线相连
    • 控制器和适配器的区别在于它们的封装方式
      • 控制器时I/O设备本身或者系统的主印制电路板(主板)上的芯片组
      • 适配器是一块插在主板插槽上的卡
  • 主存
    • DRAM
    • 线性字节数组,每个字节都有唯一的地址(数组索引),这些地址是从零开始的
    • 一般来说,组成程序的每条机器指令都由不同数量的字节构成
  • 处理器
    • 寄存器,大小一个字,程序计数器(PC),指向主存中的某条机器语言指令(即含有该条指令的地址)
    • 指令执行模型是由指令集架构决定的
    • 寄存器文件,小的储存设备,由一些单个字长的寄存器组成,每个寄存器都有唯一的名字
    • 可能会执行的操作
      • 加载:从主存复制一个字节或者一个字到寄存器,以覆盖寄存器原来的内容
      • 存储:从寄存器复制一个字节或者一个字到主存的某个位置,以覆盖这个位置上原来的内容
      • 操作:把两个寄存器的内容复制到ALU,ALU对这两个字做算术运算,并将结果存放到一个寄存器中,以覆盖寄存器中原来的内容
      • 跳转:从指令本身中抽取一个字,并将这个字复制到程序计数器(PC)中,以覆盖PC中原来的值
    • 指令集架构
      • 描述的是每条机器代码指令的效果
    • 微体系结构
      • 描述的是处理器实际上是如何实现的

通过直接储存器存取(DMA)技术,数据可以不通过处理器而直接从磁盘到达内存

1.5

根据机械原理,较大的存储设备要比较小的存储设备运行得慢,而快速设备的造价要远高于同类的低速设备

随着半导体技术的进步,处理器与主存之间的差距还在持续增大。加快处理器的运行速度比加快主存的运行速度要容易和便宜得多

高速缓存存储器(cache memory,高速缓存)更小更快,作为暂时的集结区域,存放处理器近期可能会需要的信息

一个典型的寄存器文件只存储几百字节的信息,而主存里可存放几十亿字节

位于处理器芯片上的L1高速缓存的容量可以达到数万字节。一个容量为数十万和数百万字节的更大的L2高速缓存通过一条特殊的总线连接到处理器。

进程访问L2高速缓存的时间要比访问L1高速缓存的时间长5倍,但是这仍然比访问主存的时间快5-10倍

L1、L2高速缓存是用一种叫做静态随机访问存储器(SRAM)的硬件技术实现的

更新的、处理能力更强的系统甚至有三级高速缓存:L1、L2、L3。系统可以获得一个访问速度很快的很大的存储器。因为利用了高速缓存的局部性原理,即程序内具有访问局部区域里的数据和代码的趋势

利用高速缓存可以将程序的性能提升一个数量级

1.6

在处理器和一个较大较慢的设备(例如主存)之间插入一个更小更快的存储设备(例如高速缓存)已成为普遍观念。实际上,每个计算机系统中的存储设备都被组织成了一个存储器层次结构

!图()

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

1.7

把操作系统看成是应用程序和硬件之间插入的一层软件,所有应用程序对硬件的操作尝试都必须通过操作系统

操作系统的两大功能

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

通过三个基本的抽象概念来实现

  • 进程
    • 对处理器、主存和I/O设备的抽象表示
    • 进程是操作系统对一个正在运行的程序的一种抽象
    • 并发运行说的是一个进程的指令和另一个进程的指令是交错执行的
    • 通过处理器在进程之间切换来实现并发地执行多个进程,这种交错执行的机制称为上下文切换
    • 操作系统保持跟踪进程运行所需的所有状态信息。这种状态即是上下文,包括很多信息,比如PC和寄存器文件的当前值,以及主存的内容
    • 在任何一个时刻,单处理器系统都只能执行一个进程的代码。当操作系统决定要把控制权从当前进程转移到某个新进程时,就会进行上下文切换,即保存当前进程的上下文、恢复新进程的上下文,然后将控制权传递到新进程。新进程会从它上次停止的地方开始
    • 从一个进程到另一个进程的转换是由操作系统内核(kernel)管理的。内核是操作系统代码常驻主存的地方。当应用程序需要操作系统的某些操作时,它就执行一条特殊的系统调用(system call)指令,将控制权传递给内核。然后内核执行被请求的操作并返回应用程序
    • 内核不是一个独立的进程,它是系统管理全部进程所用代码和数据结构的集合
    • 线程
      • 一个进程可以由多个称为线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据
      • 多线程之间比多进程之间更容易共享数据,也因为线程一般来说都比进程更有效。当有多处理器可用的时候,多线程也是一种使得程序可以运行得更快的方法
  • 虚拟内存
    • 对主存和磁盘I/O设备的抽象表示
    • 每个进程看到的内存都是一致的,称为虚拟地址空间。实现的基本思想是把一个进程虚拟内存的内容存储到磁盘上,然后用主存作为磁盘的高速缓存
    • 每个进程看到的虚拟地址空间由大量准确定义的区构成,,每个区都有专门的功能
      • 程序代码和数据
        • 对所有的进程来说,代码是从同一固定地址开始,紧接着的是和C全局变量相对应的数据位置
        • 代码和数据区是直接按照可执行目标文件的内容初始化的,在进程一开始运行时就被指定了大小
        • 紧接在上一层后,称运行时堆
        • 当调用像mallocfree这样的C标准库函数时,堆可以在运行时动态地扩展和收缩
      • 共享库
        • 大约在地址空间的中间部分是一块用来存放像C标准库和数学库这样的共享库的代码和数据的区域
        • 位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数调用。和堆一样,用户栈在程序执行期间可以动态地扩展和收缩。每次调用一个函数时,栈就会增长;从一个函数返回时,栈就会收缩
      • 内核虚拟内存
        • 地址空间顶部地区域是为内核保留的。不允许应用程序读写这个区域地内容或者直接调用内核代码定义的函数。相反,应用程序必须调用内核来执行这些操作
  • 文件
    • 对I/O设备的抽象表示
    • 就是字节序列
    • 每个I/O设备,包括磁盘、键盘、显示器,甚至网络,都可以看成是文件
    • 系统中的所有输入输出都是用过使用一小组称为Unix I/O的系统函数调用读写文件来实现的
    • 向应用程序提供了一个统一的视图,来看待系统中可能含有的所有各式各样的I/O设备

1.8

系统不只是一个孤立的硬件和软件的集合体。实际上,现代系统经常通过网络和其他系统连接到一起。对于单独的设备,网络可以视为一个I/O设备。系统可以从主存复制一串字节到网络适配器,数据流经网络到达另一台机器,系统也可以读取从其他机器发送来的数据,并把数据复制到自己的主存

1.9

Amdahl定律

若系统执行某应用程序需要时间为$T_{old}$,假设系统某部分所需的执行时间与该时间的比例为$\alpha$,而该部分性能提升比例为$k$,则总的运行时间应为

$$T_{new}=(1-\alpha)T_{old}+\frac{\alpha T_{old}}{k}$$

则可以计算加速比为

$$S = \frac{1}{(1-\alpha)+\frac{\alpha}{k}}$$

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

表示相对性能

性能提升最好的表示方法就是用比例的形式$\frac{T_{new}}{T_{old}}$,如果有所改进,则比值应大于1。用后缀×来表示比例,因此“2.2×”即为“2.2倍”

更传统的方法是用百分比,这种方法适用于变化小的情况,但其定义是模糊的

考虑一个特殊情况,即$k$趋向于$\infty$时。意味着,可以取系统的某一部分将其加速到一个点,在这个点上,这部分花费的时间可以忽略不计,有式子

$$S_{\infty}=\frac{1}{1-\alpha}$$

例如能将60%的系统加速到不花时间的程度,获得的净加速比将仍只有2.5×

Amdahl定律描述了改善任何过程的一般原则。获得较高的加速比例因子只有通过优化系统的大部分组件才能获得

并发和并行

并发(concurrency)指一个同时具有多个活动的系统,并行(parallelism)指的是用并发来使一个系统运行得更快。并且可以在计算机系统得多个抽象层次上使用。

  • 线程级并发
    • 构建在进程上,可以设计同时有多个程序执行得系统,使用线程,甚至能够在一个进程中执行多个控制流
    • 传统意义上,这种并发执行只是模拟出来的,使通过使一台计算机在它正在执行的进程间快速切换来实现的
    • 这种并发形式允许多个用户同时与系统交互
    • 以上配置为单核处理器系统
    • 当构建一个由单个操作系统内核控制的多个处理器组成的系统时,我们就得到一个多处理器系统
    • 最近,随着多核处理器和超线程(hyperthreading)的出现,这类系统才变得常见
      • 多核处理器是将多个CPU(称为“核”)集成。典型的组织结构为
        • 微处理器芯片有4个CPU核,每个核有独立的L2高速缓存,其中的L1高速缓存分为两部分——一个保存最近取到的指令,另一个存放数据这些核共享更高层次的高速缓存,以及到主存的接口。
      • 超线程,有时候又称为多线程(simultaneous multi-threading)是一项允许一个CPU执行多个控制流的技术
        • 它涉及的CPU某些硬件有多个备份,如程序计数器和寄存器,而其他硬件部分只有一份,比如执行浮点算术运算的单元
        • 常规处理器需要在20000个时钟周期做不同的线程的转换,而超线程的处理器可以在单个周期的基础上决定要执行哪一个线程
        • 这使得CPU可以更好地利用它的处理资源
    • 多处理器系统的使用可以
      • 减少在执行多个任务时模拟并发的需要
      • 使应用程序运行得更快,安东尼是要求程序是以多线程方式来书写
  • 指令级并发
    • 在较低的抽象层次上,现代处理器可以同时执行多条指令的属性称为指令级并行
    • 现代处理器可以保持每个时钟周期2-4条指令的执行效率
    • 每条指令从开始到结束需要长得多的时间,大约20个或更多周期,但是处理器使用了流水线等技巧来同时处理多条指令
      • 在流水线中,将执行一条指令所需要的活动划分成不同的步骤,将处理器的硬件组织成一系列的阶段,每个阶段执行一个步骤,这些阶段可以并行地操作,用来处理不同指令地不同部分
    • 如果处理器可以达到比一个周期一条指令更快地执行速率,就称之为超标量(superscalar)处理器
  • 单指令、多数据并行
    • 现代处理器拥有特殊的硬件,允许一条指令产生多个可以并行执行的操作,这种方式称为单指令、多数据,即SIMD并行
    • 提供这些SIMD指令多是为了提高处理影像、声音和视频数据应用的执行速度。
    • 虽然有些编译器会试图从C程序中自动抽取SIMD并行性,但是更可靠的方法是用编译器支持的特殊的向量数据类型来写程序

抽象

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

在处理器里,指令集架构提供了对实际处理器硬件的抽象。只要执行模型一样,不同的处理器实现也能执行同样的机器代码,而又提供不同的开销和性能

在操作系统中,文件是对I/O设备的抽象,虚拟内存是对程序储存器的抽象,而进程是对一个正在运行的程序的抽象。在此基础上再增加一个抽象:虚拟机,它提供对整个计算机的抽象,包括操作系统、处理器和程序。最近显示出优势,因为一些计算机必须能够运行为不同的操作系统或同一操作系统的不同版本设计的程序

2 信息的表示和处理

数字表示

  • 无符号编码(unsigned)
    • 基于传统的二进制表示法,表示大于或等于零的数字
  • 补码(two’s-complement)
    • 表示有符号整数的最常见的方式,可以为正或者为负的数。
  • 浮点数(floating-point)
    • 以2为基数的科学计数法

表示方法为有限位,结果太大而不能表示时就会溢出(overflow)

整数的表示虽然只能编码一个相对较小的数值范围,但是这种表示是精确的,而浮点数虽然可以编码一个较大的数值范围,但是这种表示只是近似的。