Loading

CSAPP读书笔记:Chapter I

信息就是Bits + Context

一堆bit可以表示系统中的所有信息,包括磁盘中的文件、内存中的程序和用户数据以及网络中传输的数据,区分它们的唯一方式便是我们查看这些数据对象时所处的上下文(Context)。例如,相同的一串bit在不同的Context中可能代表一个整数,也可能代表一个浮点数,甚至字符串。

程序的转化过程

一个简单的C程序hello.c如下:

#include <stdio.h>

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

高级的C程序文件hello.c被转化为一系列低级的机器语言指令,最后以二进制可执行文件存储在磁盘中。

  • 预处理阶段(Preprocessor):预处理器修改C程序文件中以#号开头的命令。如hello.c中的#include <stdio.h>命令将会告诉预处理器系统头文件stdiio.h的内容,然后将其直接插入到程序文本中。生成的新程序文件为hello.i
  • 编译阶段(Compilation):编译器将hello.i文件转化为由汇编语言组成的hello.s文件。每条汇编语句都描述了一条低级的机器语言指令,不同高级语言编译后的汇编语句是通用的;
  • 汇编阶段(Assembly):汇编器将hello.s文件转化为由二进制机器语言指令的hello.o文件。如果我们用文本编辑器打开该文件,将会展现出一堆乱码;
  • 链接阶段(Linking):由于我们的程序调用了printf函数,而它存在于一个名为printf.o的预编译文件中。链接器负责将该文件并入,得到最终的可执行文件hello

20210428011313

系统的硬件组成

  • 总线(Buses):贯穿整个系统的一组电子管道,负责在各个组件之间传递给定大小的字节块(称为word)。word的大小是系统的基本参数,一般有4字节(32位)或8字节(64位)两种;
  • I/O设备:系统与外部世界连接的桥梁。图中的I/O设备有用于用户输入的键盘⌨️和鼠标🖱️、用于展示的用户输出以及用于长期存储数据和程序的磁盘驱动,每个I/O设备都通过控制器(Controller)或适配器(Adapter)与I/O总线相连。其中,控制器是设备自身或系统主板(Motherboard)上的芯片组,而适配器则是插在主板插槽上的卡;
  • 主存储器(Main Memory):处理器执行程序时存放程序和数据的临时存储。物理上来说,内存是由动态随机存取存储器(DRAM, Dynamic Random Access Memory)芯片组成的集合。而逻辑上则是一个线性的字节数组,每个字节都有其唯一地址(从0开始的数组索引);
  • CPU(Central Processing Unit ):解释或执行主存储器中指令的引擎。
    • PC:CPU的核心是一个大小与word相同的存储设备(或寄存器),称为程序计数器(Program Counter)。PC始终指向主存储器中某条机器语言指令,即内含其地址。CPU会不断地重复执行PC指向的机器指令,并更新PC使其指向下一条指令;
    • Register file:寄存器文件是一个小型存储设备,由一组word大小的寄存器组成,而每个寄存器都有自己的唯一名称;
    • ALU: 算术/逻辑单元(Arithmetic/Logic Unit),能够计算新的数据和地址值。

20210428011429

程序的运行过程

  1. 从键盘上读取命令:当我们在终端中输入命令./hello后,shell程序将逐一读取命令字符串到寄存器(Register),然后存储于内存中;

20210523175310

  1. 从磁盘加载可执行文件到内存:当我们输入回车键后,shell程序得知输入结束,于是开始加载可执行文件hello,其中的代码和数据将通过直接存储器访问技术(DMA, Direct Memory Access)从磁盘拷贝到内存中;

20210428021025

  1. 从内存中将结果输出到显示器:处理器执行hello文件中的机器语言指令,然后将hello world\n字符串中的字节从内存拷贝到寄存器文件中,最终传输到用于展示的屏幕🖥上。

20210428021052

高速缓冲存储

在程序运行的过程中,操作系统花费了大量时间将信息从一个地方拷贝另一个地方,CPU从寄存器文件中读取数据要比从主存储器中读取快近百倍。因此系统设计者引入了一种更小、更快的存储设备,称为高速缓冲存储(Cache Memories or Caches),它会暂存CPU在短期内需要用到的数据。

20210523222131

L1级别的Caches位于CPU芯片之上,容量为上万字节并且拥有和寄存器文件相当的访问速度。而L2级别的Caches则通过一条特殊总线连接到CPU,容量可达十万到百万字节。虽然其访问速度比L1 Cache慢五倍左右,但依然比主存储器要快五到十倍。在某些先进的操作系统中,还会使用L3级别的Cache,它们均是通过静态随机存取存储器(SRAM, Static Random Access Memory)实现的。

