oohcode

$\bigodot\bigodot^H \rightarrow CODE$

csapp chapter1:计算机系统漫游

第一章是对本书的概括性介绍,虽然是概括介绍但是仍然有不少猛料,比如一个c语言是如何编程可执行文件的,计算机的存储器是如何构成的,一个进程在计算机虚拟空间中的结构等等,这些问题能够理解了我感觉自己在写程序的时候有种豁然开朗的感觉,没写一行代码仿佛都看到它们是怎么进入计算机并执行的,实在是爽啊~

信息就是位+上下文

这里说的位其实就是信息其实就是一些字符串组成的,但是不同的信息有着不同的作用,这其实是跟这些信息所处的上下文环境有关系,同样的一串字符在不同的环境中发挥不同的作用~

程序的翻译过程

一个C编写的程序需要经过这怎么样的过程才能让计算机识别并执行?话不多说直接上图
程序翻译过程
从图中可以看到一个c语言源程序需要经过预处理器处理生成.i文件,然后在经过编译器的处理编程.s文件,最后经过汇编器处理编程.o的二进制文件,你以为这样就可以了?no,一个源程序需要其他库文件的支持,还要经过链接器才能生成可执行文件。
预处理阶段
这个阶段会对你写的源代码进行预处理,首先是找到#开头的行,比如说#include 这时候就需要加载需要的文件。最后形成了一个完整的源文件内容:以.i结尾的文件。
编译阶段
编辑器在这个阶段会发挥作用把C语言转换为汇编语言。(由于没有学过编译原理,这里只能囫囵吞枣了,下一步准备研究一下龙书)。
汇编阶段
这个阶段汇编器把.s结尾的汇编文件转化为以.o结尾的二进制文件
链接阶段
经过上面的几个步骤后程序还不能真正的运行起来,需要将程序用到的系统类库等于这个二进制文件进行组合,最后生成一个可执行的目标文件,这个目标文件就可以执行了~
这里还提到了解编译系统的工作原理是大有益处的,其中主要的几点就是可以优化程序性能、理解链接是出现的错误、避免安全漏洞等,这些在以后的章节中会介绍到。

计算机系统的硬件组成

还是直接上图:
计算机系统硬件组成
从图中可以看到一个计算机系统的组成主要包括一下几个重要部分:总线(bus)、IO设备、主存(Main memory)、处理器(CPU)。下面对它们的作用一一介绍:
总线
总线是贯穿整个系统的一组电子管道,它携带信息并在各个部件之间进行传递。总线通常被设计成传送定长的字节块,也就是字(word)。总线宽度决定了计算机系统的寻址能力,因为每根总线代表的是一位bit,我们所说的32bit系统其实就是说总线的宽度为32,也就是4字节,而现在的64bit就是说总线宽度为64位。可见总线的宽度决定了计算机的寻址能力。
IO设备
IO设备这个更好理解了,就是我们能够看到的键盘、鼠标、显示器以及看不到的磁盘,这些都是进行与终端用户进行交互的或者是永久存储数据的。
主存
主存是一个临时的存储设备,当程序运行时程序本身的以及程序运行时需要处理的数据。其实就是我们经常说的内存,服务器内存不够用了很有可能是打开的程序太多或者程序处理的数据太大导致内存被占用过多。从物理上说主存就是一个动态随机存储器(DRAM),从逻辑上说主存其实就是一个线性的数组,每个字节都对应一个唯一的地址。
处理器
处理器全称是中央处理单元(CPU),它是解释或执行存储在主存中指令的引擎。处理器的核心是一个字长的存储设备(或寄存器),称为程序计数器(PC),在任何时刻程序计数器都是指向主存中某条机器语言指令的(即程序计数器存储的是主存中某条指令的地址)。
处理器是整个计算机系统的核心,CPU的操作主要是围绕主存、寄存器文件(register file)和算术/逻辑单元(ALU)进行的。寄存器文件是1字长的寄存器组成的存储设备,没个寄存器都有一个唯一的名字。ALU计算新的数据和地址值。下面简要说一下CPU执行的一些操作:

  • 加载: 把一个字节或一个字从主存复制到寄存器,已覆盖寄存器原来的内容。
  • 存储: 把一个字节或一个字从寄存器复制到主存的某个位置,已覆盖这个位置上原来的内容。
  • 操作: 把两个寄存器的内容复制到ALU,ALU对这两个字做算术操作,并将结果存放到一个寄存器中,已覆盖该寄存器中原来的内容。
  • 跳转: 从指令本身中抽取一个字,并将这个字复制到程序计数器(PC)中,以覆盖PC中原来的值。

