通过 Unix Domain Socket 传递文件描述符

Unix Domain Socket 是 Unix 系统下重要的本地进程间通信(IPC)机制之一,在 DDE、GNOME、KDE 等 Linux 桌面环境中常见的进程间通信方式 DBus 有一种实现方式就是基于 Unix Domain Socket 做的。虽然一直知道它的大名,也一直知道 Unix Domain Socket 可以用来传递文件描述符,但是碍于以前经验和眼界不足,加上没有深入去了解,完全不知道能传递文件描述符是多么强大的能力和必要性。 首先,我想着文件描述符不就是一个数字么,哪个 IPC 不能传递数字呢?完全没有思考到文件描述符是只在进程范围内有效,同样一个文件描述符放在不同的进程完全就不是一回事儿。这时候你肯定想,既然传递文件描述符这么麻烦,为啥非要传递文件描述符呢,使用文件名不也是一样的么?那么恭喜你,你也有更我一样在践行陶渊明老前辈看事物“不求甚解”的作风。传递文件描述符还是有它的必要性的,一方面,文件描述符代表的不只是一个文件,它还包含了文件打开的状态,比如 seek 的位置等,有点进程之于可执行程序的意思,拿到文件描述符也就拿到了这些动态的信息;另一方面,文件描述符不只对应于本地文件,它为了一众可读写对象提供了统一的读写接口,包含什么呢?本地文件、(匿名)管道、标准输入输出、甚至于 Socket 本身等……可以让你完全不关心文件描述符背后的实现是什么而方便实现自己的逻辑代码。 想通了以上道理,又有了以前似曾相识的感慨“古人诚不欺我”——前人留下的东西,必然有一定的合理性。这也是为什么我比较排斥看见一个软件不满意就立即重新造,尤其是能流传很广或者流程时间很长的软件,里面很多看起来不必要的东西,可能有其存在的合理性,最好的做法是尝试去改进,改进的过程了解其历史、学习其精髓,等自己胸有成竹的时候再下手重造不迟。额,跑题了…… 回到正题,之所以前段时间突然研究了一下 Socket 传递文件描述符的东西,是遇到这样一个需求:一个进程将自己的标准输入、标准输出和标准错误输出映射到另外一个进程相应的位置。带着对 Unix Domain Socket 的朦胧认识,写了一个简单的实现原型: // outlet.c #include <sys/socket.h> #include <sys/un.h> #include <string.h> #include <stdio.h> #include <errno.h> #include <unistd.h> #define SOCKET_NAME "/tmp/connection-uds-fd" int make_connection() { int fd; int connection_fd; int result; struct sockaddr_un sun; fd = socket(PF_UNIX, SOCK_STREAM, 0); sun.sun_family = AF_UNIX; strncpy(sun. Read On →

给 Doxygen 生成的文档中 Qt 的引用添加链接

额……这个标题怎么改读着都不怎么顺口,不过不用在意这些细节,问题不大 :) 上篇文章提到了 如何给DTK添加文档 ,给同事讲的时候有同事提出来最好能自动给对 Qt 的引用加上链接,我当时拍胸脯说文档里面 Qt 的类和方法随便引用,到时候一定让生成的文档可以链接到 Qt 的文档上。其实,到啥时候我也说不准……所以,还是研究了一下,翻了半天的 Stack Overflow 和 其他一些资料才搞清楚要怎么做到,这里记录一下。 如何在文档中添加外部文档的链接呢?官方文档 说得其实挺简单的: TAGFILES !最开始没有仔细看文档,理解歪了,以为 tags 文件是一个文档压缩包,添加到 TAGFILES 这项后面生成文档的时候自动下载,然后自动根据索引添加引用链接到文档中。所以,根据 doxygen generated documentation with auto-generated links to qt project 这篇文章,Doxyfile 配置文件写了: TAGFILES = qtcore.tags=http://doc.qt.io/qt-5/ \ qtgui.tags=http://doc.qt.io/qt-5/ \ qtwidgets.tags=http://doc.qt.io/qt-5/ \ qtxml.tags=http://doc.qt.io/qt-5/ \ qtnetwork.tags=http://doc.qt.io/qt-5/ 生成文档,怎么都不生效…… WTF ?! 继续 Google ,发现一个提交, 里面的 for i in core svg xmlpatterns; do curl -fsSLO "https://doc.qt.io/qt-5/qt$i.tags"; done; 引起了我的注意,恍然大悟: 原来这个 tags 文件就是索引文件,需要预先下载下来,之所以有 = 加上一个网址,是因为 tags 中只有类名到文档名的映射,并没有规定使用某种协议以及host。 Read On →

如何给 DTK 添加文档

