[阅读] CSAPP 读书笔记 —— 计算机系统漫游

本贴最后更新于 1675 天前,其中的信息可能已经天翻地覆

深入理解计算机系统是 豆瓣 接近满分评分的计算机科学丛书,借着这次机会正好考虑将它读一下。这本书重点是执行 x86-64 机器代码的系统,至今为止,我们使用的家用电脑和笔记本电脑,大多都是 x86-64 的处理器。

为了更好的学习,选择使用 linux 发行版 arch 的衍生版 manjaro 进行学习,这 种 “类 Unix” 操作系统也是书中的推荐,同时也需要一些能够简单指令的基础,诸如切换目录等简单操作。

这本书主要是用的 C 语言进行阐述,主要通过 x86-64 处理器上的 GNU GCC 编译器进行编译。

今天开始第一章的学习:

你们已经学会了编程,但是我们和计算机的本质相隔甚远

When you've learned programming,you've been very much separated from the realities of the machine.

你们只认为编程是把文本放入一个小盒子里。不知道为什么,产生的结果和行为是你们希望程序做的。

You just think about code just you put some text into some little box. Somehow, and outcomes of behavior that it hopefully is what you intended the program to do.

这门课的目的是让你深入了解当在执行你的代码时“盒子”在做什么。通过这些来帮助你在你想做的事情上做的更好。

The purpose of this course is to give you enough understanding of what that box is doing when it executes your code. And though that to help you become better at what you're trying to do.

你们会明白程序是如何运行的,机器是如何支持程序运行的。会明白为什么有时程序可以正常运行有时却不能,

You'll understand what programs do how they work, what the machines that support them do. Why sometimes they work really well and sometimes they don't work so well.

hello 程序

学习 C 语言的时候,一定学过这样的代码

#include <stdio.h>

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

从这个代码来开始我们的学习

Q:这个程序的生命是从什么时候开始的?

程序的生命周期是从一个源程序/源文件开始的,或者说是从创建这个文件的时候开始的。源程序实际上就是一个由值 0 和值 1 组成的位(比特)序列,8 个位被组织为一组,称为字节,每个字节表示程序中的某些文本字符,例如这个源程序的大小如下:

hello

这些文本字符都是使用 ASCII 标准来进行表示,这个程序以字节序列的方式储存在文件中时,每个字节都有一个整数值与字符对应。这个时候的 hello.c 只由 ASCII 字符构成,它属于 文本文件,而其他的文件都是 二进制文件

这说明了一个基本思想:系统中所有的信息——包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据,都是由一串比特表示的。区分不同数据对象的唯一方法就是我们读到这些数据对象时的上下文。因为在不同的上下文中,一个同样的字节序列可能表示一个整数、浮点数、字符串或者机械指令。

Q:程序是如何被计算机所识别的?

为了能够让计算机识别并运行这个程序,每条 C 语句都必须被其他程序转化为 低级机器语言 指令,然后这些指令按照一种称为 可执行目标程序 的格式打好包,并以二进制磁盘文件的新式存放起来,目标程序也称为 可执行目标文件

我们可以通过 编译器驱动程序 完成这个过程:

linux> gcc -o hello hello.c

他的编译过程分为四个阶段如下:

compilation system

可以看到,最终它是会被链接器转化为系统所能识别的二进制来进行工作的。

Q:计算机如何处理并运行的呢?

我们通过上一步得到了可执行的文件,现在可以通过 shell 命令行解释器来运行这个文件:

linux> ./hello
hello, world
linux>

在我们理解运行这个程序发送了什么之前,需要先了解一个典型系统的硬件组织

all

  • 总线 —— 贯穿整个系统的一组电子管道,携带信息字节并负责在各个部件间传递。
  • I/O 设备 —— 系统与外部世界的联系通道。
  • 主存 —— 零时存储设备。
  • 处理器 —— 中央处理单元,是解释/执行存储在主存中指令的引擎。

当我们在 shell 中运行这个程序时(即输入 “./hello” 后),shell 程序会将字符逐一读入寄存器,再把他存放到内存中:

all

当我们敲回车键时,shell 程序知道结束了输入,最终他会将 hello 目标文件中的代码和数据从磁盘复制到主存。

利用 直接存储器 (DMA)技术,数据可以不通过处理器直接从磁盘到达主存:

next

然后就是将输出字符串从存储器写到显示器

输出到显示器

