二,进程管理
1. FreeBSD 的进程既可以在用户态 (user mode) 运行,也可以在内核态 (kernel mode) 运行。
2. 进程结构
3. 进程状态
State |
Description |
NEW |
undergoing process creation |
NORMAL |
thread(s) will be RUNNABLE, SLEEPING, or STOPPED |
ZOMBIE |
undergoing process termination |
4. 线程调度的类型
Range |
Class |
Thread type |
0–63 |
ITHD |
Bottom-half kernel (interrupt) |
64-127 |
KERN |
Top-half kernel |
128-159 |
REALTIME |
Real-time user |
160-223 |
TIMESHARE |
Time-sharing user |
224-255 |
IDLE |
Idle user |
注:此处的 Range 是优先级范围,而范围 128-255 皆为用户态优先级
5. 主动 / 被动上下文切换
主动的上下文切换发生在线程需要等待不可用资源的情况;
被动的上下文切换则发生在线程的时间片用完或系统确定让优先级更高的线程先执行的情况。
6. 等待通道一般情况下是某个数据结构的地址,这个数据结构表示的线程正在等待的资源或者事件。
7. 线程睡眠队列
sleep() 与 wakeup() 例程通过求等待通道的散列值来计算出睡眠线程位于这些队列中的哪一个。
8. 上锁的级别
Level |
Type |
Sleep |
Description |
Lowest |
hardware |
no |
memory-interlocked test-and-set |
|
spin mutex |
no |
spin lock |
|
sleep mutex |
no |
spin for a while, then sleep |
|
lock manager |
yes |
sleep lock |
Highest |
witness |
yes |
partially ordered sleep locks |
9.lock manager
进程间对一种资源的同步一般通过把它和一个锁结构联系起来的方法来实现。而锁管理器则操作这么一个锁。锁管理器提供了两种类型的锁: exclusive lock 与 shared lock ,并且提供了两种锁之间的转换方法与规则。
10. 线程调度
FreeBSD 提供了三种线程调度算法:分时调度算法,实时调度算法和 ULE 调度算法。
1> 分时调度 (time-share-scheduling algorithm)
该算法是以多级反馈队列 (multilevel feedback queue) 为基础,系统动态地调整线程的优先级,反映出资源的需求和线程消耗的资源量。系统根据线程调度优先级的变化把它们在运行队列之间移来移去。系统使用 64 个运行队列,它根据线程的优先级除以 4 的商,为线程选择一个队列。队列内部则不会再更具其优先级来排序。该算法仅涉及到优先级范围的 160-233 。
2> 实时调度
该算法所涉及的优先级范围为 128-159 。实时线程的优先级由应用程序本身自行设置,内核不会改变它们的优先级。实时线程的运行级别高于分时线程,为于内核级线程之下。
3>ULE 调度
将 sched_ule 结合结合起来就能知道 ULE 的含义了—没有含义,仅仅是为了调度函数更好看。
ULE 的目的是为了:
提供对 SMT(symmetric multithreading ,对称多线程机制 )— 多核心 CPU— 更好的支持。
提高调度算法的性能,从而让性能不在与系统内的数量有关系。
ULE 调度程序给系统内的每个 CPU 都创建了一组共 3 个队列。每台处理器都有自己的队列,于是就有可能在 SMP 系统上实现处理器绑定机制。
3 个队列中有一个队列是 idle 队列,其中保存了所有空闲线程。另外两个队列是 current 队列和 next 队列。线程按照优先级次序从当前队列选出来运行,直到该队列为空,然后交换 current 队列和 next 队列,重新开始执行调度。只有其他两个队列都为空的时候,才运行处于 idle 队列内的线程。实时线程和中断线程总是被插入到 current 队列,使其调度延迟尽可能小,而将交互式线程插入 current 队列是为了保持交互相应速度。
除此之外, ULE 还为在多个处理器之间来回迁移线程提供了两种机制。
第一种,当一个 CPU 的队列中没有要做的工作时,它就在一个为所有处理器共享的比特位掩码 (bit-mask) 中对一个比特位做标志,表明它处于空闲状态。其他处理器仅通过快速检查共享掩码就恩那个知道那个处理器处于空闲,并将自己的一部分线程迁移过去,以把负载均分到系统上。
第二种,系统会定期执行 ( 每秒两次 ) 选出系统内负载最重和最轻的处理器,然后平摊他们的运行队列,从而能更积极地将任务均摊到系统内的其他处理器上。该形式的迁移叫做推迁移 (push migration) 。
下图是处理 SMT 时,为各个核心建立的数据结构
Processor with two cores.
11. 进程管理的主要系统调用流程
Process-management system calls.
12. 为什么还需要 ZOMBIE 态?
不外乎两个原因:第一,当一个进程调用 exit 退出的时候,它无法清除完所有自身运行状态数据,例如进程项以及运行栈上的数据。第二,为了父进程统计子进程整个生命周期中的所消耗的资源,这些资源有子进程提供,但由父进程使用或忽略。最后才有父进程将进程项里的数据清除掉。