本书首先以霍金提出的两个理论物理限制为引子,解释了多核并行计算兴起的原因,并从硬件的角度阐述并行编程的难题。接着,本书以常见的计数器为例,探讨其不同的实现方法及适用场景。在这些实现方法中,除了介绍常见的锁以外,本书还重点介绍了RCU的使用及其原理,以及实现RCU的基础:内存屏障。最后,本书还介绍了并行软件的验证,以及并行实时计算等内容。
本书适合于对并行编程有兴趣的大学生、研究生,以及需要对项目进行深度性能优化的软硬件工程师,特别值得一提的是,本书对操作系统内核工程师也很有价值。
Linux社区大名鼎鼎的黑客Paul编著!众多Linux社区“常委”级大咖力荐!20年传奇工匠程序员翻译!
谢宝友:1996年毕业于四川省税务学校税收专业,现供职于中兴微电子操作系统团队,对操作系统内核有较强的兴趣。专职于操作系统内核已经有8年时间。希望利用10年时间,成为一名真正的“内核菜鸟”。
主要工作是对Linux进行分析,解决遇到的标准内核故障,并向项目组提出应用程序优化措施。作为Linux ZTE平台的Maintainer,偶尔也向开源社区提交一些补丁。在中兴通讯操作系统产品部工作期间,作为技术总工参与的电信级嵌入式实时操作系统,获得了行业最高奖——“中国工业大奖”。 负责本书第11章至附录F,以及附录D的对应答案部分内容翻译。
鲁阳:2009年硕士毕业于成都电子科技大学,曾在中兴通讯操作系统部门和腾讯移动浏览器部门工作,后赴美留学,目前供职于图灵奖得主Michael Stonebraker创立的内存数据库公司VoltDB。8年时间从操作系统内核做到上层应用,目标是做一名真正的“全栈工程师”。负责本书第1至第10章,以及附录D的对应答案部分内容翻译。个人邮箱是 luyang.co@gmail.com。
推荐序
读着《深入理解并行编程》的样章,我的脑海里不断地浮现出9年前的一幕幕。我在网上寻找操作系统的志同道合者,看到一个税收专业中专毕业者的自荐信,其时他已具有10年的IT行业工作经验,从事过大量手机、通信行业软件研发工作,担任过项目总监研发管理工作,在电信应用开发方面已经做得比较成功。但他对操作系统有浓厚的兴趣、执着的追求,放弃了在高层应用软件方面的既有优势,专注于操作系统的研究。离职在家,利用半年时间开发出一个嵌入式操作系统模型,计划两年内研发一款自研操作系统。有感于他的执着和热爱,我向公司争取破格录取他。我认为做一个操作系统不难,但做生态难,做商业成功难,建议他深入学习开源Linux的技术,站到巨人肩膀上,再结合操作系统团队的商业模式探索,争取把操作系统做成功。于是,他如痴如醉地研究Linux内核,在一年时间里,每天晚上坚持花三个小时以上的时间钻研《深入理解Linux内核》这本书,还将自己的读书心得笔记共享到团队论坛上,并对开源内核进行注解,分享到开源论坛上。
2008年正是多核架构快速发展之时,操作系统的支持参差不齐,驱动、应用开发模式不成熟,既有单态单核单进程的业务应用如何进行重构和演进,方案设计、开发联调、故障排查、系统调优又会遇到很多复杂和棘手的问题,中兴通讯操作系统团队需要支撑公司所有产品、各种CPU架构、各种复杂业务场景,团队面临着前所未有的技术和进度压力。团队成员除了在研发一线通过不断实践进行被动积累和提升外,也加强了主动的理论知识提升,阅读《深入理解并行编程》就是其中之一。令我印象非常深刻的是,多核故障往往比较随机和复杂,难以复现和理解,但以谢宝友为代表的团队成员往往可以通过阅读业务、驱动、内核代码就定位到故障根源,整理出故障逻辑,我认为这与他们的系统理论水平提升是分不开的。非常欣慰的是,我们成功地解决了这个过渡时期涌现的诸如多核内存序相关故障,利用无锁并行编程优化了系统性能。时至今日,我们团队已经从30人发展到数百人,嵌入式操作系统已全面应用于公司所有产品,在全球稳定商用,并且扩展应用到电力、铁路、汽车等领域,2016年获得了第四届中国工业大奖。
另一方面,站在技术的角度来看,在计算机领域,并行编程的困难是众所周知的。
有4、5年编程经验的读者,可能或多或少遇到过并行编程的问题,最著名的问题可能就是死锁。读者需要掌握调试死锁问题的技巧,以及避免死锁问题的编程技术。
喜欢深入思考的读者,在理解并解决死锁问题之后,可能还会阅读并行编程方面的书籍,进一步接触到活锁、饥饿等更有趣的并行编程问题。中兴通讯操作系统团队的同事,就曾经在开源虚拟化软件中遇到过类似的问题:虚拟机容器在互斥锁的保护下,轮询系统状态并等待状态变化。这样的轮询操作造成了进程调度不及时,系统状态迟迟不能变化。这是一个典型的活锁问题。在多核系统越来越普及的今天,类似的活锁问题更容易出现。解决这类问题,需要经验丰富的工程师,借助多种调试工具,花费不少的时间。
但是,并行编程仅仅与锁相关吗?
在摩尔定律尚未失效时,并行编程确实主要与锁紧密相关。但是,我们看看霍金向IT工程师所提出的两个难题:
1.有限的光速;
2.物质的原子特性。
这两个难题最终会将CPU频率的理论上限限制在10GHz以内,不可避免地使摩尔定律失效。要继续提升硬件性能,需要借助于多核扩展。
要充分发挥多核系统的性能,必须提升并行软件的扩展性。也就是说,并行软件需要尽量减少锁冲突,避免由于锁竞争而引起性能急剧下降。这不是一件简单的事情!我们知道,Linux操作系统在接近20年的时候内,一直受到大内核锁的困扰。为了彻底抛弃大内核锁,开源社区近几年内做出了艰辛的努力,才实现了这个目标。即使如此,Linux内核仍然大量使用不同种类的锁,并且不可能完全放弃锁的使用。
也许你会说,在多核系统中,有一种简单的避免锁的方法,就是原子变量。在某些架构中,原子变量是由单条指令实现的,性能“想必”不差,使用方法也简单。曾经有一位具有十多年编程经验的工程师也表达过类似的观点。在此,有两个问题需要回答。
1.这样的原子操作指令,其性能真的不差?它的执行周期是否可能达到上千个时钟周期?
2.对于多个相互之间有逻辑关联的变量,原子操作是否满足要求?
实际上,多核系统中的并行软件,除了常见的锁之外,还需要使用冒险指针、RCU、内存屏障这样的重量级并行编程工具。这些编程工具都属于“无锁编程”的范畴。
即使在Linux内核开源社区工作10年以上的资深工程师,也不一定能真正灵活自如地使用RCU、内存屏障来进行并行编程。因此,真正了解并行编程的读者,难免在面对并行编程难题时,有一种“抚襟长叹息”的感觉。
然而,我们知道,有很多重要的应用依赖于并行——图形渲染、密码破解、图像扫描、物理与生物过程模拟等。有一个极端的例子,在证券交易所,为了避免长距离传输引起的通信延迟(理论上,光束绕地球一周需要大概130ms),需要将分析证券交易的计算机放到更接近证券交易的地方,并且压榨出计算机的所有性能。这样,才能保证达成有利的证券交易。可以毫不夸张地说,对软件性能有苛刻需求的软件工程师和大型软件开发企业,都需要真正掌握并行编程的艺术,特别是“无锁编程”的艺术。一旦真正掌握了,它就会为你带来意想不到的性能提升。曾经有一位著名企业的高级专家,在应用了本书所述的RCU后,软件性能提升了大约10倍。
本书正是这样一本深入讲解多核并行编程,特别是无锁编程的好书。
首先,本书作者Paul具有40年软件编程职业生涯,他大部分的工作都与并行编程相关。即使在领导IBM Linux中心时,他仍然坚持每天编程,是一名真正的“工匠”。同时,作者也是Linux开源社区RCU模块的领导者和维护者。认真阅读本书后,不得不钦佩于作者在并行编程方面的真知灼见和实践能力。例如作者亲自编写了一个软件用例,来考察CPU核之间原子操作和锁的性能,得出一个结论,原子操作和锁可能消耗超过1000个CPU时钟周期;作者也编写过另外一个关于全局变量的用例,其中一个CPU核递增操作一个全局变量,同时在不同的CPU核上观察所读到的全局变量值。这个用例向读者展示了多核系统令人惊奇的、反直觉的效果;作者对内存屏障的讲解,特别是内存屏障传递性的讲解,十分深入。这些深入的内容,难得一见,非大师不能为。
其次,这本书也得到Linux内核社区和应用软件专家的一致推荐。这些推荐者既包括Linux社区大名鼎鼎的Ingo Molnar、Rusty Russel、Greg Kroah-Hartman、Maged M.Micheal,也包括国内活跃于社区的庞训磊、Shawn Guo等开源贡献者,还包括Linaro开源组织的领导和资深工程师,以及在BAT工作多年的高级应用软件专家。
第三,这本书的内容比较全面。除了介绍常见的锁以外,还重点介绍了RCU的使用及其原理,以及实现RCU的基础:内存屏障。本书最后还介绍了并行软件的验证,以及并行实时计算等内容。实际上,其中每一部分都是并行编程的宝藏。由于篇幅和难度的原因,作者在当前版本中,将RCU部分作了大幅压缩。对RCU感兴趣的读者可以阅读早期原版著作。即使如此,本书对RCU的讲解也非常深入。对于并行软件的验证,作者提出了不少独特的观点,这些观点和作者多年的编程经验息息相关,与常见的理论著作相比,有一定的新意。形式验证部分,作者以实际的例子,一步一步讲述验证过程,很明显,作者亲自动手做过这种验证。并行实时计算部分,是作者新增的内容,别具一格,值得读者细读。内存屏障部分,是本书一个难点,借助于作者在这方面的功力,需要读者反复阅读,才能真正理解。
第四,这本书讲解得很深入。有些语句,需要读者反复琢磨、推敲,甚至需要多次通读本书才能领会作者的意思。也许,经典书籍的阅读方法均是如此。刚刚开始接触Linux内核的读者,不太会喜欢阅读《深入理解Linux内核》一书,觉得这本书不易理解。但是,如果你愿意花一年时间,将这本书反复阅读三遍,则会有一种别样的心情。本书也是如此,建议读者在初次阅读时,不要轻易放弃。本书实为并行编程方面不可多得的好书。举两个例子:第一,5.2.2节中有一句原文是“One way to provide per-thread variables is to allocate an array with one element perthread (presumably cache aligned and padded to avoid false sharing).”。译者将其翻译为“一种实现每线程变量的方法是分配一个数组,数组每个元素对应一个线程(假设已经对齐并且填充过了,这样可以防止共享出现“假共享”)”。第一次阅读本书,可能会不理解括号中那句话,有一种云里雾里的感觉。要真正理解这句话,需要读者仔细阅读本书后面关于MESI消息协议部分,参阅更多参考资料。要理解本句中“对齐”和“填充”两个词,也需要深厚的内核功底。第二,14.2.10.2节,“一个LOCK操作充当了一个单方面屏障的角色。它确保:对于系统中其他组件的角度来说,所有锁操作后面的内存操作看起来发生在锁操作之后。LOCK操作之前的内存操作可能发生在它完成之后。”这句话读起来也比较绕,难于理解,似乎也相互矛盾。实际上,读者需要琢磨“看起来”这个词,它表示其他核看到内存操作的顺序,并不代表内存操作的完成时机。
总之,如果你对并行编程或者操作系统内核有兴趣,或者需要对项目进行深度性能优化,我强烈推荐这本并行编程的经典好书!
中兴通讯操作系统产品部 钟卫东
“滥用的实时延迟”->“过长的实时延迟”