我们将它的过程总结下:

  1. 命令输入,读入寄存器,存放内存中
  2. 加载可执行文件,将目标代码复制到主存。(使用 DMA 技术)
  3. 处理器执行程序的 main 中的机械语言指令。
  4. 指令将 “hello, world\n” 字符串字节从主存复制到寄存器文件。
  5. 从寄存器文件中复制到显示设备。

所以核心流程应该如下:

键入 -> 寄存器 -> 内存 -> 主存 -> 执行 -> 寄存器文件 -> 显示设备

高速缓存

我们从上面的示例中揭示了一个重要的问题:系统花费了大量的时间把信息从一个地方挪到另一个地方:

  • hello 程序
    • 程序加载时:机械指令从磁盘上复制到主存
    • 处理器运行程序时:指令从主存复制到处理器
  • 数据串 “Hello, world/n”
    • 开始时:磁盘上复制到主存
    • 最后:主存上复制到显示设备

这种复制过程就是开销,减慢了程序 “真正” 的工作。

系统设计者采用了更小更快地存储设备,称为 高速缓存存储器(简称 cacahe 或 高速缓存),作为暂时的集结区域,存放处理器近期可能会需要的信息。

cache

每个计算机系统中的存储设备都被组织成了一个存储器层次结构

all

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

操作系统管理硬件

对于我们的程序来说,无论 shell 还是 hello,都没有直接访问键盘、显示器、磁盘或者主存。取而代之的是,他们依靠 操作系统 提供的服务。

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

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

操作系统通过几个基本抽象概念(进程、虚拟内存、文件)来实现这两个功能。

进程

操作系统会提供一种假象,就好象系统上只有这个程序在运行。程序看撒谎嗯去是独占地使用处理器、主存和 I/O 设备。

进程 是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程,而每个进程好戏在独占地使用硬件。

并发运行,则是说一个进程的指令和另一个进程的指令是交错执行的。

操作系统保持跟踪进程运行所需的所有状态信息,这种状态,也就是上下文,包括许多信息。

当操作系统决定要把控制器从当前进程转移到某个新进程时,就会进行上下文写换,即保存当前进程的上下文、恢复新进程的上下文。然后将控制权传递到新的进程。新进程就从他上次停止的地方开始。

线程

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

虚拟内存

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

虚拟地址空间

文件

文件就是字节序列,仅此而已。

他向应用程序提供了一个统一的视图,来看待系统中可能含有的所有各式各样的 I/O 设备。

并发与并行

  • 并发:指一个同时具有多个活动的系统
  • 并行:指的是用并发来使一个系统运行得更快

按照系统层次结构中由高到低的顺序强调三个层次

  1. 线程级并发
  2. 指令级并行
  3. 单指令、多数句并行

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

前面介绍了三个抽象:

  • 文件是对 I/O 设备的抽象
  • 虚拟内存是对程序存储器的抽象
  • 进程是对一个正在运行的程序的抽象

现在再增加哎一个新的抽象:虚拟机,它提供对整个计算机的抽象,包括操作系统、处理器和程序。

Q: 信息=位 + 上下文, 什么是上下文? 工作中有哪些例子

上下文即保持跟踪进程运行所需的所有状态信息,它保存了当前进程或程序中的一些状态以及信息。工作中的例子,比如 spring security 中的 SecurityContextHolder,他是了当前用户身份的上下文信息。

Q: RISC 指令集和 CISC 指令集有什么区别,它们的典型 CPU 有哪些?

  • RISC:精简指令集。对指令数目和寻址方式都做了精简,使其实现更容易,指令并行执行程度更好,编译器的效率更高。
  • CISC:复杂指令集。每个指令可执行若干低级操作,诸如从存储器读取、存储、和计算操作,全部集于单一指令之中。

RISC 与 CISC 的主要特征对比

比较内容 CISC RISC
指令系统 复杂,庞大 简单,精简
指令数目 一般大于 200 一般小于 100
指令格式 一般大于 4 一般小于 4
寻址方式 一般大于 4 一般小于 4
指令字长 不固定 等长
可访存指令 不加限制 只有 LOAD/STORE 指令
各种指令使用频率 相差很大 相差不大
各种指令执行时间 相差很大 绝大多数在一个周期内完成
优化编译实现 很难 较容易
程序源代码长度 较短 较长
控制器实现方式 绝大多数为微程序控制 绝大多数为硬布线控制
软件系统开发时间 较短 较长
典型 CPU CDC 6600System/360VAXPDP-11Motorola 68000 家族、x86 等。 DEC AlphaARCARMAVRMIPSPA-RISCPower Architecture(包括 PowerPCPowerXCell)和 SPARC 等。
  • 阅读
    76 引用 • 236 回帖 • 4 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...