随记

fleuria » 24 Dec 2010

调度

发现这个词很暧昧,教科书上说的“调度”几乎就是"进程调度"的同义词,可是看下各种源代码的schedule()函数里面除了进程调度还总是有很多怪怪的东西。搞明白还有个"页面调度"就好理解了。Unix V6与进程的优先级调度相关的属性只有p_cpu和p_pri,linux0.11里面对应的也只是counter和priority两个属性,至于utime,stime,cutime,cstime,start_time神马都是页面调度用的。在这里把“优先级调度”的这个“调度”说成”cpu调度“或许更准确些。

需要留意一个历史背景,那就是Unix V6的内核是swap式的内存管理,内存里只有有数的几个进程,换出则换出整个进程。进程切换时候发生swap的概率还是很高的,所以页面调度和进程调度几乎是一体。当然很低效了。

写时复制

似乎很容易就可以实现写时复制,不过有点细节需要注意。

  • 需要把父进程和子进程的所有页面都标记为只读。 (linux0.11的copy_mem()函数对进程0特殊处理不标记为只读就是因为这个。内核地址空间里的页要都只读了还了得)
  • 需要为每个物理页留一个引用计数。 (这也就是3BSD没搞写时复制的原因(其设计目标之一即对原系统做最少的修改),为fork性能做的trade off是一个新的系统调用vfork。顺便一提,3BSD是第一个应用页式内存管理的Unix系统,先前移植到VAX机的Unix/32V还是swap的机制,很蹩脚)。
  • 出现写保护错误的时候,就复制这个页面。若引用计数为1,就把最后那个页面的写保护去掉。

K.I.S.S

有点感觉到前面那篇post的幼稚了。早期Unix的内核确实简洁明快,不过要用它肯定是自虐吧...最近经常在想,问题本身的复杂是一个KISS可以盖的过去么?fork()+exec()接口比CreateProcess()好看一万倍,但Unix引入线程的时候也是阵痛了好一阵。fork()本身并不是适合多线程的设计,这是自己承认的。而引入线程是像《unix编程艺术》里说的那样仅仅是因为愚蠢么?不知道...可是线程确实比进程更轻量,至于怎么同步那就是考量程序员的事了。

像Unix早期的抽象机制,"一切皆文件"。进程通信依然是文件系统的范畴,管道就是硬盘上一个文件,两个进程可以一个读一个写。后来出现了n多复杂的IPC机制,到BSD时候管道改由socket实现的时候,Unix”一切皆文件“的抽象就已经不是再适用了。大神看囊肿了不爽了,搞Plan9,网络、驱动全部回归成文件,内核的代码量恢复成十来万行,然后...就没有然后了....

Mach则是一切皆进程。port是第一公民(我怀疑socket在设计时有参考过port,名字都像,但是没找到资料),连系统调用都是port连接起来的进程通信(Message)。设计的动机和Plan9也差不多(这说法不恰当,Mach比plan9早好像),就是通过合理的抽象减少内核的体积,同时为可移植性再设计。一段时间看来这显然是现代系统高科技的典范,GNU那个杯具系统的内核就是GNU Mach,而HURD仅仅是内核以外的一组核心的进程。然后微内核杯具了,喜欢微内核的老教授Tanebaum还让毛头的Linus带人围观了一通(参考人家的minix碍不着linus骂他)。

单靠一套抽象机制独当一面就比较容易捉襟见肘吧。水至清则无鱼,逮上某某主义一而贯之,往往不如具体问题具体分析来的实在。

hosted on github, and powered by jekyll. (rss)