csapp ch1: A Tour of Computer Systems

这篇文章是csapp第一章阅读笔记。

1. 信息就是:位+上下文

下面是一个简单的hello程序:

#include <stdio.h>

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

这个程序中所有的字符都是ASCII字符,那么类似这样只有ASCII字符的文件,就是文本文件(text file)。

其它的文件,就是二进制文件(binary file)。

上面的hello.c文件展示了一个计算机中的基本想法:

All information in a system, is represented as a bunch of bits. The only thing that distinguishes different data objects is the context in which we view them.

也就是说计算机中,不管是磁盘文件、内存中的程序或者网络中传输的数据,都是由一个个比特位(bits)组成的。用来区别它们的唯一方式是所处的上下文(context)。

所处的上下文不同,那么相同的一串比特信息可以构成不同的信息。

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

为了执行上面的hello程序,我们需要将它编译成一个可执行程序:

gcc -o hello hello.c

这个编译过程经过了如下的几个阶段:

2.1 Preprocessing Phase

预处理阶段:预处理器(cpp)修改原始的C程序hello.c,根据#所包含的头文件,预处理器将所包含的头文件中的内容直接插入到C程序中,得到hello.i文件,这也是一个文本文件。

2.2 Compilation Phase

编译阶段:编译器(cc1)将上一阶段的结果hello.i文件转换成另一个文本文件hello.s,这里的hello.s文件包含的是汇编程序。比如:

main:
    subq    $8, %rsp
    movl    $.LC0, %edi
    call    puts
    movl    $0, %eax
    addq    $8, %rsp
    ret

汇编语言很有用,因为它为不同的高级语言提供了一个通用的编译结果。比如C语言编译器和Fortran编译器都可以生成同样的汇编语言文件。

hello.s还是一个文本文件。

2.3 Assembly Phase

汇编阶段:汇编器(assembler)将上一阶段的结果hello.s文件转换成机器语言指令,然后包装成可重定位目标程序(relocatable object program),将结果保存在hello.o文件中,这里,hello.o就是一个二进制文件了。

2.4 Linking Phase

链接阶段:在上面的hello程序中,我们使用了printf函数,这个函数是C语言标准库提供的,这个函数保存在另一个二进制文件printf.o中,为了使用这个函数,我们需要把上一阶段的结果hello.o和这个printf.o合并到一起。

这就是链接器(ld)的工作。输出的结果hello文件,就是一个可执行的二进制文件了,可以加载到内存中然后执行了。

3. 了解下编译系统是如何工作的很重要

通过上面的过程,我们知道了一个hello.c文本文件是如果通过编译变成一个可执行二进制文件hello的了。

理解这个过程对于程序员是很重要的。原因有一下几点:

  • 理解编译的过程可以优化程序性能
  • 可以理解链接时错误
  • 避免一些安全问题的坑

总之,理解底层的原理,终归是有好处的。

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

为了执行上面的hello程序,只需要;

./hello

即可,就是这么简单。

那么计算机是如何执行存储在hello文件中的指令的呢?首先看一下计算机的整体结构:

4.1 系统的硬件组成

上图展示了系统的硬件组织,主要有下面几个部分:

4.1.1 Buses

总线。总线用来在不同的部分之间传输数据。就像城市里的公路有四车道八车道一样,总线也有一个衡量的大小。总线有一个固定大小的指标:字(word),一个字就是总线能传输数据的最小单位,可以用bytes来表示。

不同的系统字的大小不同,也就是字包含的字节数不一样。32位系统一个字有4个字节;64位系统一个字有8个字节。

4.1.2 I/O Devices

输入输出设备。这就是计算机系统和外界的连接点了,比如键盘、鼠标、显示器和磁盘等。

每一个输入输出设备可以通过controller或adapter连接到系统中。

4.1.3 Main Memory

内存。内存就是dynamic random access memory(DRAM)的集合。

4.1.4 Processor

中央处理器,CPU。这个是用来执行指令的,有逻辑处理单元(arithmetic/logic unit, ALU)、程序计数器(program counter, PC)和寄存器文件(register file)。

CPU通过执行一些简单的指令就可以完成大量的工作:

  • Load
  • Store
  • Operate
  • Jump

4.2 执行hello

介绍完计算器的硬件组成,来看看计算机是如何执行hello这个程序的。

当我们将./hello通过键盘输入之后,这个信息就被存储到寄存器文件中了。这个过程就是读指令。如图:

当我们敲下回车键之后,计算机就知道指令输入完了,接下来就是执行了。shell将可执行的hello文件从磁盘加载进内存中。从上图可以看出,hello程序的数据先被送到了CPU中的寄存器文件中,然后送到内存中。

通过DMA(direct memory access)技术,磁盘中的数据可以直接送到内存中。如图:

