1 。我个人认为作者 MAX 对 Linux 的了解不像他对 Solaris 那样深入,我不知道也没法知道他的下列关于
Linux 的内容来自自己的代码阅读分析还是只是来自第三方的文档资料而未经自己实地验证;
2
。我已经尽量符合原意地翻译了,当然中间实在忍不住的地方也插两句自己的话;
3
。无论是只阅读这一篇文章,还是看其他东西,我都觉得,保持自己头脑清醒很重要;
4 。谢谢
Max Bruning 是一名教师 / 资讯专家,他的教授内容包括 Solaris
内部组织,设备驱动,内核和应用的 crash 分析及调试,网络组织和其他一些特定科目(他的 blog 在 blogspot
,不费点劲可能访问不了,所以也可以看看 http://www.bruningsystems.com/)。
在解释这些子系统在 Solaris 中是如何实现的时候,他的学生们总会问 “Linux
里它是怎么工作的? ” 或者 “FreeBSD 里是这样, Solaris 里呢? ” 这种经历最终让 Max 在
OpenSolaris 网站写了这篇 A Comparison of Solaris, Linux, and FreeBSD
Kernels 。
文章里讨论了调度,内存管理和文件系统架构--这 3
个子系统在任何操作系统中都有普遍应用,而且他们是最 well-understood 的组件。
目前很多分析或对比文章所引用的材料及代码都比较老,与现实脱节, Max 推荐如下几个多少比较 up
to date 的网站:
Solaris Vs. Linux
Comparing MySQL Performance
Fast Track to Solaris 10 Adoption
Solaris 10 Heads for
Linux Territory
其实抛开 3 个系统之间的差别,他们也有很多相似之处。除了那些不同的命名习惯,这些 OS
在实现不同概念的时候采用了非常相似的方法。他们都支持线程的分时调度,支持最近未使用页面替换算法实现请求调页,支持虚拟文件系统层允许不同文件系统架构。这个系统里的一个好概念在另一个系统里也会采用。比如
Linux 也接受并实现了 Solaris slab 内存分配算法的概念。 FreeBSD 代码里的很多术语在 Solaris
里也出现了(快去看看代码。。。)。考虑到这 3 个系统的源代码都能得到了, fxr.watson.org
提供了系统源码的交叉阅读浏览,可能会发现很多有趣的地方。
好了,温情默默的套近乎结束,进入正题。
调度和调度器
Solaris 的调度单位是
kthread_t , FreeBSd 是 thread , Linux 是 task_struct 。抬高一级, Solaris
的进程是 proc_t ,当然每个进程里的线程就是 kthread_t ; Linux 的进程和线程都由 task_struct
表示,单线程的进程在 Linux 里是一个 task_struct 。单线程的进程在 Solaris 里有一个 proc_t ,一个
kthread_t ,还有一个 klwp_t 表示。 klwp_t 提供了用户和内核模式线程切换的存储区。 FreeBSD
里的单线程进程有一个 proc ,一个 thread 和一个 ksegrp 。 ksegrp 是 “ 内核调度的实体组 kernel
scheduling entity group” 。三个系统的线程表示结构不同,不过都支持调度线程。
和大家熟悉的基本一样,调度是基于优先级的。小小的数学问题是,在 Linux 和
FreeBSD 里,数字越小,优先级越高;而 SUN 的宝贝却喜欢数字越大,优先级越高。参考下表

三个系统都更推崇 interactive 线程 / 进程(下面会提到 interactive
怎么回事)。 Interactive 线程比 compute-bound 线程优先级要高,不过得到的时间片要少一些。 Solaris
, FreeBSD 和 Linux 都使用每 CPU 的 “ 运行队列 runqueue” 。 FreeBSD 和 Linux
有一个 active 队列和一个 expired 队列。名字说得很清楚了--系统从 active
上按照优先级选择线程进行调度。用完自己时间片的线程就从 active 搬到 expired 上(或者为了避免 “ 饿死 ”
的其他情况), active 空以后,内核交换 active 和 expired 。 FreeBSD 还多一个 idle
队列--其他两个 queue 都空的时候才轮到这个。 Solaris 的概念是每 CPU“ 调度队列 dispatch queue”
。线程用完时间片后,内核给其一个新优先级然后放回调度队列。所有 3 个系统的 runqueue
,对不同优先级的可运行线程都分别有链表。
FreeBSD 四个优先级共享一个链表, Solaris 和 Linux 则每个优先级一个链表
Linux 和 FreeBSD 结合运行时间和睡眠时间计算线程的 interactive-ness , Solaris
查表。他们都不支持 “gang scheduling” (有兴趣查 Google
即知,并行计算上的调度算法,大白话说就是一组任务一把 disptach 到各个 CPU 上。劳伦斯 .
利弗莫尔那帮造原子弹的家伙最喜欢了,他们有世界上最昂贵的玩具,可以理解)每个 OS 都调度下一个线程而不是 N 个线程开始运行。这
3 个 OS 都有利用 CACHE ( warm affinity )和负载均衡的机制。对超线程 CPU , FreeBSD
能尽量将多个线程保持在一个 CPU 节点上(当然可能是不同的 CPU 超线程上)。 Solaris
也有类似机制,不过是在用户和应用的控制下,而且并不限于 CPU 的超线程,他们的术语是 processor sets ,
FreeBSD 的叫法是 processor groups 和其他 2 个 OS 最大的不同是, Solaris 同时支持多个
“scheduling classes” 。 3 个 OS 都支持 POSIX 的 SCHED_FIFO , SCHED_RR 和
SCHED_OTHER (或者 SCHED_NORMAL )。 SCHED_FIFO 和 SCHED_RR
通常支持实时线程(我不同意。。。但是照翻。。。)。
Solaris 和 Linux 为支持实时线程都支持了内核抢占。 Solaris 支持 fixed
priority 类, system class 的是系统线程(比如换页线程), interactive 的是在 X
控制下运行窗口环境的线程,还有一个 Fair Share Scheduler 用于资源管理。具体可以参考 Solaris 资料。
FreeBSD 的调度器是在编译时决定的, Linux 的调度?--要看版本了。
支持在系统中加入新的调度类是要付出代价的。内核中每个可能决定调度的地方都得有一个间接得函数调用去 call
调度类相关的代码。比如,当一个线程将要 sleep 时,内核调用调度类相关代码,完成该类中线程 sleep 需要完成工作。在
Linux 和 FreeBSD 上,调度已经完成了所有工作。不需要再来一个间接调用。额外的层次,就意味着 Solaris
的调度要占用稍微多一点的系统开销--不过提供了更多的功能。
内存管理和分页
Solaris 的进程地址空间由逻辑段 segment 组成。进程地址中的这些段可以通过 pmap
访问。 Solaris 将其内存管理代码和数据结构分为平台无关和平台相关部分(这不跟没说一样嘛。。。)。平台相关部分位于 HAT (
hardware address translation )层。 FreeBSD 用 vmspace
描述进程地址空间,将其划分为逻辑块 region 。硬件相关部分在 pmap ( physical map )模块,而 vmap
例程处理硬件无关部分和数据结构。 Linux 使用内存描述符划分进程地址空间,逻辑单位是 memory areas 。 Linux
也由 pmap 来 examine 进程地址空间。
Linux 将机器相关层从更高层次的机器无关层中划分出来。
Solaris 和 FreeBSD 中大多数类似代码比如 page fault 处理是机器无关的,而 Linux 处理 page
fault 的代码则非常机器相关--从 fault 处理开始就是这样了。由此下来的结果是, Linux
能很快地完成大多数分页相关代码--因为数据抽象更少。不过,代价是,下层硬件的改变需要大量修改代码-- Solaris 和
FreeBSD 则分别把这样的工作堵截在 HAT 和 pmap 层搞定。
Segment , region 和 meory area 的分割是:区域的虚拟地址
segmetn/region/memory area 映射的 object/ 文件的位置权限 map 的大小
例如,程序的 text ( text 段,即代码)在一个 segmetn/region/memory area 中, OS
管理地址空间的机制是类似的,不过数据结构名字完全不同。
分页 3 个系统都使用了最近最少使用 least
recently used 算法的变种完成页替换。他们都有一个守护 daemon 进程 / 线程完成页替换。 FreeBSD 的是
vm_pageout daemon ,它周期性地,或者当 free 的内存不多时,被唤醒。当可用内存低于某个限制时,
vm_pageout 运行例程 vm_pageout_scan 扫描内存并释放一些页面。 vm_pageout_scan
例程可能需要异步地将更改过的页面写回到磁盘,在释放他们之前。不论由多少颗 CPU ,只有一个这样的 daemon 。 Solaris
的是 pageout daemon ,它也周期性地运行,处理空闲内存不多的情况。 Solaris
中的分页限制值在系统启动时自动校准,这样可以避免该守护进程过渡占用 CPU 或者向磁盘发出洪水般的换页请求(嗯, flood
这么翻正好 ;P )。
FreeBSD 的 daemon 在大多数情况下使用的值是固定的--不过也可以调整。 Linux 的
LRU 算法可以在运行时动态调整,而且可以有多个 kswapd daemon ,每 CPU 最多一个。这 3 个系统都使用
global working set 策略,而不是 per process working set 。 FreeBSD
有多个页面链表来追踪最近使用页。包括 active , inactive , cached 和 feee
页。根据使用情况,页面在这些链表间走来走去。经常访问的页面会在 active 上。退出的进程的数据页面将被马上放到 free 上。
如果因为负载原因 vm_pageout_scan 来不及扫描全部内存的话, FreeBSD
内核可能将整个进程全部换出。如果内存短缺十分严重, vm_pageout_scan 可能会 kill 系统中最大的进程。 Linux
也使用不同的页面链表。物理内存被分为(多个) 3 重 zone :一个 DMA 页面,一个普通页面,一个动态分配内存页面。 zone
的实现很像由于 x86 架构限制而很产生的。页面在 hot , cold 和 free 链表间移动--机制和 FreeBSD
的类似。经常用的页面在 hot 上。可用页面则在 cold 或者 free 上。
SUN 的大佬使用 free 链,哈希链, vnode 页面链支持自己的 LRU 实现。后两者大致相当于
FreeBSD 和 Linux 的 active/hot 链--也是 FreeBSD 和 Linux 要扫描的链。 Solaris
要扫描的不是这两个对象,它用 two-handed clock 算法扫描全部页面(见 Solaris Internals
或其他什么地方随你便)。大致方法是,两只手相隔固定举例,前面的手将 page
的引用位清空以作为标识,如果自此开始没有进程引用这个页,后面的手就释放这个页面(当然如果需要就写回磁盘)。
3 个系统在分页时都考虑了 NUMA 本地性。他们都把 IO buffer cache 和虚拟内存页面的
cache 合并到一个系统页 cache 中。系统页 cache 用于读写文件已经被 mmap 了文件,还有应用的 text 段和
data 段。
文件系统
3 个系统都使用数据抽象层向应用隐藏文件系统实现细节。就是用大家熟悉的 open , close ,
read , write , stat ,等等系统调用访问文件,无论下层的文件数据的实现和组织如何。 Solaris 和
FreeBSD 把这种机制称为 VFS ( virtual file system ),基本数据结构是 vnode (
virtual node )。 Solaris 和 FreeBSD 里每个被访问的文件都有一个赋给他们的 vnode 。除了
generic 的文件信息外, vnode 还包含到 file-system-specific 信息的指针。 Linux
采用了详细的机制,也叫 VFS ( virtual file switch ),文件系统无关的数据结构是 inode 。这个机构和
vnode 类似(小心: Solaris 和 FreeBSD 也另有自己的 inode --是 UFS 文件系统里
file-system-dependent 的数据)。 Linux 还有两个不同的结构,一个用于文件操作,另一个用于 inode
操作。 Solaris 和 FreeBSD 将他们合并为 vnode 操作。
VFS
允许在系统里实现多种文件系统。这意味着他们相互访问对方的文件系统没问题。只要相关的文件系统例程和数据结构已经被移植到 VFS
上。所有这 3 个系统都允许文件系统堆叠 stacking 。下表列出了每个 OS 实现的文件系统类型,不是全部哈。

结论
Solaris , FreeBSD 和 Linux
显然都在从对方身上获益。随着 Solaris 的开源,这种相互促进有望更快。 Max 个人已经感觉到 Linux
的变化是最快的。新技术被快速地集成进系统,只是文档和健壮性可能有点落后。 Linux 有很多--或者有时是看上去有很多--开发者。
FreeBSD 则大概是(从某种意义上) 3 个系统中历史最长的。 Solaris 来自 BSD Unix 和 AT&T
Bell 实验室 Unix
的结合,使用了更多数据抽象层,因而一般说来能更简便地支持更多功能。不过,内核中大多数这样的分层都没有文档描述。可能随着代码的开放这一点会有所改善。
至于他们的差别,最大的地方之一是 page fault 处理了。在 Solaris 中,发生
page fault 时,代码是从平台相关的 trap handler
开始执行的(以大家的智商,这好像不用说了吧。。。),然后会调用 generic 的 as_fault 例程,这个例程判断发生
page fault 的 segment ,然后调用 segment driver 处理 page fault 。 segment
driver 调用文件系统代码,后者再调用进驱动程序,换入页面。换入完成后, segment driver 调用 HAT
层来更新页表项。在 Linux 上,发生 page fault
后,内核调用的代码在会马上进入平台相关部分,这些处理可能更快,不过可能不太容易扩展和移植(后半段说得太省,不知道作者有没有真的研究过
Linux 下对应的处理过程)。
内核观察和调试工具对正确理解系统行为有关键意义。在这方面, Solaris 有 kmdb , mdb
和 DTrace 。在开源之前, Max 就对 Solaris 做过多年 “ 反向工程 ”
--他发现解决问题的时候使用工具总比阅读代码来得快--我也知道,不过得看什么场合,大家可不要被他误导。 Linux 嘛,我看作者
Max 不太熟,所以认为没有太多工具。对 FreeBSD ,他也认为只是可以用 GDB 调试内核的 dump -- Liux
也可以。
【本站声明】本站刊载的部分内容全部来源互联网,对于此类文章本站仅提供交流平台,不为其版权负责。如涉及侵犯您的知识产权的文章,请联系我们,我们将尽快做出更正。并向您表示感谢!同时特别感谢对本站所有支持的网友。