1. 进程调度
进程调度是内核子系统,用于将有限的处理器使用时间资源分配给各个进程,决定哪些进程可以运行及运行多久。
调度的目标:
*最大化cpu利用率
- 尽可能提高系统交互响应速度
- 让每个进程都能被运行
2. 调度分类
- 协同式:进程自己主动放弃运行,让其他进程运行
- 抢占式:调度程序决定何时停止一个进程运行,让不同进程继续运行
linux使用抢占式。
3. 时间片
时间片是cpu分配的基本单位。
时间片的长短设置值得考虑。
通常,若希望最大化系统吞吐量及整体性能,可以使用较大时间片(减少进程切换,享受时间局部性)。
若希望最佳交互性能,则使用非常小时间片。
注意,进程可能不会用完它的所有时间片,
如,进程被分配了100ms时间,可能运行20ms,然后阻塞等待资源。
调度程序会将其从可运行进程列表中移除,当资源可用,调度程序会唤醒进程。
然后进程继续运行,直到用完时间片剩余80ms,或再次阻塞等待资源。
4. 从进程角度看调度
进程分为:
- IO密集型:希望分配较长时间片,但不太在乎调度优先级
- cpu密集型:很在乎调度优先级,不太在乎时间片长度
linux调度程序会试图找到IO密集型程序,并提高优先级,并降低cpu密集型程序的优先级。
实际上,多数程序是混合型。
5. 抢占式调度
当一个进程的时间片用完,内核会暂停其运行,并允许新的进程。
若系统没有可运行的进程,则内核会给 一组已经用完时间片的进程,重新补足时间片,并再次让他们运行。
如此,所有进程都能得以运行。
若系统没有可运行的进程,内核会运行空间进程,空闲进程实际上不是进程,也不会允许(以便节省电池电力)。
空闲进程是内核的特殊例程,用于简化调度程序算法。
若有一个进程正在运行,突然一个高优先级进程变成可运行的(可能因为键盘输入),于是进程会立即暂停当前正在
运行的进程,并运行优先级高的进程。
因此,当前有时间片的优先级最高的进程绝不可能 进程可运行态,但是未运行状态。
正在运行的进程往往是系统中优先级最高的可运行进程。
5.1 完全公平调度
上面的调度方法的过时的,
完全公平调度 更优调度算法,特点是没有时间片,而用时间比例。
比如,CFS给N个进程分别分配1/N的处理器时间,然后CFS通过优先级计算每个进程的比列以调整分配,
默认优先级为0,权值为1,则比列不变,优先级的值越小,权值越高,增加分配比列。
为了避免比例太小导致只够进程切换消耗,所以 引入 最小粒度,最小粒度为 时间长度的基本单位。
6. 线程
每个线程有自己的虚拟处理器:一套寄存器,指令指针,处理状态。
对linux内核而言,线程是独特的进程,内核将两个线程所组成的进程,看作 两个共享内核资源(地址空间,所打开的文件等)的进程。
7. 让出处理器
int sched_yield(void);
linux虽然是抢占式,但也提供了一个系统调用,让进程可以主动放出执行权。
若存在其他可运行进程,sched_yield 的调用进程会暂停,内核会将其放到可运行进程列表尾部,并选出一个新的进程运行。
若不存在任何其他进程,sched_yield 的调用进程不会暂停,而继续执行。
7.1 sched_yield的用途
首先进程调度应该交给内核,因为内核能看到所有的进程,以安排最优调度策略。
sched_yield的可能用途
- 等待外部事件。
以生产消费者为例
do {
while (producer_not_ready())
sched_yield();
process_data();
} while (!time_to_quit());
但是,unix上不会这样写,因为又更好的方法:事件驱动。
如上面用一个管道代替 sched_yield。
总之,unix程序应该把目标放在依赖可阻塞的fd的事件驱动解决方案上。
- 线程锁定
一个线程需要获得另一个线程持有的锁,则应该让出cpu,让另一个线程释放锁。这个方法很简单,有效。
可替代方法: 现代linux线程实现(new posix threading library NPTL)有一个使用
futexes(为用户空间锁提供内核的支持)的优化解决方案。
8. 进程优先级
linux根据进程优先级对进程进行调度,
优先级会影响进程何时运行,和运行多久。
nice值为 [-20, 19] 默认为0。
8.1 设置优先级
int getpriority(int which, int who);
int setpriority(int which, int who, int prio);
返回当前进程优先级
ret = getpriority(PRIO_PROCESS, 0);
设置当前进程组所有进程优先级为10
ret = setpriority(PRIO_PGRP, 10);
8.2 IO优先级
IO优先级 影响 进程IO请求,IO调度程序会先服务IO优先级高的进程的请求。
默认情况下,IO调度程序使用进程nice值确定IO优先级。因此设置nice值自动变更IO优先级。
9. 处理器亲和性
由于多核环境下,若进程移动cpu,有如下弊端:
- 原cache数据作废
- 不能访问原cache中的数据
所以进程调度程序会尽可能让一个进程安排特定cpu来运行。
但又由于cpu负载可能不均衡,所以当负载不均衡时,调度程序会让进程移动到较空闲的cpu。
有时进程需要一定一直绑定在特定处理器,linux提供了相关系统调用。
放心不会导致cpu负载不均衡,因为调度程序会移动其他进程。
获得进程pid的cpu亲和性,当Pid为0,表示获得当前进程。
cpu_set_t set;
int i;
CPU_ZERO(&set);
sched_getaffinity(0, sizeof(cpu_set_t), &set);
for (i = 0; i < CPU_SETSIZE; i++) {
int cpu;
cpu = CPU_ISSET (i, &set);
printf("cpu = %i is %s\n", i, cpu ? "set" : "unset");
}
若打印
cpu=0 is set
cpu=1 is set
cpu=2 is unset
若希望只运行在cpu0上,可以这么做
cpu_set_t set;
int i;
CPU_ZERO(&set);
CPU_SET(0, &set);
CPU_CLR(1, &set); // 这是多余的,因为上面已经清零,但为了完成性
sched_setaffinity(0, sizeof(cpu_set_t), &set);
for (i = 0; i < CPU_SETSIZE; i++) {
int cpu;
cpu = CPU_ISSET (i, &set);
printf("cpu = %i is %s\n", i, cpu ? "set" : "unset");
}
成功后,打印
cpu=0 is set
cpu=1 is unset
cpu=2 is unset