hello文件中的指令和数据加载到内存之后,计算机就可以执行程序中的指令了,并将数据输出到屏幕上:

5. 缓存至关重要

从上面可以看到数据在磁盘、内存和CPU中不断穿梭,穿梭的过程中就浪费了一些时间。

所以为了降低时间,一个重要的组成部分就是缓存(cache)。

比如CPU中可以加上L1和L2缓存(static random access memory, SRAM)。

当然,在别的地方也可以加缓存。在计算机系统中,我们会经常看到缓存的身影。

6. 层级结构的存储设备

7. 操作系统管理硬件

当我们执行hello程序的时候,并不是这个程序完成了所有的工作,它依赖于另一个程序来完成大部分的工作,这个程序就是操作系统。操作系统用来管理硬件。结构如下:

7.1 Processes

进程,是操作系统提供的一个抽象。它让运行在这个进程中的程序感觉,只有它自己在使用整个计算机系统。

进程的抽象如下:

为了支持多个进程,需要使用上下文切换(context switching)的机制。说白了就是计算机的使用权在不同的进程中“反复横跳”:

除了用户进程,有一个内核进程。这个内核进程可以管理所有的进程。

7.2 Threads

线程的粒度更低一些,一个进程可以有多个线程,所有的线程共享这一个进程的代码和数据。

多线程编程的代价比多进程编程的代价低一些。

7.3 Virtual Memory

虚拟内存。就像进程是一个抽象一样,虚拟内存也是一个抽象。它给每个进程一个幻觉,好像自己独占了所有的内存一样。

虚拟内存的结构如下:

主要包括:代码段、数据段、堆、共享库、栈和内核虚拟内存。

7.4 Files

文件就是比特串。在linux中,所有的IO设备也被认为是文件。

8. 系统间使用网络通信

上面介绍的都是在一个计算机系统中。那么不同的计算机系统之间怎么通信呢?

答案是使用网络:

网络也可以认为是一个I/O设备,当然也可以当做是一个文件。

9. 重要的主题

9.1 Amdahl’s Law

主旨就是:当我们提升系统中一部分的效率时,那么整体的效果取决于,那部分在整个系统中的重要性以及那部分提升效率的具体值。

令$T_{old}$作为系统升级前所需时间,$α$作为升级部分在系统中的比例,性能提升系数是$k$,那么系统提升后所需的时间:
$$
T_{new}=(1-α)T_{old}+(αT_{old})/k
=T_{old}[(1-α)+α/k]
$$
所以系统整体性能提升的系数$S$是:
$$
S=\frac{1}{(1-α)+α/k}
$$
比如要升级一个系统中占60%的子系统(α=0.6),性能提升系数是3(k=3)。那么整体提升的性能系数是1/[0.4+0.6/3]=1.67。

有意思的是,当k无穷大时,系统整体的性能提升系数是:
$$
S_{∞}=\frac{1}{1-α}
$$
拿上面的例子来说就是2.5。

所以我们在做系统优化的时候,对于一个子系统不一定非要提升得多么多么好,从而白白浪费精力也没有给整个系统带来可观的性能提升。

9.2 Concurrency and Parallelism

并发与并行。在整个计算机的发展过程中,我们主要有两个目的:让计算机做得更多以及,做得更快。

使用并发(concurrency)这个术语来指系统同时做多个任务;使用并行(parallelism)来指系统通过并发执行得更快。

9.2.1 线程级并发

9.2.2 指令级并行

就是处理器同时执行多条指令。

在一个时钟周期内执行多条指令的处理器叫做超标量处理器(superscalar processors)。

9.2.3 单条指令,多个数据并行

最低一层,就是在一个指令中,处理多个数据。这叫做single-instruction, multiple-data(SIMD)。

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

计算机系统中的一个重要概念就是抽象了,比如API就是一个抽象。

下面就是一些这章内容介绍的抽象:


 Previous
csapp ch02 (part 1): Information Storage csapp ch02 (part 1): Information Storage
这篇文章是csapp第二章第一节的阅读笔记。 机器级的程序把内存当做一个大数组,数组里的元素就是字节。 接下来就应该看看,编译器和运行时系统是如何把由一块块字节组成的信息翻译成它本来的养子。 1 十六进制表示法在C语言中,我们可以通过0
2020-05-06
Next 
《中国历代政治得失》笔记 《中国历代政治得失》笔记
利用周末一个下午的时间,重新刷了一遍钱穆的《中国历代政治得失》。能说“西方在政治经验上一般都比较短浅”这话的,只有中国人。这不是自夸,这是事实。从整本书中都能看出钱穆对中国古代政治经验的一种称赞。他推崇汉代的地方政府组织,推崇唐代的中央政
2020-03-30
  You Will See...