今天介绍一本书,书名叫做:《Operating Systems: three easy pieces》。如果日常工作中和底层打交道的话会遇到很多操作系统相关的问题,比如:

  • 为何系统会出现 load 值高 cpu 利用率却不高的情况?为何会有那么多僵尸进程?某些场景下如何快速创建进程的 snapshot ?如何高效利用 CPU Cache Line(利用 Cache Friendly 的数据结构)?如何避免 False Sharing ?  并发情况下如何避免死锁?zero-copy 为何高效?单纯的 context switch 都是 ms 级的,为何频繁的线程调度会导致性能低下?各种锁(互斥锁、自旋锁、读写锁)的适用场景等。

     


如何去理解以及解决这些问题就需要我们对操作系统的底层工作机制有一定的了解。这本书来自美国威斯康星大学课程的教材。为什么要介绍这本书呢?主要由于以下几个方面:

  • 阅读体验良好。书中以短句居多,配图丰富。我大概花了两周的业余时间读完的,整体下来阅读过程的非常的愉快。讨论问题由浅入深。就像我们有时候做题一样,先考虑简单的情况,然后再一点一点增加条件逐步解决。基本在本书中的论述都是先制定衡量标准,然后将情况一点一点复杂化来对比各种设计的优劣。给出了非常多的阅读材料。上一次有这种感觉还是看 《Designing Data-Intensive Applications》的时候。书籍开源:链接点我。作者写过一篇文章:Why Textbooks Should Be Free。非常让人尊敬,网站上给了 donate 链接,如果你阅读之后觉得还不错的话,欢迎 donate。

     

虽然是研究生的课程,但是书中讨论问题的深入浅出的方式,本科生读来应该也毫不费力。最后,不得不提的一点是,操作系统是一个非常庞大的系统工程,读完这本书并不能保证你对操作系统相关的问题都能找到答案。这更像是一个 guide,将我们引入操作系统的世界,而想要成为专家则需要持续不断的学习("read more" 在这本书出现了几十次之多)。除此之外,另外几本操作系统相关教程也值得推荐一下:《深入理解计算机系统》、《现代操作系统》。
开始之前再插句关于书名的题外话:《Three Easy Piece》,是为了致敬费曼的关于物理学的书籍:《Six Easy Pieces: Essentials Of Physics Explained By Its Most Brilliant Teacher》。用作者的话说,操作系统只有物理学一半难,那就叫 《Three Easy Pieces》好了。本书三个部分分别为:虚拟化(Virtualization)、并发(Concurrency)、持久化(Persistence)。其中虚拟化部分包括虚拟内存和 CPU 虚拟化以让进程以类似独占内存和 CPU 的方式在运行;并发部分主要讨论了并发编程,以及锁(lock)、条件变量(Condition Variables)、信号量(Semaphores);持久化只要讨论了文件系统,包括不同的存储介质(HDD, RAID, SSD)和不同文件系统的实现(vFS, FFS, LFS, NFS, AFS)。下面简单介绍一下各个部分,并简单提出来几个问题,大家不妨带着问题去看书。文章的偏差部分欢迎大家指出。欢迎交流。
1. 虚拟化

1.1 虚拟内存

虚拟内存,简而言之,就是操作系统为每个进程虚拟出来的内存空间,这样每个进程就可以在自己的虚拟内存空间中运行,并同时起到简单的隔离作用。相对虚拟内存,真实存在的是物理内存,也就是我们机器上面的内存条。那么问题来了。

  • 直接将进程运行到物理内存上有哪些问题?虚拟内存的地址空间大小是怎么决定的?虚拟内存和物理内存直接是如何进行映射的?为什么要进行分段和分页管理内存?空闲内存如何管理?页表是什么?...

     

关于虚拟内存,我之前写过一篇文章:malloc 背后的系统知识,所以这就不再赘述了。
1.2 CPU 虚拟化

在看书之前,不妨先思考几个 CPU 相关的问题:

  • 衡量 cpu 利用率有很多指标 cpu_user,cpu_system, cpu_nice,cpu_intr,这些指标之间的差别是什么?系统负载 load 和 cpu 之间是这样的关系?什么情况下会出现高负载的情况?关于 cpu 利用率和负载情况都可以通过 top 命令(或者在 /proc 下面相关文件)看到,load 一般是一分钟负载、五分钟负载、十五分钟负载一起出现。cpu 的层次结构是什么样的?L1, L2, L3 cache 分别是什么?以及缓存策略分别是什么样的?中断是什么鬼?context switch 过程中发生了什么?欢迎补充……

     

