《深入理解计算机系统》第一章 计算机系统漫游

深入理解计算机系统

一、计算机系统漫游

1.1 信息就是位+上下文

源程序实际上就是 一个由值0和1组成的位(又称 为比特)序列,8 个位被组织成 一组,称为字节。每个字节表示程序中的某些文本字符。

大部分的现代计算机系统都使用ASCII 标准来表示文本字符,这种方式实际上就是用一个唯一的 单字节大小的整数值°来表示每个字符 。 比如 ,图 1 - 2 中给出了 hello.c 程序的 ASCI I 码表示。

image.png

系统中所有的信息— 包括磁盘文件、内 存中的程序、内存中存放的用户数据以及网络上传送的数据,都是由一串比特表示的。区分不同数据对象的唯一方法是我们读到这些数据对象时的 上下文 (标识这串字节序列的解析方式)。 比 如 , 在不同的上下文中,一个同样的字节序列可能表示一个整数、浮点数、字符串或者机器指令。

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

GCC编译器驱动程序读取源程序文件hel1o .c,并把它翻译成 一个可执行 目标文件he 110。这个翻译过程可分为四个阶段完成,如图 1-3 所示。执行这四个阶段的程序(预处理器、编译器、汇编器和链接器)一起构成了编译系统(compilation system):