背景 经过几年的摸爬滚打,DTK 作为构建 deepin 全家桶的基石,一直被说的主要有两个毛病: 内部人员觉得它不够稳; 外部人员觉得它无从下手; 第一个问题主要原因是 DTK 在最开始的时候缺失的东西太多,各种剧烈的变化同时进行,但是又没有比较好的版本控制和接口管理,所以经常是这个应用要用新版本,新版本删除了一个废弃的接口,用了旧接口的应用就崩了。由于最近 DTK 的变化日趋平稳,以及有了基本的版本控制,第一个问题渐渐淡出了人们的视野。 第二个问题外部人员无从下手的原因是 DTK 在开发过程中并没有留下接口文档,所以目前想使用 DTK,内部靠得是口口相传,外部靠的是阅读源码,完全的石器时代……甚至连社区的小伙伴都看不下去了,自己做了一个《Deepin开发指南》 。 是时候给DTK添加一下文档了! 工具选择 其实, DTK 之前是有写文档的,但是写得并不多,主要问题就是英文文档不好写。我也尝试写过,每个类、每个函数都只能憋出一句英文,这种文档跟没有是差不多的。所以,这里需要做一个艰难的决定:文档用中文来写,英文靠爱好者来翻译。 用什么文档工具呢? DTK 是基于 Qt 之上的开发库,最自然的想法是使用 QDoc,但是据使用 QDoc 搭建了 dde-file-manager 的 Github Pages 的 @BLumia 同学说 QDoc 对国际化的支持不是特别好,我们按道理是要支持中英双语文档的,遂放弃。除了 QDoc 以外,文档生成工具就是 Doxygen、还有一个KDE 项目自己的文档工具 KApiDox ,它们三者之间是什么关系呢?Doxygen 从 QDoc 上 fork 出来的,KApiDox 是基于 Doxygen 的封装……我没有怎么看 KApiDox ,所以就选择了 Doxygen 。 如何写 下面就是规则部分了。 1. 文档注释位置 为了保持头文件的清爽和干净,所有文档注释都要写在源文件中。 2. 多语言 按照 Doxygen 多语言处理的方式,需要在中文文档前添加 \~chinese 命令,在英文文档前添加 \~english 命令,这样设置 Doxyfile 中 OUTPUT_LANGUAGE=Chinese之后,就只会包含中文文档,而不会夹杂英文了,如: Read On →

关于 deepin-code-release 的一点新的思考

为什么要做 deepin-code-release 呢?其实我们在 deepin-code-release 的 README.md 中写得挺详细了: 从deepin项目启动开始,伴随着越来越多的deepin子项目产生,不同开源/商业定制版本的分割加剧,项目间的版本依赖问题、上游项目的patch丢失问题和系统环境无法恢复问题越来越严重,我们迫切需要一个统一的系统来帮助我们记录不同版本的ISO/软件仓库的状态,做各个子项目版本的版本控制和patch的版本控制,以达到快速恢复开发/系统环境,进而使针对不同系统版本的开发更加高效。 deepin release系统就是这样一个辅助系统,它的目标在于将它的每一个状态都对应到我们所有发布的ISO/软件仓库状态、每一个针对ISO/软件仓库的改动都能反映到这个仓库的改动上。 简而言之,deepin-code-release 是版本的版本控制管理工具,用来管理 deepin 众多“轮子”项目的版本号的,每次系统发布都需要进行一次记录,方便以后开发修问题的时候能找到当时的代码进行bug修复。 为了确保记录的准确,它的想法是这样的,每次(系统)发布前,深度的程序员各自检查一下自己负责的项目,如果需要跟随系统进行发布,就提交 CL 到 deepin-code-release 项目,这个 CL 会自动触发项目在对应仓库的打包,并且将打包结果反馈到 CL 的 Code Review 评分上,+1 分的就进行合并,反之闭门思过解决问题后再次提交,直到成功合并。 问题 以上需求还是听简单明确的,但是在目前的实现上却有一个问题:使用了 git-submodule 进行子项目的管理,再加上其他几个没有考虑的情况,这导致了好多小问题,以至于推行到现在还是阻碍重重: 1. 使用起来比较麻烦 deepin 的项目都是放在 gerrit 上的,它并没有一个统一的 git 用户(类似git@github.com中的git),所以同一个项目在不同开发人员的机器上的项目地址是不一样的,当成 submodule 放到 deepin-code-release 这个项目中以后,必须驱动项目地址中的用户名部分,再在 ~/.config/ssh 中添加一些额外的配置(参见 README.md )。 2. submodule 用起来比较别扭 最直接、也是抱怨最多的一点,是大家平常使用 git submodule 系列的命令并不多,所以对操作 git submodule 有不太习惯,而且就算是我比较熟练了,也总觉得 git submodule update 这个有点反直觉,感觉上应该是类似 git pull --recurse-submodules 的功能,但是实际上并不是这样。以至于很多人一不小心,把别个辛辛苦苦提交的更新又给退回去了。 3. 多了一个额外的仓库 原本的打算是每个开发都只需要克隆一个 deepin-code-release 项目,然后在其中 init 自己负责的项目并进行提交,需要关注多个项目的就按需 init 多个项目即可,但是实际推行的过程中还是发现大家并不是特别关注自己项目外的项目,deepin-code-release 里面就一个自己的项目初始化了,而且用着还挺别扭,还不如自己单独克隆一份,结果就每个开发上面既有一个自己项目的仓库,还要有一个包含了自己项目仓库的 deepin-code-release 项目,也是抱怨声颇多。 Read On →

15.7 研发心得——最好版本的台前幕后