CPU 虚拟化这个部分讨论主要是 CPU 对进程或者线程不同的调度方式以及优劣。调度方式主要包括:FIFO(First In, First Out)、SJF(Shortest Job First)、STCF(Shortest Time-to-Completion First)、MLFQ(Multi-level Feed- back Queue)、lottery scheduling、Multiprocessor Scheduling(包括 SQMS 和 MQMS)。
这里的 CPU 虚拟化严格来说并不是 Docker 或者其他虚拟化技术中使用 unix 的内核特性 cgroup 进行 cpu 使用限制来达到“虚拟化”的目的。
理解了各种 CPU 调度策略的优劣,往往可以对操作系统的 CPU 运行有一个更直观的理解。
2. 并发

2.1 线程(thread)

线程,用书中话说是 abstraction for a single running process,用《深入理解计算机系统》中的话说是: 运行在进程上下文中的逻辑流。我一般比较喜欢将线程理解为一个 CPU 运行的实例。一个进程(process)可以由一个线程或者多个线程组成,线程是运行在 CPU 中的基本单元。如果进程由多个线程组成,那么就包含多个 PC (program counter),以及上下文切换的时候需要保存多组状态。单线程进程和多线程进程的内存布局如下图。
荐书:Operating Systems: Three Easy Pieces 
使用线程的主要好处一个是在多核系统上提高并行度;另一个好处是高效利用资源,比如说在 IO 请求的时候,调用另外一个线程去跑 CPU 。多线程编程的难点在于由于同一个进程内部多个线程共享数据的情况存在,导致共享数据的同步控制变得比较难。
不同语言针对线程提供了不同的 api,比如线程创建等。书中以 C 语言(有点废话,学习操作系统当然是 C 语言)为例写了很多代码,大家可以自己去看。
2.2 锁(lock)

锁是处理并发问题最简单直接的方法,既然数据共享有危险,那么我就每个时间点只让一个线程来操作数据好了。具体体现在代码上操作共享数据的代码片段就叫做临界区(critical section),在进入临界区的时候加锁(lock),离开临界区的时候解锁(unlock)。
那么如何来实现锁呢?要实现锁最简单的方式就是在 lock 期间独占 CPU,简单来说就是屏蔽时钟中断,但是这种简单粗暴的方式会导致非常多的问题。具体是哪些问题,可以先想一想再去看书。另外的实现方式是通过操作系统和硬件提供的同步原语(primitive)来实现。
评估锁(Evaluating Locks)。书中提出了三个 criteria 来评估锁,分别是:

  • mutual exclusion:互斥,也是最基本的功能,不让多个线程进入临界区fairness: 对于多个想要获得锁的线程是不是公平的,存在不存在某些线程被饿死的情况performance:锁的性能如何。 

     

书中剩下的部分通过上面的这三个评估标准分别对锁的不同实现方式进行了深入讨论。非常精彩。
如果有人对源码级别的锁的实现感兴趣的话可以看一下我之前写的基于 Golang 源码的锁的实现分析:

  • 当我们谈论锁,我们谈什么golang中的锁源码实现:Mutex读写锁以及golang的实现

     

2.3 条件变量(Condition Variables)

条件变量的存在是因为锁的状态太单一:只有 lock 和 unlock。而很多多线程场景下有一种情况是需要先判断某些条件是否满足,然后接着执行代码。比如我们用 pthread_create 创建出来一个子进程之后如何判断子进程有没有结束呢?

  1. [/code]条件变量就是为了这种场景而生的,他提供了两种操作:
  2. [list][b]wait[/b]: 将线程加入条件变量的等待队列。[b]signal[/b]: 向条件变量的等待队列中的线程发送信号,表明现在条件满足了,可以开始运行了。
  3. [/list]另外条件变量要和锁结合来使用,可以想一想为什么?值得一提的时候,书中用生产者/消费者模型来阐释信号量的使用,也是非常值得一读。
  4. [size=5]2.4 信号量(Semaphores)[/size]
  5. 信号量是另一种可以取代锁和信号量的线程同步原语,最先由 Edsger Dijkstra 提出,是的,没错,就是图论中的最短路算法:Dijkstra 算法的作者。信号量是具有非负整数值的全局变量,只有由两种特殊的操作来处理,这两种操作称为 P 和 V。
  6. [code]

复制代码

那么究竟如何使用信号量来实现锁和条件变量的作用呢?看完这本书你就有答案了。
2.5 其他

书中剩下部分还讨论了常见的并发 bug,以及 event-based 的并发编程。不再赘述了。
3. 持久化

持久化部分主要是文件系统相关。关于文件系统不同人应该会有不同的疑问。通常的问题主要有:断电保护怎么做?不同存储介质对存储性能会有怎么样的影响?如何衡量一个文件系统的 IO 性能?
3.1 IO 架构

文件简单介绍了一下物理上 IO 设备如何和其他部分连接的,如下图。
荐书:Operating Systems: Three Easy Pieces 
3.2 Hard Disk Drives