image.png
  • 预处理阶段:.c 文件 -> .i 文件

    • 文件包含(#include)

    • 宏替换展开

    • 条件编译:根据条件选择性地包含或者排除代码块

    • 删除注释

  • 编译阶段:.i 文件 -> .s 文件

    • 编译器(ccl)将文本文件hello.i 翻译成文本文件hello.s

    • 汇编语言为不同高级语言的不同编译器提供了通用的输出语言

  • 汇编阶段:.s 文件 -> .o 文件

    • 汇编器(as)将 hello.s 文本文件翻译为机器语言指令,并打包成一种叫做"可重定位目标程序(relocation object program)",并将结果保存在目标文件 hello.o(存放着指令编码) 中。
  • 链接阶段:各种 .o 文件 -> .exe 文件

    • 链接器(ld)将程序所依赖的目标文件 .o (例如 printf.o)合并为最终的可执行文件 hello

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

好处主要有以下几点,并基于此给出一些问题(有空可以研究研究

  • 优化程序性能

    • 一个 switch 语句是否总比一系列的 if-else 语句高效得多

    • 一个函数调用的开销有多大

    • while 循环比 for 循环更有效吗

    • 指针引用比数组索引更有效吗

    • 为什么把循环求和的结果放在一个本地变量,会比将其放到一个通过引用传递过来的参数中,运行起来快很多呢

    • 为什么我们只是简单地重新排列以下算术表达式中的括号就能让函数运行得更快呢

  • 理解链接时出现的错误

    • 链接器报告说无法解析一个引用,这是什么意思

    • 静态变量和全局变量的区别是什么

    • 静态库和动态库的区别是什么

    • 我们在命令行排列库的顺序有什么影响

    • 为什么有些链接错误直到运行时才会出现

  • 避免安全漏洞

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

此刻,hello.c 源程序已经被编译系统翻译为可执行目标文件 hello,并被存放在磁盘上。可以在Unix 系统中的 shell 中输入 hello 进行执行。

1.4.1 系统的硬件组成

image.png
  1. 总线:贯穿整个系统的一组电子管道

通常总线被设计为传送定长的字节块,也就是字(word)。现在大多数机器的字长要么是 4 个字节(32 位),要么是 8 个字节(64位)。

  1. I/O 设备:系统和外部世界的通信通道

示例图中包括四个 I/O 设备:用户输入的键盘鼠标、系统输出的显示器,长期存储数据和程序的磁盘驱动器(磁盘)

每一个 I/O 设备都通过一个控制器或适配器与 I/O 总线相连。控制器和适配器的区别在于它们的封装方式,控制器是 I/O 设备本身或者系统主电路板(主板)上的芯片组,而适配器是一块插在主办插槽上的卡,它们的功能都是在 I/O 总线和 I/O 设备之间传递信息。

  1. 主存:一个临时存储设备,在处理器执行程序时,用来存放程序和数据

从物理上看,主存是由一组动态随机存取存储器(DRAM)芯片组成。从逻辑上看,是一个线性的字节数组,每一个字节都有其唯一的地址(数组索引),地址是从0开始的。

  1. 处理器:中央处理器(CPU),是解释(或执行)存储在主存中指令的引擎。

处理器的核心是一个大小为一个字的存储设备(或者寄存器),称为程序计数器(PC),在任何时候,PC 都指向主存中的某条机器语言指令(即含有该条指令的地址)。

PC 的大小设置为一个字的原因是,PC 存放的主存中的地址,因此要覆盖整个内存地址空间,PC的大小和字长一致,可以确保访问全部内存地址。

从系统通电开始,直到系统断电,处理器一直在不断地执行程序计数器指向的指令,再更新程序计数器,使其指向下一条指令(其在内存中的地址,不一定和刚刚执行的指令相邻)。

执行指令具体为围绕着主存、寄存器文件(register file)和算术/逻辑单元(ALU)进行。寄存器文件是一个小的存储设备,由一些单个字长的寄存器组成,每个寄存器都有唯一的名字。ALU 计算新的数据和地址值。下面是一些简单操作的例子,CPU 在指令的要求下,可能会执行这些操作:

  • 加载(内存 -> 寄存器):从主存复制一个字节或者一个字到寄存器,覆盖寄存器原来的内容

  • 存储(寄存器 -> 内存):从寄存器复制一个字节或者一个字到主存的某一个位置,覆盖主存这个位置的原来内容

  • 操作(寄存器 -> ALU -> 寄存器):把两个寄存器的内容复制到 ALU,ALU 对这两个字做算术运算,并将结果存放在一个寄存器,覆盖该寄存器中原来的内容。

  • 跳转(寄存器 -> PC):从指令本身中抽取一个字,并将这个字复制到程序计数器(PC)中,覆盖 PC 中原来的值。

    • 例如,j 0x00400000 ; 机器码中的目标地址字段为 26 位,那么实际目标地址 = (26 位字段 << 2) | (PC 的高 4 位) → 扩展为 32 位地址

1.4.2 运行 hello 程序

  1. shell 从键盘中读取 hello 命令,shell 命令会将字符逐一读入寄存器,再放入内存中,如图所示:
image.png
  1. 从磁盘中把 hello 目标文件中的代码和数据复制到主存,利用直接存储器存取(DMA,在第6章介绍)如图所示:
image.png
  1. 处理器开始执行 hello 程序的机器语言指令,这些指令将 “hello, world\n”字符串中的字节从主存复制到寄存器,再从寄存器文件中复制到显示设备
image.png

1.5 高速缓存至关重要

hello 程序的机器指令最初是存在磁盘上的,当程序加载时,被复制到主存;当程序运行时,指令又从主存复制到处理器,而数据串“hello world”则是从磁盘复制到主存,又从主存复制到显示设备。这些复制都是开销,所以系统设计时,一个主要目标就是使这些复制操作尽可能快地完成。

根据机械原理,存储设备越大,运行越慢,但是造价就更便宜。处理器从寄存器文件读取速度要比从主存读取快100倍,而且随着半导体的发展,这个差距还在持续增大,因此,加快处理器的运行速度要比加快主存运行速度要更容易和更便宜。

因此,系统设计者在 cpu 中采用了更小更快的存储设备,称为高速缓存存储器(cache memory,简称 cache 或者高速缓存),存放 CPU 近期可能需要的信息,利用静态随机访问存储器(SRAM)的硬件技术实现的。如图所示,cache 又有多级缓存设计,L1 高速缓存的容量可以达到数万字节,速度几乎和寄存器文件一样快,L2 缓存容量可以达到数十万字节到百万字节,通过一条特殊的总线连接 CPU,相应的,访问速度比 L1 缓存要慢 5 倍,不过也比主存快很多。相应的,也有容量更大、速度更慢的 L3 缓存。

image.png

1.6 存储设备形成层次结构

在 cpu 和一个较大较慢的设备(例如主存)之间插入一个更小更快的存储设备(例如高速缓存)的想法已经成为一个普遍观念,实际上,计算机系统中的存储设备都被组织为一个存储器层级结构从上到下,访问速度越慢,容量越大,每字节造价越便宜

image.png

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

1.7 操作系统管理硬件

当 shell 加载和运行 hello 程序时,并没有直接访问键盘、显示器、磁盘、主存、处理器,而是依靠操作系统提供的服务,所以可以把操作系统看成应用程序和硬件之间插入的一层软件,如图所示:

image.png

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

  1. 防止硬件被失控的应用程序滥用;

  2. 给应用程序提供简单一致的机制,用于控制复杂且不同的低级硬件设备;

因此,操作系统通过 3 个基本的抽象概念(文件、虚拟内存、进程)来实现这两个功能,如图所示:文件是对 I/O 设备的抽象,虚拟内存是对主存和磁盘 I/O 设备的抽象,进程则是对处理器、主存、I/O 设备的抽象

image.png

1.7.1 进程

  • 进程的作用: hello 在运行时,操作系统会提供一种假象:系统上只有 hello 在运行,它独占着所有硬件资源(cpu、内存、io 设备),而这种假象就是通过“进程”这个概念实现的。

  • 进程的概念:对一个正在运行的程序的抽象,在它运行的那一刻,确实是独占着资源,但不是一直独占。

  • 并发运行:一个进程和另一个进程的指令交错运行。通过进程间切换,可以实现一个 CPU 并发执行多个进程。这种切换称为,上下文切换

  • 上下文:进程运行的所有状态信息,比如 PC寄存器文件中的值,主存中的内容。

  • 上下文切换:进程之间的切换动作是由操作系统去执行的,具体来说,是由操作系统内核管理的,内核是唯一有权修改进程状态(运行、就绪、阻塞等)的主体

  • 内核:内核是操作系统的核心代码和数据结构的集合,常驻内存,负责管理硬件资源和协调所有进程。它不是独立进程,而是特权代码的集合,直接控制 CPU、内存、设备等底层资源

    • 组成

      • 代码:实现进程调度、内存管理、文件系统、设备驱动等核心功能。

      • 数据结构:

        • 进程控制块(PCB):记录进程状态、寄存器值、内存映射等信息。

        • 页表/段表:管理物理内存与虚拟地址空间的映射。

        • 设备队列:管理 I/O 请求的等待队列。

    • 运行模式:

      • 特权模式(内核态):CPU 在此模式下可执行所有指令(如操作硬件寄存器)。

      • 非特权模式(用户态):应用程序在此模式下运行,受限访问资源。

    • 为何不是独立进程:

      • 无进程上下文:内核代码运行在触发它的进程或中断上下文中,没有自己的独立执行流。

      • 被动执行:内核代码仅在系统调用、中断或异常时被触发,而非主动调度。

image.png

1.7.2 线程

概念:一个进程可以由多个线程的执行单元组成,每一个线程都运行在进程的上下文(寄存器+内存)中,共享同样的代码和全局数据。

优点:

  1. 资源消耗更低,线程共享所属进程的地址空间、文件描述符、全局变量等资源,因此创建和销毁线程只需分配少量栈和寄存器资源,而进程需要独立的地址空间、文件描述符表、内存映射等,资源开销大。

  2. 比进程更容易共享数据,线程共享进程的全局内存,可通过全局变量、堆内存直接通信,无需借助管道、消息队列、共享内存等复杂的进程间通信(IPC)机制

  3. 比进程更高效,切换起来更加方便,线程切换只需保存和恢复寄存器、栈指针等少量上下文,而进程切换需切换页表、刷新 TLB(Translation Lookaside Buffer)、更新内核数据结构(如进程控制块 PCB),开销显著更高,

1.7.3 虚拟内存

概念:虚拟内存是一个抽象概念,为每个进程提供了一个假象,即每一个进程都在独占使用主存,每一个进程看到的内存都是一致的,称为虚拟内存空间。

结构:地址空间最上面的区域是保留给内核代码和数据的,底部区域是存放用户进程的代码和数据,地址从下往上是增大的。

image.png
  • 程序代码和数据: 所有进程的代码都是从同一个固定地址开始的,紧接着全局变量对应的数据位置。代码和数据区是直接按照可执行目标文件的内容初始化的。

  • 堆:代码和数据区在进程一开始运行时就被指定了大小,但是堆可以动态扩展和收缩,通过 malloc 和 free 这样的函数可以做到。从下往上生长。

  • 共享库:存放像 C 标准库和数学库这样的共享库的代码和数据

  • 栈:位于用户虚拟空间顶部,从上往下生长,编译器用它实现函数调用。

  • 内核虚拟内存:为内核保留,不允许应用程序读写这个区域的内容或者直接调用内核代码定义的函数,必须调用内核来执行这些操作。

1.7.4 文件

文件就是字节序列,仅此而己。每一个 I/O 设备,包括磁盘、键盘、显示器、甚至网络,都可以看作文件,系统中所有的输入输出都是通过 Unix I/O 的系统函数调用来读写文件实现的。文件这个概念,可以向应用程序提供了一个统一的视图来看待可能的各种 I/O 设备。

1.8 系统之间利用网络通信

现代系统经常通过网络和其他系统连接到一起,网络可以视为一个 I/O 设备。系统从主存复制一串字节到网络适配器,数据流经过网络到达另一台机器,再被读取到内存中。

image.png

举一个 hello 的远程通信的例子,如下图所示:

image.png

1.9 重要主题

1.9.1 Amdahl 定律

这个定律是用于表征系统某个部分的性能提升对整体系统的提升效果,具体推导不赘述,最终的公示如下图,a 表示系统某部分执行时间占系统整体执行时间的比例,而这部分的性能提升比例为 k。

image.png

举个例子,如果系统某个部分初始耗时比例为 60%(a = 0.6), 其加速比例因子为 3 (k = 3),计算得到的加速比为 1.67 倍。这个比例要远小于 3 的,所以虽然我们对系统的一个主要部分做出了重大优化,但是获取的系统整体加速比明显小于这部分的加速比。因此, Amdahl 定律的一个主要观点:要想显著加速整个系统,必须提升全系统的相当大的部分的速度,单单只加速部分,哪怕加速到极致,对整体的影响也有限

1.9.2 并发和并行

  1. 并发和并行概念
  • 并发:想要计算机做的更多,指一个同时具有多个活动的系统

  • 并行:想要计算机运行得更快,指一个运行更快的系统

  1. 线程级并发

随着多核处理器和超线程的出现,线程级并发的实现更方便了。如图一个多核处理器,其中微处理器芯片上有 4 个 CPU 核,每一个核都有自己的 L1 和 L2 高速缓存,其中 L1 高速缓存又分为两个部分:一个保存最近取的指令,一个存放数据;这些核共享更高层级的缓存(L3 缓存、主存)

image.png
  1. 指令级并发

没看明白。。。

  1. 单指令、多数据并行

没看明白。。。

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

抽象的使用是计算机科学中最重要的一个概念之一;如下图所示,指令集架构是对处理器硬件的抽象,使用这个抽象,机器代码程序可以表现为运行在一个一次只执行一条指令的处理器上(底层是并行执行多条指令的),而且只要执行模型一样,不同的处理器可以处理同样的机器代码,只是开销和性能存在差异。

image.png

二、信息的表示和处理

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容