15.7 终于如期地发布了……好吧,也就晚了2个工作日……毕竟这么好的版本,多等两天也可以的,对吧。需要多解释一句的是,我们之所以也没有按照往常的套路——拖到周五发,是因为周五刚好是情人节,毕竟程序员找个女朋友不容易,所以就拖了两天……这应该是个好借口吧 :P 发完系统第一件事情当然就是写《研发心得》咯,不知道从什么时候开始,《研发心得》成了发布标配,上午发系统,晚上就要《研发心得》,我只能微笑着说……嗯,挺好的!不过虽说是研发心得,但是别人看了我的心得以后都说像软文。我的想法是这样的:像软文就像软文嘛,让各位支持deepin的朋友看得舒舒服服的同时还能了解新功能,多好! 不过这次我准备换个风格,重点是真实。所以,这次的心得我准备说说 15.7 这个版本的台前幕后。 台前&幕后 “这是我用过的最好的deepin版本”。 我预测应该有很多用户会跟我有同样的想法,毕竟,对于发了这么多版本的我来说,15.7 是第一个版本让我有重新完整安装一次这样冲动的版本。三年多以来,每逢系统发布前,我都会挑一个良辰吉日加上一个好时间,这个时间其实主要取决于版本发布的延期情况而定,隆重地安装一下即将发布的新版本的ISO,说是为了测试一下其实是骗人的,毕竟我也测试不出什么bug来……主要是装一下心里面爽啊,你们要明天才能下载,我今天就用上了,呵呵呵…… 虽说每次的ISO我都安装过,但是我从来没有舍得把我现在在用的系统格式化掉重新安装,因为这个老家伙从deepin切换仓库到Debian我重新安装了一次系统开始就一直陪伴着我,没有必要的话,我是想一直让它继续往上升级的。直到这次15.7发布,我感觉它又是一次值得纪念的发布才想着来一次完整重装,不过冲动归冲动,我还是按捺住了心中那团火——还是没有重新安装,因为思来想去,升级上来的也是一样的嘛 :) 这次升级可以分为两大部分,一部分是DDE的优化,另一部分是仓库同步Debian上游。从一个用户的角度出发,我觉得吸引我的地方,或者说值得用户期待的地方主要有这么几个: 更快的启动速度 这个主要得益于“林姐”亲自操刀的文件预热技术warm-sched,它主要的原理是这样的,大家知道操作系统在启动的过程中,会进行大量的IO操作,这个在CPU性能不是问题的平台上就是启动慢的罪魁祸首了,大家还知道Linux内核其实本身是有文件缓存机制的,被缓存的文件访问会非常非常快,所以如果我们在系统加载的适当时候能进行一定的预热,那么系统加载的时间当然就会大大缩短了。 不过这个会导致的一个问题就是,用户登录的速度变得飞快(从原来的10s+变成3s),但是系统好像在deepin水波纹的时间似乎更长了,其实不用”似乎“,真的会这样,文件预热技术不是神话,它没办法吃掉那部分加载文件的时间,它能做到的只是在合适的时间点预热下一个阶段需要的文件。 ”这不是拆东墙补西墙么?“你可能会这么问,但是我要告诉你还真不是,如果没有预热,系统加载文件的过程是”乱序“的,这里的乱序是指文件存放不一定是顺序存放在磁道上的,乱序的文件访问会导致磁盘访问的过程中转来转去的,效率低下;相反,warm-sched不仅只是对文件预热,加载的过程可以保证磁盘的顺序访问,文件多了的话,这部分优化还是比较可观的…… 我记得以前有位同事总是喜欢开玩笑说”苍蝇也是肉啊“,当时在饭桌上没少让人倒胃口,但是这句话用在性能优化、以及上面这个例子上算是再合适不过了。 更少的资源占用 首先是电量,如果你是笔记本的话,应该能感受到笔记本续航的提升,deepin测试人员实测的数据是以前3小时续航能力的笔记本,待机会多出30分钟+的时间,也就是提升差不多有17%,跟使用powerstat这个工具测试的效果基本相差不大。那是不是原来待机4个小时的笔记本,也只会多出30分钟的续航时间呢?并不是的,因为这是一个功耗比例上的变化,这次优化以后理论上可以增加的待机时间就是 原来的待机时间x17% 了,如果你的电池健康程度够的话,应该能有更大的优化效果。 不过,这些都是在使用电池的情况下的数据,因为默认情况下deepin系统只有在拔掉电源的情况下才会自动进入节能模式,如果你想在连接电源的情况下也开启节能模式,我是说假如你真这么环保的话,你可以在控制中心的电源模块手动打开节能模式;相反,如果你像我一样不在乎耗电,只追求卓越的性能,你也可以随时把节能模式关掉,大丈夫就是这么帅,不怕编译的时候机器烫手;总之,由着你的性子来! 另外一个能明显感受到的变化是开机内存的减少,这里所说的开机内存是指在没有任何开机启动项的情况下,DDE达到稳定状态的一个内存值,由原来的 1.1G 占用降低到 830M 左右,在使用独显的情况下,内存占用会更低,简直令人发指!好处是什么呢?更低的内存占用意味着DDE变得更加轻量,应用程序可以占用更多的系统资源,例如你是chrome这个内存占用大户的用户,15.7 可以让你在系统不变卡的情况下多开十来个标签页呢,是不是很开心? 不过做这些优化的过程中最让我感到意外的是DDE对电量消耗的“助攻”竟然没有大家预想的那么多,进程抽风性地CPU占用高、间歇性地进程状态切换等居然连硬件功耗的零头都占不到,不过该改的地方一个都不少,所以,DDE 的CPU占用高的问题在15.7中也可以不用再见了。 更新的软件和驱动 这个就不用多说了,搭载了Debian上游最新的一波升级,你想要的“更新的软件”、“更新的驱动”统统都给你,就是这么大方!而且,这次你收到的不仅仅是一次更新带来的快感,更是deepin更新会变得更加快、准、狠的承诺。在15.7的需求中我们争论了好多次,一方说已经有太多用户吐槽我们软件不够新、基础库不够新,用户需要新的软件;另一方说更新太快容易导致用户环境不稳定,我们毕竟是一个面向普通用户的发行版;争论不休,都可以开一个深度辩论赛了……最后终于达成一致,我们希望能更快速的将软件更新推送给用户,不再一次性积累大批量的更新,同时保证一定的节奏,确保快速更新的同时不会挂挂挂。 另外,值得一提的是论坛用户和老板不停的要求、催促和威逼利诱下,我们终于——终于把PRIME方案引入到了深度显卡驱动管理工具中,现在如果你是双显卡(N+i)用户,你只要小手一点,就可以方便地在几个预设方案中轻松切换了……嗯,不辛苦,用户和老板高兴就好 :) 一点感悟 上面说了这么一大堆,其实当时为了准备这些优化的内容的时候,心里面还是比较慌的,因为系统优化这事儿做起来远没有听起来那么爽,尤其是在没有既定方法的情况下要定一个优化目标出来,还是相当无助的,而且一旦你陷入 盲目—烦躁—焦虑 的怪圈中,就很难再痛快地出来,这就是这次15.7所面临的第一个挣扎。 我跟几个程序员都交流过,程序员大多都有比较强的焦虑感,再加上程序员多有洁癖,动不动这不清真那不科学,更容易掉进上面这个怪圈,在这个怪圈里面,你要么有一天实在承受不了了,咔嚓——破罐子破摔,从此成为浩瀚宇宙中的一粒尘埃;要么有一天你想通了:“还想个毛,就是干”,然后挑一件事后跟别人扯淡都羞于提起的小事,慢悠悠地开始做,做完了你会发现你的心理负担变轻了不少,然后再挑一件小事,接着干……越干目标越近,焦虑感也越来越少……最终,完成发版大业。 所以,当时我们不知道到底优化目标定成什么样子,那就捡最笨的方法,跟其他几个操作系统:Ubuntu、Win7、Win10做一个横向对比,看看其他几家的情况,至少做到在单一侧面都不是最差的吧?(做到任一侧面都是最好的,就留作下次优化的目标吧,毕竟大家都等着咱们发版呢。)定完了就朝着目标开始研究怎么使用优化的工具:perf、valgrind、heaptrack、google-perftools等,大家都不怎么有经验?那就学一个培训一个,各自有任务去练习……等优化的patch都集成完了,对比下优化目标,不够或者还不满意就再来……所有15.7可见的优化都是这么一点点的“苍蝇肉”拼出来的。 刚好最近在Twitter上看到的一段话,可以把我上面想说的非常明白的表达了出来,把这段“心得”分享给大家: 真的,诸位,有什么难事千万别耗着,别等着,那只会让人在无尽的焦虑中煎熬,你就先大吼一句:“去你妈的。”然后两眼血丝地去推进,去做事,做着做着就有出路了。

