计算机操作系统 -- 深入理解计算机系统
第一章:计算机系统漫游
1.1 约定
- 本次分享的内容涉及到的计算机的一些机制都以现在世界上现存的典型协议进行描述,不对一些为特殊场景下进行定制的协议进行阐述,例如我们会认为文本字符在计算机上的表示是通过基础的ASCII标准,涉及到字符存储时不去对别的标准原理进行介绍。
- 由于第一章里面直接对整个系统的运行进行了笼统的介绍,涉及的内容太多,所有笔记中指名道姓提及本书后续某章会详细讲解的内容和问题,朋友们就别问了,免得大家都尴尬 :)
1.2概述
本章我们试图去简单的介绍一个最基本的hello world程序的运行原理,实际上,这一整本书就是在详细的描述这一程序运行过程中计算机各个模块之间的配合调度,以下为hello.c文件内容
#include <stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
1.3 hello world生命周期
存储
上面的hello.c文件在磁盘上是以一串byte格式进行的存储,当我们用文本工具打开时会翻译为方便人类阅读的格式
- 编码(把人类字符翻译为byte数据的过程):
- ASCII:这里也简单的对ASCII编码进行介绍,每一个字符都被翻译为一个唯一与其对应的整数值,也就是一个byte(byte 的取值范围为 1000 0000 到 0111 1111)
- Unicode:为了解决ASCII码不能容积而产生的编码标准,通常占用两个字节
- UTF8:为了解决Unicode造成的空间浪费,UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,汉字通常是3个字节(所以UTF8天生兼容了远古时期的ASCII码数据)
- 关于汉字的存储,在txt的格式下汉字其实也只是占用了一个字节,这里有一帮哥们为了节省空间为汉字做了一个汉字库(16*16库需求282.5KB磁盘空间),真正存储的时候是存储了汉字在该库里面的地址(内码)
- 计算机中数据的存储:所有的信息都是以一串byte方式进行的存储。
编译
hello.c是高级语言(C语言),需要先转化为一系列低级机器语言指令(存储到hello文件中),这个过程叫编译,编译的工具叫编译器驱动程序。
为何要了解编译过程和原理(细节本书第六章会讲,这里受篇幅所限我就不展开讲了,就当我知道:):
- 优化程序性能
- 理解链接是出现的错误
- 避免安全漏洞
由于不同的语言有不同的运行环境,这个地方不好统一详尽的介绍,只对Java的运行过程有过一些探索,如果大家有兴趣,推荐《深入理解java虚拟机》
运行
运行是指,处理器读取磁盘上编译后的文件hello到内存并逐一的解释内存中的指令的过程。
为了能解释运行的过程,我们要先了解下计算机的硬件组成
1.4 计算机硬件组成
总线
用于连接计算机各个组件的通信管道,通常被设计为传输定长的字节块,系统中字长为4字节的就是我们所说的32位计算机,字长为8字节的是64位计算机。
IO设备
输入输出设备,典型的IO设备:鼠标、键盘、显示器以及硬盘。(第六章会详细讲解IO设备的工作原理)
主存
主存临时存储设备由一组动态随机存取存储器(RAM)芯片组成,我们所说的内存就是由主存(RAM)和只读存储器(ROM)组成。
处理器
中央处理单元(CPU)简称处理器,是解释或执行存储在主存中指令的引擎。
- 程序计数器:CPU的核心是一个大小为一个字(字的长度决定了计算机是多少位的)的存储设备(寄存器)也称为程序计数器。
- 在我个人所了解的模型里面程序是以栈的方式来存储指令,而程序计数器可以看做指向栈顶的指针,CPU会一直执行程序计数器所指向的指令(多线程切换的时候就是要记录下这个程序计数器的位置,再次获得CPU使用权的时候继续执行)
- 本书第四章会详细讲解处理器是如何实现的,第五章会讲解处理器工作原理
1.5 再次运行hello
在了解了计算机的大体组成后,我们已经极度膨胀,再次来探究hello的运行过程。于是我们在shell命令行执行:
./hello