Hard Disk Drive 就是我们平时最多说的普通磁盘,物理结构为柱形,由磁盘头和盘面组成。系统层面的 IO 到达物理设备层面就是磁盘头移动到指定位置然后执行某些操作的过程。关于 HDD,书中主要讨论了磁盘寻址耗时以及顺序读写和随机读写的性能量化计算,理解了磁盘寻址就能理解为什么 Kafka 的随机写性能为什么好了。最后还讨论了 IO 调度算法。
关于磁盘性能相关的 metrics,如果对 linux 系统比较熟悉的话应该知道通过 iostat 命令可以查看。那么 iostat 的很多参数分别代表什么意思呢?为什么有 io 次数,还有一个 io_merge 次数?
遗憾的是,关于磁盘的物理介质,文中没有做过多介绍。
3.3 RAID

Redundant Disk Array,中文一般叫做冗余磁盘阵列。简单的说,RAID 是一种把多块独立的硬盘(物理硬盘)按不同的方式组合起来形成一个硬盘组(逻辑硬盘),从而提供比单个硬盘更高的存储性能和提供数据备份技术。书中通过三个 metrics: capacity, reliability, performance 对不同工作模式的 RAID 进行了评估和对比。通过本章可以学习的 RAID 的不同工作模式的优劣以及如何去定性和定量的分析问题。
3.4 文件系统实现

文件系统先讨论了一下操作系统提供的系统调用,比如创建新文件(open)、文件读写(open, write)相关操作等。书中提到了一个上古神器:strace,通过 strace 可以更形象的理解文件系统的底层系统调用具体的实现,比如删除文件 rm 命令。
[code][/code]看到上面的 unlink,你也许会想到文件系统里面的软链接、硬链接。但是这些又和 rm 有啥区别呢?这里不妨简单说一下,我们直接操作的可理解的文件名字比如上面的 foo,底层是和一个具体的 inode 节点关联的,inode 节点一般再具体关联到一个文件。由于一个 inode 可能被多个文件名字关联,所以删除文件的时候涉及到引用计数的问题。说到引用计数,不禁让人想到了垃圾回收算法了。还有软链接、硬链接对应底层文件操作又分别是什么呢?不妨自己动手看看。
在讨论问操作系统抽象出来的系统调用之后,书中讨论了两个具体的文件系统的实现,分别是 VSFS(the Very Simple File System)、 FFS (the Fast File System) 和 LFS(Log-structured File Systems)。vsfs 是 Unix 文件系统的简化版本,要点主要有:

  • inode 的设计(链表结构和多层结构)superblockFree space 管理(bitmap)...

     

FFS 对于 Unix 文件系统做了一些优化:提供目录结构的信息,以及各种磁盘访问的优化。目前应用有 BSD FFS。
LFS,日志文件系统,日志文件系统的初衷简单来说是因为磁盘的顺序 IO 和随机 IO 下的性能差别太大,所以 LFS 提出一种先将所有的 IO 记录到内存中一个叫 segment 的结构,segment 满了之后再将被顺序写到一个特定的磁盘位置。分布式日志文件系统 Bookkeeper 的设计思想和 LFS 很像,我觉得可能就是 LFS 的分布式版本。
除此之后,我觉得还有两种文件系统也很指的学习一下:

  • AUFS:联合文件系统,docker 中使用的就是 aufs。ZFS: 全称 Zettabyte File System,由 Sun 公司为 Solaris 开发的文件系统。由于 Z 作为英文字母表中的最后一个字母,ZFS 又被成为终极文件系统。

     

3.5 如何解决 crash-consistency problem ?

crash-consistency problem 的意思是由于系统异常 crash 导致数据在内存中还没有刷到磁盘上,从而影响到磁盘上的数据的一致性。处理这个问题的方法有 fsck、 journal(或者 wal)等。在 HDFS 中也有 fsck;而 WAL 对于做存储的同学则太熟悉不过了。如果是使用 Centos 系统(文件系统是 ext3 及以上)的同学,经常会遇到 /var/log/journal 这个目录会非常大,其实这个就是 WAL 日志。关于这些方式工作的细节,大家可以通过这本书籍了解个大概。
3.6 其他

书中文件系统剩下的部分还介绍了 SSD 和分布式文件系统(NFS 和 AFS),由于篇幅限制,这里就不再展开了。
4. 结语

最后,用书中的一句话来作为结语:

Keep working until your head hurts; you then know you’re headed in the right direction. 
And thus the real point of the educational process: to go forth, to study many new and fascinating topics, to learn, to mature, and most importantly, to find something that lights a fire for you.

5. 参考
 

  • OSTEP mainpageWhy Textbooks Should Be Free

相关文章:

  • 2021-06-29
  • 2021-07-24
  • 2021-07-23
  • 2021-04-09
  • 2021-05-16
  • 2021-06-26
  • 2021-08-21
  • 2021-08-24
猜你喜欢
  • 2021-05-15
  • 2022-01-04
  • 2021-09-19
  • 2021-09-29
  • 2022-12-23
  • 2021-09-24
  • 2022-12-23
相关资源
相似解决方案