致王勇

以下内容是我在知乎对“如何看待Deepin操作系统创始人王勇离职”的回答。 这个月,从王勇改微博后缀,到删除deepin字样相关的代码,再到一则朋友圈,最后论坛的一篇《感谢亲爱的你们,大家继续加油》,有好几个朋友过来问、或者代朋友问王勇的情况,我虽然已经知道他的打算,但是不管朋友远近我都是说他在家休息,因为我知道他离开的消息有多大的影响。现在大家都知道了,也都震惊了,有人觉得伤感、失落,包括我自己都一样,这没什么,但是很多无良的媒体大肆渲染,最后以讹传讹,我甚至感觉我明天就不用去上班了…… 压抑了好久,有必要在这里说说。 王勇离开深度,我觉得对我最大的影响是 少了一个导师,没有了精神寄托。 跟很多deepin的粉丝一样,我还在上大学的时候就知道了deepin,随之就对deepin和ManateeLazycat(王勇的网名)产生了巨大的喜爱,成了王勇的脑残粉;幸运的是我居然凭着自学(当然也得感谢当时猴哥和xiangzhai在网上对我的指导),拿到了去深度工作的机会。而我刚进深度的第一个目标就是“王勇,我要在技术上超越你”,被王勇知道后以至于到现在还被他拿出来刺激我 :O 深度当时的技术氛围非常浓厚,因为一个屋子,清一色的程序猿。所以在深度的第一年里,是我技术成长接触面最广的一年,学了很多语言,接触了很多技术,对我影响最大的可能就是跟着王勇和猴哥学了Emacs,另外也自学了Elisp和Common Lisp,虽然当时心高气傲搞了一个hualet-emacs,实际上用王勇的话说“就是对着deepin-emacs抄嘛 :P”,事实上也确实如此,那时候也是第一次看到王勇在UI细节打磨上执着,他把emacs的每个插件都折腾成自己的风格,所有插件放在一起你都不觉得他们是分开的,自成一套体系,对我影响很大。 后来的一年多时间,是我在深度的沉寂期。默默无闻,但是我看到了王勇做的一些事情,其中就包括跟其他社区的人吵架,或者叫“互喷”吧。当时有点不能理解,社区都好好相处,不是挺好么,还容易积聚力量,后来慢慢懂了。前段时间看《走向共和》和《曾国藩的正面与侧面》,我觉得王勇很像晚清那些想叫醒中国人的斗士、英雄,看到开源社区很多浮在空中的“所谓的大牛”,说的话都对,却很少或者说从来不做实事,而愤怒地发出怒吼。 “有了求实一念,人才会从道德制高点上下来,脚踏实地,不激不随。”我觉得王勇能成为我心目中的领袖,是他很早就知道“求实”,而不是站在道德的制高点上喷这个、吹那个。 15年的时候,我迎来了自己在深度的机会:掌管桌面组和系统发布。但实际上当时我申请了这个职位以后没过两周我就后悔了,深度的用户都知道,15版本是近年来最大的一次版本变迁,首先是仓库从ubuntu切换到了debian;另外是桌面环境全换Qt重写。这对我来说不仅是巨大的工作量,关键是我当时还主要是做应用,压根儿不知道这个桌面是怎么一步一步被做出来的,还要面临的一个问题是为了赶商业机会,我们的重写必须在那3-6个月完成,那时候真的是异常痛苦。 也就是这个时候,我和王勇的关系才从以前一种盲目崇拜与被崇拜,变成了学生和导师的关系。记得非常清楚,无数个加班的夜晚,我跟兄弟们加完班以后,还得找王勇“倒垃圾”,这个事情有多难、有多坚持不下去、这方面有问题怎么办、那方面又问题怎么办……有时候王勇也不见得有什么好的办法,但是有一个人一直在支撑你走下去的感觉,会产生奇迹。这种经历一生也不会有太多。 那半年是我蜕变的半年,王勇在其中起了太多的作用。 所以要说王勇身上背负的不仅仅是自己的压力,还要背负兄弟姐妹们身上的压力,这个压力我自忖是完全承受不来的。这也是为什么当时猜出来王勇要离职的时候,我对他说“从公司员工的身份上我对你这个决定是接受不了的,但是从朋友的角度我觉得你做这个决定我很佩服”。他确实承受了太多事情,如果都这么决定了,我们能做的就是尽量不要给他压力。 王勇临离开之前做的最后一些事情,就是研发各个团队的负责人挨个谈了一遍,跟我说的内容很少涉及自己怎么样,更多的是帮我捋清方向。 就这样。 至于说深度会不会因此倒闭了,或者说deepin项目会不会凉了,我觉得至少短时间内不会。 一方面,王勇作为武汉研发总部的负责人,长期以来从来也没有忘记去培养人,培训和言传身教,影响了一部分有想法的同事,虽说我们现在没有能力像他一样作为个人去领导一方,但是目前在做的事情是可以承担起来的;另一方面,就像 张成前辈 的回答里面说的一样,深度前期能在完全没有盈利目标的情况下开始做,到现在依然坚持在为社区做贡献,王勇的热情是一部分、刘总(deepin)对社区的看重是另外一部分。现在王勇出走了,但是我没有看到刘总说社区就此打住、不再做了,实际上公司上下稍微有点Linux背景的人都没有质疑过社区这一条方向。所以deepin项目不会就此凉了,相反我们可能会在做国产化的同时去探索更多的面向社区和普通消费者的方向。 最后,致勇哥一声深深的谢谢!