简单描述:将hello指令从磁盘复制到寄存器、再从寄存器复制到主存、寄存器去执行主存上的指令、将“hello world\n”复制到寄存器再复制到显示设备上。
- 这个时候朋友们就要问了,为啥要复制来复制去,为什么寄存器不直接从磁盘上读“hello world\n”然后丢给显示器
解:磁盘读写速度慢,很慢,非常慢;寄存器的读写速度大概是磁盘的10亿倍,相反的寄存器的存储空间非常小(几百字节),
所以我们需要先把需要用到的数据写到内存里面去(内存的读写比磁盘快1000w倍,比寄存器慢100倍),之后寄存器去内存
里面取指令进行执行。
其实在上面的流程里面有一部分是可以被优化的,就是一开始从磁盘读取到内存的过程还是经过了寄存器,这部分其实和我们的目标不符合,有一个技术叫直接存储器存取技术(DMA,第六章会详解),它可以直接将指令从磁盘读取到内存而不经过寄存器
这个时候朋友们又要问了,既然是这样那我们把所有数据都存到内存甚至是寄存器不就好了吗?
这里给个出一个参考价格,一个3000左右CPU的三级缓存数据大概是8*32k、8*512k、2*16M,
一个8G内存条价格大概300,一个2T的机械硬盘大概350
- 所以在向生活低头之后,我们选择用缓存来解决CPU和磁盘之间日益增长的阶级矛盾。
缓存
目前大多数的CPU都会采用三级缓存的方式来降低CPU和内存之间数据交互的时间消耗,下面是一个典型的计算机系统中缓存层级图:
所有上层的存储器都可以看做是下层存储器的高速缓存
1.6 操作系统
让我们回顾刚才hello程序运行的流程,我们直接是向shell程序发起运行的指令,这时候shell其实是不能直接访问磁盘和CPU之类的硬件设备的,它需要依赖操作系统提供的服务,以下是计算机系统的分层示意图:
操作系统的作用:
- 防止应用滥用硬件资源(这里把操作系统看做一个公正的裁判,但有时候裁判也会耍流氓)

- 向应用程序提供简单一致的底层驳杂硬件的调用接口(不同的硬件通过对应的驱动程序和操作系统达成统一,操作系统再将统一的标准接口面向应用程序)
下面我们对操作系统的几个重要特性进行简单的介绍
进程
进程是对计算机上正在运行的程序的一种抽象,它使得程序(例如我们的hello)看上去是在独占计算机的硬件资源,是计算机科学中最重要和成功的概念之一。
一个CPU在一个时间上只能执行一个进程,并发的原理是处理器来回切换不同的进程,CPU离开时记录进程执行的上下文(寄存器PC、主存内容等)状态,进程再次获得CPU的调用时根据记录的状态继续任务
线程
以前我们认为一个进程只有一个单一的控制流,但在现代计算机系统中,进程是由多个称为线程的单元组成的,线程都是运行在进程的上下文中,所以同一个进程下的线程间共享资源,在目前处理器普遍多核的情况下,多线程很更好的利用系统资源提高效率(在本书第12章会详解并发的概念)
虚拟内存
“虚拟内存是一个抽象概念,它为每个进程提供一个假象,即每个进程都在独占地使用主存”
“基本思想是把一个进程虚拟内存的内存存储到磁盘上,然后用主存做为磁盘的高速缓存”
第九章会解释它如何工作,这里的细节不是很清楚,但个人猜测这个策略应该是导致电脑硬盘从机械升级到固态后能很好的缓解内存压力而大幅度提高电脑整体运行速度的原因。
这里有一个小建议,如果大家想要提高旧电脑的运行速度,绝大多数的情况下升级一个固态硬盘都是性价比最高的方案,不用扩展内存,一个128G的固态不到200块,可以带来好几倍的使用流畅度。
文件
“文件就是字节序列,仅此而已”,前面有简单的描述过字节byte的一个情况。
网络通信
到目前为止的介绍中,我们都把计算机看为一个孤岛,实际上今天的我们片刻都离不开网络,网络也可以视为计算机的一个IO设备。
就hello而言,我们可以通过telnet应用在远程主机上运行hello程序,示意图:
在本书第11章中会介绍如何构建一个网络应用程序,搭建一个简单的web服务器。
1.7 一些重要概念
并发和并行
并发:指一个同时具有多个活动的系统;并行:指通过并发来使系统运行更快。
- 线程级并发:同进程下多个线程的并发
- 超线程:一个CPU核心同时处理多个控制流(线程或者进程),i7单核可以同时处理两个控制流。
- 多核:由于单核模拟并发是在不断的切换控制流,这个过程是需要消耗资源的,所以多核心在并发上的效率提升不只是倍数。(其实在多核和超线程提出来之前,大家对并发的热情并不高,单个控制流执行下的并发成本高)
- 指令级并发:在较低的抽象层面上,处理器同时处理多条指令。(第四章中会讲解流水线pipelining模型中指令的调度来详细解释)
- 单指令、多数据并行:在最低的层次上,处理器允许一条指令产生多个可以并行执行的操作,称为单指令、多数据并行,SIMD并行。
