MacOS: Mdworker曾让我很不爽

TL;DR

一开始,我以为mdworker是“memory decompress worker”, 好吧,谁让我是linux内核出身的哪,正应了那句话,叫做“往往以自己的立场来揣测别人”。 事实上,它是“metadata server worker”。不过它让我很不爽的原因与它的名字无关,在我的Macbook刚升级到Mavericks的时候,大概是升级后还有很多善后工作要处理,它老是偷偷摸摸的运行,导致我看个优酷都卡的不行,你能体会到正到精彩处电脑却卡的连鼠标都动不了的那种心情。 需要声明的是,那段时间我在看《绝命毒师》:)  

然后我就有了要干掉这个mdworker的念头。作为一个内核出身的工程师,自然是不屑于打开control panal,这里勾一下那里选一下的这种毫无技术含量的做法的。I do it on my own。我打算自己写个C代码来控制它,虽然这不是个好方法,甚至都不是个正确的方法,但是至少我在思考并且知道是可行的,而且我还能搞定它。

再然后我就发现我不太适合做一个freelance,我的这个想法一直拖拖拉拉都没动笔,过了段时间Maverick竟然很流畅了,mdworker也轻易不出山了,即使出山也没那么大动静了,这更让我没动力来写了。 

最后,有一天我感冒了,周末躺在床上什么都不想动,于是打开了电脑就开始写这个很无聊的工具了。 

我的目的是这样的:我要让这个进程的cpu占用率低于某个值,比如10%。

思路

接着我就想该怎么来实现,虽然当时我躺在床上浑身无力,可是大脑还能正常运转,下面是我的思路:

  1. 跟据mdworker这个名字来找到对应的进程
    关键点:
    可能会有多个进程,注意是起多个进程,不是指多线程。
  2. 我需要遍历整个进程链表来进行名字匹配,如果某个进程的名字mdworker,就认为找到了对应的进程。 关键点:
    bsd系统维护着两个进程链表,一个是allproc,一个是zomproc。zomproc是bsd的僵死进程链表,非僵死进程都在allproc链表里面。所谓僵死进程是指,这个进程挂掉了,但是它没有通知它的父进程,打个比方就是,你死了但是还没有去民政局里登记你的死亡,此时你就是处在僵死状态。
    要明白allproc和zomproc这两个链表的特性,我们只需要知道他们使用的是什么结构体就好了。他们两个使用的都是bsd系统的通用结构体LIST_ENTRY,于是我们就可以在心里默念一下这个结构体的特点,它是一个双向非循环链表,会使用一个全局变量header来标识这个链表,这个header会指向链表的头部和尾部,在通常情况下我们都是把新元素插入到链表的头部。我们知道系统中进程的PID是按照由小到大递增的,也就是现有PID0,然后是PID1… 于是我们就知道这个allproc和zomproc链表里的元素(进程)从尾部到头部的PID是逐渐增大的,当然如果fork的进程数超过了PID MAX,那就另说了。
  3. 抓取到这个进程后,我们就可以使用它的pid来控制它了,如何来控制?
    关键点:
    进程A如何来控制一个毫不相干的进程B? 也许你一下子想不起来该怎么来实现,但是你再仔细想一想,这不就是GDB干的事嘛!gdb使用的ptrace系统调用来将目标进程变成自己的子进程,然后attach到这个子进程后就可以任意的玩这个子进程了,想必对于我们国人而言这个不太难理解,上司自然是可以控制自己的下属的,或者说,我要想让你听我的话得先把你变成我的下属。人之本性在技术层面也是被体现的淋漓尽致啊!     如果使用ptrace的话,在这里可能就有些大材小用了,我一开始设想是使用ptrace系统调用,后来发现实现起来相对复杂些。 然后我又想到,CTRL+C不也是可以停止一个进程嘛,它使用的是信号,我可以使用signal来实现我的目的。 即,如果进程老是占用CPU,我就给它发一个stop信号来停止它,过段时间我再给它发个continue信号让它运行。当然前提是它可以接受信号。
    至于说让它停止多长时间和运行多长时间则是一个小学数学问题了。 比如我想让这个进程的CPU占用率不超过10%,那么只要检测到该进程的CPU占用率超过了10%,我就强制让它睡眠。假如我以1S为一个周期,我只要让进程的工作时间不超过100ms就可以了。
  4. 如何计算一个进程的CPU占用率
    关键点:
    我的计算方式是,在时间点a我记录下进程执行了多长时间pa,然后在时间点b我再记录进程执行了多长时间pb。那么(pb-pa)/(b-a)就是这段时间进程的CPU占用率。 
  5. 我要想控制别人,得比别人拥有更大的权利
    关键点:
    这个进程既然是控制进程,那么它得比被控制的进程具有更高的优先级来执行,不然被控制进程都执行完了那这个控制进程也没啥用了。
    • 首先,这个控制进程选用什么样的调度策略。当然是选用Normal,如果选用FIFO或者RR不是会更影响整个系统的性能嘛!
    • 接着,normal进程可以通过调整它的nice值来调整它的优先级,nice的范围是(-20,+20),-20具有最高优先级,+20的优先级最低。
    • 最后,我们可以通过setpriority()这个函数来减小进程的nice值,不过,Only the super-user may lower priorities.(man手册)。也就是说,你要使用root权限。

coding

有了上面的思路之后,我就开始动笔写了,虽然躺在床上浑身乏力,可是手指还算灵活,敲起键盘来也还能噼里啪啦。 
然后,我就在github上发现了一个类似的东西,叫做 cpulimit  。我看了一下它的代码,发现了很多bug,也想到了解决方案,后来看到这个项目只活跃了半年时间,而且都一年多没有commit了,就没有心思去给这个项目贡献点自己的力量了。 我的代码可以说是这个cpulimit的mini版,虽然短小功能少,可是很强悍,正所谓浓缩的都是精华。 我正在着手将我的代码保存到github上时,忽然卡特打了个电话喊我一起打篮球。大概是写了点代码精神爽吧,虽然感冒没好完,我还是毫不犹豫的披挂上阵了。于是那个周日的下午,我和卡特,姚明一起在中科院电子所和其他几个人3v3了两三个小时,等到周一我起来去上班时感觉整个身子骨都散架了。 
在我打算写这篇博客时,也将这个mini版的cpulimit给放到了github上: my-task-manager  。 

Comments