指令集结构描述的是每条机器代码指令的效果,微体系结构描述的是处理器实际上是如何实现的。

一个hello world的自白:
我是一个hello world,我使用C语言写的,我被翻译后变成了可执行文件,下面是我如何从可执行文件显示到显示器的过程,过程晦涩,大牛勿入:
下面是执行的过程:

1
2
./hello.out
hello world!

下面是计算机系统所做的工作:
读取用书的输入-->在当前目录下勋章可执行文件-->把可执行文件从磁盘复制到主存-->处理器执行main函数-->把要输出的hello world!从主存复制到寄存器文件-->寄存器内容复制到显示器
前面的工作其实是由外壳程序(其实就是shell)控制的

存储设备的层次结构

根据机械原理,较大的存储设备要比较小的存储设备运行的慢,但是快速设备的成本却别低速设备高。根据这个原理我们知道处理速度方面寄存器文件>主存>磁盘。而成本也是这样,为了平衡成本和速度,以及适应不同的设备,需要在它们之间再加一个缓存设备,叫做高速缓存存储器。它能够存储更多的信息同时也有更快的速度。这就形成了存储设备的层次结构。直接上图:
存储设备的层次结构
了解存储设备的层次结构可以利用这种特点提高程序的性能,这点在第六章会做介绍。

操作系统管理硬件

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

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

操作系统是通过进程、虚拟存储器和文件等抽象概念来实现这两个功能的,这种抽象表示如图所示:
进程的上下文切换
下面对他们进行简要的介绍:
进程是操作系统对一个正在运行的程序的一种抽象,一般来说一个CPU同时只能够运行一个进程,一个CPU运行多个进程是需要通过时间片轮转进行上下文切换。下面这幅图展示了一个CPU如何运行多个进程的:
进程的上下文切换
虚拟存储器是一个抽象概念,它为每个进程提供了一个奸相,即每个进程都在独占地使用主存,每个进程看到的是一致的存储器,称为虚拟地址空间。一个进程的虚拟地址空间如下图所示:
进程的上下文切换
可以看到虚拟地址空间分为几部分,每个区都有专门的功能。下面从地址空间的最下方向上进行一一介绍:

  • 程序代码和数据: 对于所有的进程来说,代码是从同一固定地址开始。紧接着是全局变量和静态变量相对应的数据位置。代码区和数据区是直接按照可执行目标文件的内容进行初始化的。这两个区域是从程序一开始运行是就规定好了,不会再做改变了。
  • 堆: 代码区和数据区后紧随着的是运行时堆。这个区的空间是动态的伸缩的,通过malloc和free这样的C标准库函数进行申请和释放空间。
  • 共享库: 在地址空间的中间部分,是一块用来存放像C标准库和数学库这样共享库的代码和数据的区域。这部分会再第七章介绍是如何工作的。
  • 栈: 位于虚拟地址空间顶部的是用户栈,编译器用它来实现函数调用,比如保持局部变量,保存函数调用的现场等等。
  • 内核虚拟存储器: 内核总是驻留在内存中,是操作系统的一部分。这部分不允许应用程序读写。

ps:注意到堆的地址空间是向上增长的,而栈的地址空间是向下增长的。这样设计是为了最大限度的利用地址空间。可以根据堆和栈的需求动态分配地址空间。
文件
文件其实就是字节序列。每个IO设备都可以看做是文件。系统的所有输入输出都是通过使用一小组成为Unix I/O的系统函数调用读写文件来实现的。第十章会进行详细的介绍。

系统之间利用网络通信

不同的系统之间通过网络进行通信。详细过程会在第十一章进行介绍。

其它

下面几个概念贯穿了计算机系统的所有方面。我们需要铭记在心:

  • 并发(concurrency): 是一个通用的概念,指一个同时具有多个活动的系统;
  • 并行(parallellism): 指的是用并发使一个系统运行的更快。

并发和并行的却别是一个很容易混淆的概念,下面通过一个简单的图像来表述他们的区别:
并发与并行
可以看出并发是在一个时间段内执行不同的任务,但是同一个时间点只能执行一个任务,通过时间片轮转来进行任务的切换。
并行是在同一个时间点不同的任务同时被执行,但是每个任务都有一个对应的执行者。
关于并发和并行其实有很多更加复杂的方式,一个go语言的例子可以学习一下:并发不是并行

总结

本章虽然是一个概括性的介绍,但其实已经包含了本书很多重要的核心内容,以后章节是对每个重要的部分进行一个详细的介绍。了解本章的内容对了解计算机系统有一个整体性的印象,让人有一种把握全局的感觉~