使用 Perf 优化程序性能

因为原定deepin 15.7会做优化(能耗、资源占用等)方面的工作,所以在此之前,想提升一下团队整体的性能剖析、优化方面的水平,也就有了这次(周五)的培训。本来想用视频记录的,一个是自己回头能看看,反思和改进一下演说能力;另一个是以后有新人入职,我也就不用挨个再讲一遍,天不遂人愿,录制视频的软件半道挂了( 所以只能这里尽量回忆当时想表述的内容,这里文字稿记录了。 下面就是培训内容: 好,人到齐了我们就开始了。今天要培训或者说要分享的内容是程序性能优化方面的内容,其实我们对性能优化不陌生:最开始接触龙芯和申威平台,系统组件间调用不是异步导致系统卡得无法使用;好不容易交了一个版本,控制中心各个模块还是因为切换卡顿、使用体验不好等做改进;前段时间为2G内存机器做优化等等。现在我们因为程序设计问题导致的性能问题比以前少了很多,一方面我们还要对这些部分持续改进,另一方面我们需要掌握一些性能剖析工具的使用,来帮助我们改进一些更细粒度的程序性能问题,今天我们主要集中在程序执行性能、也就是CPU占用这部分的性能优化。 今天培训的内容比较基础,还是老规矩,我做敲门砖,其余的还需要各位发挥。 刚才说了我们以前做了几次优化,我们再来回顾一下为什么要做性能优化。第一点肯定是占用过多的系统资源,不论是CPU、内存还是IO,如果系统组件的资源占用比较高,那系统资源有限、程序间的资源又是会互相竞争的,必定导致用户使用的应用程序的可用资源减少,在一些配置普通的机器上,可能就会导致第二个问题,也就是系统使用不够流畅,当然导致系统使用不流畅也分两部分原因,一个就是我们刚才说的,系统组件占用资源过多,另外一个也是刚才提过的程序架构设计有问题,导致卡界面等等; 第三个是性能问题在低配置的机器上会被无限放大,就像我们在最初的龙芯和申威平台一样,在x86上可能感受不到或者感受不太强烈的问题,在龙芯和申威上就会严重到无法使用(当然,现在的龙芯和申威平台使用已经没有问题了);最后一个也是跟我们15.7想要解决的能耗问题息息相关,就是能耗高、发热大。主要是我们有些系统组件“不老实”,很多情况下随机“抽风”。 这个问题我们论坛用户提到的也不在少数: 当然了,大家可能也知道功耗主要跟系统配置关系很大,例如CPU开不开睿频、有没有设置节能模式,各个网卡、声卡驱动有没有设置节能模式等等,但是我觉得至少我们的程序至少不能成为能耗高的帮凶吧。 现在的情况是我们的程序不仅是帮凶,而且帮得很厉害,一会儿会给大家看一下。 刚才说了我们做了很多次性能优化,可能很多用户期待说我们能一次解决所有性能问题,但是实际上性能优化是持久战,它是一个需要持续做下去的事情。 另外主要还是因为性能优化很难,第一点主要就是性能瓶颈的定位很难,比如最开始我们系统登录的过程非常慢,所有程序都是并行启动,看着所有的系统资源占用都很高,换成串行启动以后,依然如此,这个定位当时就比较麻烦; 第二点是有时候优化的效果并不明显,虽然说我们看到有时候系统的资源占用挺高,但是可能分到每个程序中就不是特别多,做性能优化得慢慢抠,可能从单个程序来看效果并不明显;第三点是有时候做性能优化的技术和经验要求比较高,一方面是性能剖析工具的使用、对程序运行的本质要熟悉,另一方面就是要对被优化程序的代码要非常了解,不然就会导致我们说的第四个问题:优化可能影响正常功能。比如我上次给Dock提交了一个性能优化的提交,虽然有让sbw同学审核代码,但是最后还是导致了一个功能性bug。 虽说性能优化很难,但是……不积跬步无以至千里。所有复杂的系统都不是一天两天做好的,比如我们的桌面环境,到现在经过了四个版本,基本上就是四代人的心血堆积,才做成现在这个样子。 我们每个人都能做好自己那部分的话,众人拾材火焰高,解决系统性能问题我觉得没有那么麻烦,这其实也是为什么我这么迫切要组织这次培训/分享的原因,还是希望我做好敲门砖,像去年做高分屏支持一样,虽然是我开的头,但是其实最后基本上每个都比我做得好,最后我们做高分屏的效果也非常不错。 刚才说了性能优化很难,大家也不要害怕,其实性能优化也没有想象的那么复杂。 第一个是因为现在优秀的工具很多,比如右边这个非常出名的图,来自 Brendan Gregg 大神的博客。把系统性能优化每个部分对应的工具都清楚的标出来了。 第二个说实话是因为deepin的性能问题还比较多,比较容易发现,相应的上层的性能问题也多,所以说性能优化简单也是因为我们这次主要将注意力集中在 Applications & System Libaraies & System Call Interface这三部分即可。这部分的工具可以看到其实就是 perf、ltrace、strace这些,最多加上 bpfcc 、eBPF等。 说deepin性能问题还比较多是大家可以看到,静置状态下,我什么都不操作,几个组件就又可能时不时抽风一下(占用CPU部分)。 看到了有性能问题,但是我们总得有办法把这些性能问题优化掉吧。 性能优化一般分三个部分,第一部分就是查找程序性能热点,实际上一般我需要先定位性能瓶颈到底是在哪部分,是CPU?是内存还是IO等。不过我们这里目标很明确就是优化CPU占用,所以可以直接朝着有性能问题的程序去。 第二步是热点诊断和修复,找到了性能热点,我们肯定需要修复吧,看看导致这个热点的原因,正常进行修复即可。 第三步是回归测试,就像我们平常修bug一样,修了bug要测试,做了性能优化也一样,我们需要做回归测试、对比一下前后的性能数据。 我们先来看第一步,利用perf查找程序性能热点,这也是我们今天要说得重点。 perf这个工具最开始是作为Performance Counter的接口引入内核的,但是慢慢引入了一堆调试接口如 tracepoint、kprobe、uprobe等等,也就慢慢发展成为Linux几乎最好用的性能调试工具了。 我们平常使用的perf命令是用户态的工具,前面说得那些都是内核里面做的事情,内核态工具主要是对搜集到的数据和事件进行处理和统计。因为这个工具跟内核版本关系比较紧密,所以安装perf的时候需要注意跟内核版本对应的问题。当然,调试的时候装dbgsym包是必须的。 使用perf查找程序性能热点,一般主要用到三个子命令,第一个就是 perf top,这个很好理解,top命令大家都用过,那个top主要是针对进程或者线程级别的资源占用进行统计和展示,perf top可以理解为函数级别的top,可以动态展示系统目前占用资源最高的函数分布情况,它后面的 -g 是启用函数栈,-p 后面加上进程 PID就可以针对单个进程进行追踪,这个跟top命令一致,如果不加 -p 就是默认系统级的统计和展示。 第二个命令是 perf record,它跟第三个命令 perf report 是搭配使用的,record 用来记录一段时间内的程序执行情况,然后用 report 来进行展示。 -g 参数的意义跟 perf top一致,启用了函数栈以后,我们可以使用 –call-graph 来制定使用哪种方式来获取函数栈: fp 方式,是使用传统的方式 frame pointer来获取堆栈,这个我在之前的文章中也介绍过; Read On →

自制 Profiler 第二部分——调用栈回溯

书接上篇,我们现在已经能在其他程序中执行我们自己的代码,并且也做到了以固定的频率去执行采样代码(我们的printf),但是如何采样还是一个问题,这篇文章会就这这个问题继续探讨接下去我们面临的挑战——调用栈回溯。 为什么要获取函数调用栈?一方面是因为profiler除了要分析程序存在的性能问题,即函数执行热点以外,还需要帮助我们可怜的程序员找到问题的原因,这时候能提供问题函数的堆栈信息就非常必要了;另一方面,我们上一篇文章其实说了,是为了通过堆栈信息尽量还原程序的执行过程:试想一个程序执行的过程是 main->funca->funcb->funcc,我们第一次采样 main->funca,第二次采样 main->funca->funcb->funcc,假如我们没有堆栈信息,我们只会统计一次 funca 和一次funcc,但是这并不能反应事实,相反,我们有堆栈信息的话,就会把 funca 、funcb 和 funcc 各计数一次,更能反应实际的执行过程。 概念 函数调用栈(Call Stack)和相应的栈帧(Stack Frame)我们其实都不陌生:在使用 gdb 调试程序的时候,bt(backtrace)命令打印出来的就是函数调用栈;而函数调用栈列表中的每一项则代表一个栈帧,我们执行 frame 命令跳转到某一个栈帧,其实就是一次回溯的过程。 想要在内存中解析出我们想要的函数调用栈,首先我们需要知道的就是一个程序的stack 段里面各个栈帧是如何布局的,要搞清楚这个,我们还需要了解一个概念叫:调用约定(Calling Convention),调用约定主要约定了(好绕): 函数的参数是如何传递的,是全都放到寄存器,还是全都放在 stack 段,还是混用两者; 函数的参数是按什么顺序放置到内存中的; 函数中的本地变量是如何分配的; 子函数调用是如何返回的; 子函数的栈帧是如何清理的; 等等 所以,调用约定基本上决定了函数调用中每个栈帧的产生、压栈、出栈对内存布局的影响,而这个约定是因架构和平台而异的。我们这里只关注x86 平台下的 cdecl 约定。 在这个约定下,假如我们有一个函数 DrawSqure 调用了 DrawLine (例子来自Wikipedia),那么程序内存布局中的 stack 段就应该是类似下图所示: 每个函数调用即创建一个栈帧,每个栈帧一次压入 stack 中。 其中,Stack Pointer(esp) 永远指向栈顶, Frame Pointer(ebp) 指向当前栈帧的中一个固定的地方(基地址);函数参数以从右往左的顺序依次压栈,然后是压入Return Address ,它是当前函数(或者栈帧)执行完成后,程序要继续执行的指令地址, 同时压入父函数的栈帧基地址(Saved EBP),它是当前函数执行完成以后,Stack Pointer 和 Frame Pointer 将会指向的地方,基于这个地址,程序指令可以方便地访问函数本地变量(ebp负向偏移)和函数参数(ebp正向偏移)。 结合上面两张图,其实可以看出,每个栈帧其实都保存了上一个栈帧的基地址,因此所有的栈帧最终组成了一个链表,这也就是我们能拿到函数堆栈的理论基础了。 (注:上面只是粗略的讲解,参考链接 [1] 非常详细的描述了函数调用的过程中栈帧、stack 段和esp、ebp寄存器的变化,如果感兴趣,可以详细了解一下。) 参考方案 看完上面一大串概念以后,我们发现如果我们要按照函数约定的方式去获取函数调用堆栈,可以,但是太过蛋疼,而且不跨平台,很难受。 所以秉承不要重复造轮子的优良传统,我们发现有几个方式可以简单地获取到函数调用栈: Read On →

自制 Profiler 第一部分

​ 最近对性能剖析的技术颇感兴趣。好不容易来了三分钟热度,自然不能浪费,因此在余热消失之前研究并实践了其中一部分细节,对于其中一些知识点,个人感觉对于自身编程能力提升还是比较有益的,因此在这里写出来,抛砖引玉。 Linux profiler简介 ​ 性能剖析的工具,其实在Linux平台还是挺多的,比如小巧实用的strace、ltrace、latrace,大名鼎鼎的google-perftools、gprof、valgrind,以及瑞士军刀型的linux-perf等等,它们主要分为三个阵营,一个是针对程序执行的性能进行剖析,对程序执行的热点进行分析,如*trace、gprof这些工具;另一个是针对程序运行过程中内存的使用进行剖析,方便针对性地做内存优化,如valgrind;最后一部分就是“脚踏两条船”的,两个我都做,比如google-perftools、linux-perf这些,我们这个系列主要集中在“针对程序执行的性能进行剖析”方面。 Profiler的本质 ​ 要自制Profiler,首先要知道Profiler的本质是什么。简单来说,Profiler的本质其实就是在程序执行的过程中对程序正在“做什么”进行搜集和统计,如何搜集呢?无非两种: Instrumentaion - 程序主动Profiler告诉它在做什么; Sampling - Profiler自己间断性地去看程序在做什么; 前者的实现主要依靠程序运行时提供的某些钩子机制、代码插桩等方式,例如gprof主要是依靠 gcc 在编译程序的时候”夹带私货“来达到程序运行的时候主动提供给gprof采样样本,来达到事后分析的目的[1]。这种方式相对来说虽然比较可靠、准确,但是对于无法控制编译条件的程序就比较无可奈何了。 后者的实现则主要是定期对程序当前执行的指令和对程序执行的函数堆栈进行回溯(unwind)来尽量还原程序执行的过程,例如google-perftools就是采用这种方式,这种情况下,采样的周期就显得尤为重要。 ​ 对比以上两种方式,我们果断采用第二种。 ​ PS: 这里需要说明一下的是,利用ptrace系统调用完成工作的strace和ltrace,虽然不依赖编译器夹带私货,但是相当于依赖了内核的“钩子”,不属于sampling的范围;同样,latrace则是依赖了ld的LD_AUDIT“钩子”,也不属于sampling的范围。 Profiler启动 ​ 那么问题又来了,ptrace没得用,我们怎么去获取被剖析程序的执行状态呢?总不至于profiler要搞成root权限的吧?答案是:不,内核管天管地,总管不到程序自己偷看自己的数据吧?我们想办法把我们的代码塞到被剖析程序中去就可以了! ​ 刚才提到了ld的LD_AUDIT,这次就轮到它的兄弟——大名鼎鼎的LD_PRELOAD——登场了。想法是这样的,我们的profiler其实只需要在程序开始的时候执行一个定时器,以后每次定时器执行的时候去抓取我们的样本就OK了,所以我们完全可以把自己伪装成一个人畜无害的动态库,等到别个程序有意无意加载到我们,哼哼……事实上,很多profiler都是采取类似的策略,比如google-perftools,再比如heaptrack等等。 编码实战 ​ 废话少说,放码过来! ​ 我们在QtCreator中创建一个动态库项目 simple-profiler,主类 SimpleProfiler。首先,我们需要设置好我们的定时器: void SimpleProfiler::enableProfile() { int ret = setitimer(ITIMER_PROF, &m_tick, nullptr); if ( ret != 0) { fprintf(stderr, "failed to enable profiler: %s", strerror(errno)); } } 根据 setitimer 的man文档,计时器主要有三种类型: ITIMER_REAL 这个计时器是根据墙上时间进行倒计时,最终触发 SIGALRM 信号; ITIMER_VIRTUAL 这个计时器是根据进程花费在用户空间的 CPU 时间进行倒计时的,最终触发 SIGVTALRM 信号; ITIMER_PROF 这个计时器是根据进程话费在用户空间和内核空间的 CPU 时间进行倒计时的,最终触发 SIGPROF 信号; 个人认为,假如都是用在 profiler 上的话,第一种计时器用来查找程序执行慢(某个函数有IO等情况)的瓶颈比较有效果;后两种计时器用来查找程序 CPU 占用瓶颈比较有效果。不过使用第一种计时器的话,对被追踪程序的执行影响相较于后两者就比较大了,所以这里采用第三种计时器。 Read On →

初探Linux内核态——通过proc文件系统作快速问题定位

文章翻译自 Peeking into Linux kernel-land using /proc filesystem for quick’n’dirty troubleshooting 这篇博客的内容完全是关于现代Linux内核的。换句话说,指的是与RHEL6一样使用的2.6.3x系列内核,而不是古老的RHEL5所使用的2.6.18内核(都什么鬼了?!),虽然大部分企业都还在使用RHEL5。另外,这篇文章也不会涉及内核调试器或者SystemTap脚本之类的东西,完全是最最简单地在有用的proc文件系统节点上执行“cat /proc/PID/xyz”这样的命令。 定位一个程序“运行缓慢”的问题 下面要举的这个例子是这样的:一个DBA反映说他们的find命令一直运行缓慢,半天都没有什么输出,他们想知道这是为什么。听到这个问题的时候我就大概有直觉造成这个问题的原因,但是他们还是想知道怎么系统地追踪这类问题,并找到解决方案。刚好出问题的现场还在…… 还好,系统是运行在OEL6上的,内核比较新,确切地说是2.6.39 UEK2。 首先,让我们看看find进程是否还在: [root@oel6 ~]# ps -ef | grep find root 27288 27245 4 11:57 pts/0 00:00:01 find . -type f root 27334 27315 0 11:57 pts/1 00:00:00 grep find 跑的好好的,PID是27288(请记好这个将会伴随整篇博客的数字)。 那么,我们就从最基础的开始分析它的瓶颈:如果它不是被什么操作卡住了(例如从cache中加载它所需要的内容),它应该是100%的CPU占用率;如果它的瓶颈在IO或者资源竞争,那么它应该是很低的CPU占用率,或者是%0。 我们先看下top: [root@oel6 ~]# top -cbp 27288 top - 11:58:15 up 7 days, 3:38, 2 users, load average: 1.21, 0.65, 0.47 Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie Cpu(s): 0. Read On →