「深入理解计算机系统」第 1 章 计算机系统漫游

171 阅读3分钟

首先明白,计算机系统是由硬件和软件组成的。

计算机系统组成

在正式开始之前,先来看看经典的 hello world 程序。

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

尽管它非常简单,可它的运行需要系统的每个组成部分协调工作。我们通过跟踪 hello world 程序来开始对计算机系统的学习。

信息就是位 + 上下文

首先创建 hello.c 源文件,并保存。源文件实际上是由 0 和 1 组成的位(又称比特)序列,8 个位组成一个 字节 。一个字节表示源文件中的某个字符,如字符 a 是由二进制位序列 01100001 组成的,转成十进制为 97 。

hello.c

这里涉及到一个常用的编码标准:ASCII 。实际上是用一个唯一的数字来表示每个字符,如使用 97 表示 a 。

以下是 hello.c 程序的 ASCII 表示。

ASCII

包括一些看不见的字符,如空格符()(上图中使用 SP 标识)的数值为 32 ,换行符(\n)的数值为 10 。

hello.c 的表示方法说明了一个基本思想:我们可以使用一串位序列来表示系统中的所有信息,区别他们的方式就是在不同的情况下(上下文)使用不同的解释方法。

比如,当我们需要一个数字的时候,计算机把一串位序列解释为数字;当我们需要执行指令的时候,计算机就把一串位序列解释为一条指令。

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

hello.c 源文件能够被我们读懂,但可惜的是计算机无法读懂它。为了运行它,必须把它转为一条条机器指令,然后把这些机器指令打包成一个 可执行文件

在 Linux 中,可以使用 gcc 命令将源文件转为可执行文件:

$ gcc -o hello hello.c

这样在当前目录下,除了源文件 hello.c,还会生成一个新的可执行文件:hello 。

编译

运行它:

$ ./hello
Hello, World!

成功!

从源文件 hello.c 到可执行文件 hello ,编译器做了大量的工作。一共分为四个阶段,分别由四个程序来处理:预处理器、编译器、汇编器和链接器。

编译过程

  • 预处理阶段。预处理器将以 # 开头的语句替换掉。如 hello.c 中的 #include <stdio.h> 会被替换成 stdio.h 文件中的内容。这样就得到一个新的文件 hello.i 。
  • 编译阶段。编译器将 hello.i 翻译成 hello.s 。hello.s 是汇编语言程序,如下所示:
main:
  subq  $8, %rsp
  movl  $.LC0, %edi
  call  puts
  movl  $0, %eax
  addq  $8, %rsp
  ret
  • 汇编阶段。汇编器将 hello.s 翻译成机器语言指令,并打包成 hello.o( 二进制文件)。
  • 链接阶段。hello.c 中包含一个 printf 函数,这个函数是 C 标准库中的函数,它早就被预先编译为 printf.o 文件中了。链接器将 hello.o 和 printf.o 进行合并,得到一个 可执行文件 hello 。

整个编译过程完成。

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

  • 优化程序性能。在 C 语言编程时可以做出更好地选择。因为即使可以实现同样的功能,但不同的编码选择会使程序的性能有所差别,比如该使用指针还是数组索引。
  • 理解链接时出现的错误。大量的错误往往与链接器有关。链接操作是将不同的文件进行合并,这时就可能发生很多意想不到的错误。
  • 避免安全漏洞

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

🚧 施工中。。。