CSAPP | Chapter 1 | 计算机系统漫游
计算机系统由系统软件 与硬件 组成。
对于一个简单的 C 程序 hello.c 来说,即便它非常简单,但是为了让它运行,系统的每个主要组成部分都需要协调工作。
c
#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}
信息就是位 + 上下文
hello.c 的 ASCII 码文本表示
hello 程序的生命周期从源程序 (或源文件)开始,源程序实际上是由 0 和 1 组成的位(比特)序列。8 个位又组成 1 个字节。每个字节表示文本中的某些文本字符。
大部分计算机系统用 ASCII 标准,即用单个字节表示每个字符。
像 hello.c 这样只有 ASCII 字符构成的文件称为文本文件 ,所有其他文件都称为二进制文件 。
实际上,系统中所有信息,都是以位序列存储,也就是二进制序列。唯一区分不同数据的就是我们读到这些对象时的上下文。例如,在不同上下文中,一个同样的字节序咧可能表示一个整数、浮点数、字符串或者机器指令。
ISO:International Standards Organization 国际标准化组织
程序被其他程序翻译成不同的格式
为了能运行 hello.c 程序,每条 C 语句都必须被其他程序转化为一系列低级机器语言指令 ,然后这些指令按照一种称为可执行目标程序 的格式打包好,并以二进制磁盘文件的形式存储。目标程序也称为可执行目标文件 。
Unix 系统,源文件到目标文件的转化由编译器驱动完成。
shell
gcc -o hello hello.c
编译系统
预处理器 preprocessor、编译器 compiler、汇编器 assembler和链接器 linker:
预处理阶段 Preprocessing phase
预处理器(cpp)根据以 # 开头的命令修改原始的 C 程序。例如 #include<stdio.h> 命令告诉预处理器读取系统头文件 stdio.h 的内容并把它插入到程序文本中。结果得到了另一个 C 程序,是以 .i 作为文件后缀(扩展名) suffix.
编译阶段 Compilation phase
编译器(cc1)将文本文件 hello.i 翻译成文本文件 hello.s,它包含一个汇编语言程序,这个程序包含了 main 函数的定义。
assembly
main:
subq $8, %rsp
movl $.LC0, %edi
call puts
movl $0, %eax
addq $8, %rsp
ret
汇编阶段 Assembly phase
汇编器(as)将 hello.s 翻译为机器语言指令,把这些指令打包成一种叫做可重定位目标程序(relocatable object program),并将结果保存在目标文件 hello.o 中。hello.o 是一个二进制文件,它包含 main 函数的指令编码(encode)。如果我们用文本编辑器直接打开这个文件,会看到一堆乱码(gibberish)
链接阶段 Linking phase
在 hello 程序中,调用了 printf 函数,它是每个 C 编译器都提供的标准 C 库(standard C library) 中的一个函数。 printf 函数存在于一个名为 printf.o 的单独的预编译好了的目标文件中,而这个文件必须以某种方式合并到 hello.o 程序中。链接器(ld) 就负责处理这种合并,结果得到 hello 文件,它是一个可执行目标文件,可以被加载到内存中并由系统执行。
处理器读取并解释存储在内存中的指令
要运行可执行 hello 程序,在 Linux 系统下我们输入
shell
linux> ./hello
hello, world
可以看到终端输出了 hello, world
shell 会判断输入的命令,如果第一个单词不是内置的指令,那么就会被认为是可执行文件的名字,并且运行它。
系统的硬件组成
Buses 总线
总线负责携带信息字节并且在各个部件中传递。通常设计为传送特定长度的字节块(字)。
I/O 设备
I/O 设备即输入/输出设备,是系统与外部世界的联系通道。每个 I/O 设备都通过一个控制器或者适配器与 I/O 总线连接。
控制器(controller) 是 I/O 设备本身或者系统的主印制电路板 (主板 motherboard)。
适配器(adpater) 是一块插在主板插槽上的卡。
虽然他们的封装方式不同,但是他们的功能都是在 I/O 总线和 I/O 设备之间传递信息。
Main Memory 主存
主存是一个临时存储设备。在处理器执行程序时,用来存放程序和程序处理的数据。
物理上:主存由 动态随机存储存储器 (DRAM) 组成。
逻辑上:存储器是一个线性的字节数组,每个字节都有自己特定的从零开始地址(数组索引)。
Processor 处理器
中央处理单元(CPU),简称处理器,是解释或执行存储在主存中指令的引擎。
它的核心是一个大小为一个自己的存储设备(或寄存器),称为 程序计数器(PC) 。在任何时候,PC 都指向主存中的某条机器语言指令(也就是含有该指令的地址)。
在计算机系统通电到断电,处理器一直在不断执行程序计数器指向的指令,再更新程序计数器,指向下一条指令。
处理器按照指令集架构(ISA: Instruction set architecture) 来操作。处理器读取 PC 指向的内存处的指令并且解释指令中的位,进行指令的操作,并且更新 PC,让它只想下一个指令(这条指令不一定和刚刚执行的指令相邻)。
Main memory 主存
register file 寄存器文件: 由一些单个字长的寄存器组成,每个寄存器都有自己唯一的名字。
ALU(arithmetic/logic unit) 算术逻辑单元: 计算新的数据和地址值
CPU 可能在指令要求下执行的操作:
Load 加载 : 从主存复制一个字节或者一个字到寄存器来覆盖寄存器原先的内容。
Store 存储 : 从寄存器复制一个字节或者一个字到主存的某个位置来覆盖这个位置上原来的内容。
Operate 操作 : 把两个寄存器的内容复制到 ALU,ALU 对他们进行算数操作,并将结果存放到一个寄存器中,覆盖寄存器中原来的内容。
Jump 跳转: 从指令本身抽取一个字并复制到 PC(程序计数器),覆盖 PC 原先的值。
hello 程序的运行
当目标文件 hello 的代码和数据被加载到主存,处理器就开始执行 hello 程序 main 程序中的机器语言指令。这些指令将 "hello, world\n" 字符串中的字节从主存复制到寄存器文件,再从寄存器文件中复制到显示设备,最终显示到屏幕上。
如果使用 direct memory access(DMA) 技术,数据可以不通过处理器而直接从磁盘到达主存。
Caches 高速缓存
由于信息从一个地方移动到另一个地方需要花费大量开销(overhead)。
Cache Memory 高速缓存存储器 作为暂时的集结区域,存放处理器可能会用到的信息。
Cache memories
Memory Hierarchy
The Operating System Manages the Hardware
应用层如果想控制硬件,必须经过操作系统,见下图。
Processes 进程
进程:A process is the operating system's abstraction for a running program.
multicore processors: 多核处理器
uniprocessor system: 单处理器系统
multiprocessor system: 多处理器系统
context switching: 上下文切换
操作系统保持跟踪进程所需的所有状态信息。这种状态就是 上下文 。单处理器系统系统在任何一个时刻只能执行一个进程的代码。当操作系统决定把控制权转换为一个新的进程,就会进行上下文切换,保存当前进程的上下文,恢复新进程的上下文,然后将控制权传递到新进程,新进程就会从上次停止的地方开始。
示例场景中有两个并发的进程∶shell 进程和 hello 进程。最开始,只有 shell 进程在运行,即等待命令行上的输入。当我们让它运行 hello 程序时,shell 通过调用一个专门的函数,即系统调用,来执行我们的请求,系统调用会将控制权传递给操作系统。操作系统保存 shell 进程的上下文,创建一个新的 hello 进程及其上下文,然后将控制权传给新的 hello 进程。hello 进程终止后,操作系统恢复 shell 进程的上下文,并将控制权传回给它,shell 进程会继续等待下一个命令行输入。
内核 kernel 控制从一个进程过渡到另一个进程。
Threads 线程
实际上,一个进程可以由多个称为线程的执行单元组成。
每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。
Virtual Memory
虚拟内存假设每个进程都在独占地使用主存,每个进程看到的内存都是一样的,称为虚拟地址空间。
进程的虚拟地址空间:(图中的地址从下往上增大)
从下往上依次为:
- 程序代码和数据:
所有进程,代码是从同一固定地址开始的。 - 堆:
代码和数据区在一开始运行时就被指定了大小。而堆可以动态扩展和收缩,例如使用 malloc 和 free 函数 - 共享库
类似于 C 标准库 - 栈
在用户虚拟地址空间的顶部是用户栈。编译器用它来实现函数调用。栈和堆一样,可以动态扩展和收缩。每当我们调用一个函数,栈就会增长。从一个函数返回时,栈就会收缩。 - 虚拟内存
应用程序不能直接读写这个区域的内容或者直接调用内核代码定义的函数。只能调用内核来执行这些操作。
Files
文件就是字节序列。
系统中所有输入输出都是通过使用一小组称为 Unix I/O 的系统函数调用读写文件来实现的。
Systems Communicate with Other Systems Using Networks
网络可以视作一个 I/O 设备。现代系统经常通过网络和其他系统连接在一起。
利用 telnet 通过网络远程运行 hello
Concurrency and Parallelism 并发和并行
并发(Concurrency):指的是一个同时具有多个活动的系统
并行(Parallelism);用并发来使一个系统运行的更快。
Thread-Level Concurrency 线程级并发
使用线程,可以在一个进程中执行多个控制流。传统意义上是通过使一台计算机在他正在执行的进程间快速切换来实现的。它允许多个用户同时与系统进行交互。以前用的是 单处理器系统 ,用一个处理器来完成多个任务切换。
多处理器系统 :一个由但操作系统内核控制的多处理器组成的系统。
多核也就是将多个 CPU(核) 集成到一个集成电路芯片上。
4 个处理器核集成在一个芯片上
超线程(同时多线程)Hyperthreading: 允许一个 CPU 执行多个控制流。
Instruction-Level Parallelism 指令级并行
现代处理器可以同时执行多条指令的属性称为指令集并行。
Single-Instruction, Multiple-Data (SIMD) Parallelism 单指令多数据并行
允许一条指令产生多个可以并行执行的操作。
Abstractions in CS
API: application program interface 应用程序接口。