荐书:OperatingSystems:ThreeEasyPieces
今天介绍⼀本书,书名叫做:《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-1.jpg
使⽤线程的主要好处⼀个是在多核系统上提⾼并⾏度;另⼀个好处是⾼效利⽤资源,⽐如说在 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]条件变量就是为了这种场景⽽⽣的,他提供了两种操作:
crash是什么意思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-2.jpg
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
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论