计算机系统中的存储器层级结构如下图所示,低层次的存储器作为相邻高层次存储器的Cache:

20210524014408

操作系统对硬件的管理

操作系统是应用程序和硬件的中间层,应用程序对硬件的所有操作必须通过操作系统实现。

20210525005510

操作系统有两个主要功能:防止硬件被失控的应用程序所滥用;为应用程序提供一种简单而统一的机制来处理复杂且通常差异很大的低级硬件设备。上述两种功能是通过下图中的几个基本抽象实现的:

20210525005550

文件(FIles)是对I/O设备的抽象,虚拟内存(Virtual Memory)是对主存储器和I/O设备的抽象,而进程(Processes)则是对处理器,主存储器和I/O设备的抽象。

进程

进程是操作系统对正在运行的程序的抽象,它让我们的hello程序看起来像是系统中唯一运行的程序。多个进程可以并行地在同一个系统中运行,同时每个进程都好像在独占硬件的使用权。而实际上不同进程中的指令是交错执行的,基于下图中的上下文切换:

20210525010332

操作系统会跟踪进程运行所需的所有状态信息,即上下文(Context),它包括了程序计数器PC的当前值,寄存器文件和主存储器的内容之类的信息。 在任何时间点,单处理器系统只能为单个进程执行代码。 当操作系统决定将控制权从当前进程转移到某个新进程时,它需要首先保存当前进程的上下文,然后还原新进程的上下文,最后将控制权传递给新进程以完成上下文切换。

进程间的转换是由操作系统内核(kernel)管理的。kernel并不是一个单独的进程,而是操作系统代码的一部分,始终存在于内存中。当一个应用程序需要操作系统完成一些操作,比如读写文件时,它便会执行一个特殊的系统调用指令,然后将控制权移交给kernel。kernel负责实现程序需要进行的操作,并将结果返回给程序。

线程

每个进程可以由多个执行单元(线程)组成。由于每个线程都运行在进程的上下文中,且不同线程之间可以共享进程内的代码和全局数据,因此线程要比进程更加高效。

虚拟内存

虚拟内存让每个进程都看起来独占了主存储器的使用权。每个进程看到的内存空间都是相同的,称为虚拟地址空间(VIrtual Address Space),其组成如下:

20210525015459

  • 程序代码和数据:所有进程的代码都始于相同的固定地址,随后则是与全局变量相关的数据区。它们的大小在进程开始运行时固定;
  • 堆(Heap):运行时堆是调用mallocfree这样的C标准库生成的结果,其大小可以在进程运行时动态扩缩容;
  • 共享库(Share libraries):存放如C标准库、数学库这样的共享库的代码和数据的区域;
  • 栈(Stack):编译器实现函数调用的区域,其大小同样可以在进程运行时动态扩缩容。如果我们调用一个函数,栈就会增长。而每当一个函数返回时,栈便会缩小;
  • 内核虚拟内存:为kernel预留的内存空间。

文件

文件是由字节组成的序列,因此所有的I/O设备(包括网络)都可以被看作文件。系统中的所有输入和输出都可以通过Unix I/O(一组系统调用),对文件进行读写来实现。

多处理器系统

由单一操作系统内核控制的多个处理器可以共同组成一个多处理器系统(Multiprocessor System),它基于多核(Multi-core)处理器以及超线程技术(Hyperthreading)。

多核处理器

多核处理器将多个CPU集成到单个集成电路芯片中,每个CPU称为一个核(cores)。下图展示了一个典型的多核处理器的架构。其中L1级别的Cache被分成了两部分,分别存储短期内需要使用的指令(i-cache)和数据(d-cache):

20210526001924

超线程技术

超线程技术允许单个CPU执行多个控制流,有时也被称为同步多线程(Simultaneous Multi-threading)。它将CPU中的程序计数器、寄存器文件等硬件资源进行拷贝,从而将一个物理CPU虚拟为多个逻辑CPU。常规的CPU需要大约两万个时钟周期(clock cycle)完成不同线程间的切换,而超线程的CPU可以在单个时钟周期内决定要执行哪一个线程,这使得CPU能够更好地利用它的处理资源。比如一个线程需要等待数据加载到Cache中,那么CPU就会先进行其他线程的处理。

虽然超线程技术能够实现多个线程的并行计算,但是当两个线程同时需要某个资源时,其中一个线程必须让出资源暂时挂起,直到这些资源空闲后才能继续执行。

posted @ 2021-05-26 01:12  koktlzz  阅读(68)  评论(0编辑  收藏  举报