进程调度是内核子系统,用于将有限的处理器使用时间资源分配给各个进程,决定哪些进程可以运行及运行多久。
调度的目标:
*最大化cpu利用率
linux使用抢占式。
时间片是cpu分配的基本单位。
时间片的长短设置值得考虑。
通常,若希望最大化系统吞吐量及整体性能,可以使用较大时间片(减少进程切换,享受时间局部性)。
若希望最佳交互性能,则使用非常小时间片。
注意,进程可能不会用完它的所有时间片,
如,进程被分配了100ms时间,可能运行20ms,然后阻塞等待资源。
调度程序会将其从可运行进程列表中移除,当资源可用,调度程序会唤醒进程。
然后进程继续运行,直到用完时间片剩余80ms,或再次阻塞等待资源。
进程分为:
实际上,多数程序是混合型。
当一个进程的时间片用完,内核会暂停其运行,并允许新的进程。
若系统没有可运行的进程,则内核会给 一组已经用完时间片的进程,重新补足时间片,并再次让他们运行。
如此,所有进程都能得以运行。
若系统没有可运行的进程,内核会运行空间进程,空闲进程实际上不是进程,也不会允许(以便节省电池电力)。
空闲进程是内核的特殊例程,用于简化调度程序算法。
若有一个进程正在运行,突然一个高优先级进程变成可运行的(可能因为键盘输入),于是进程会立即暂停当前正在
运行的进程,并运行优先级高的进程。
因此,当前有时间片的优先级最高的进程绝不可能 进程可运行态,但是未运行状态。
正在运行的进程往往是系统中优先级最高的可运行进程。
上面的调度方法的过时的,
完全公平调度 更优调度算法,特点是没有时间片,而用时间比例。
比如,CFS给N个进程分别分配1/N的处理器时间,然后CFS通过优先级计算每个进程的比列以调整分配,
默认优先级为0,权值为1,则比列不变,优先级的值越小,权值越高,增加分配比列。
为了避免比例太小导致只够进程切换消耗,所以 引入 最小粒度,最小粒度为 时间长度的基本单位。
每个线程有自己的虚拟处理器:一套寄存器,指令指针,处理状态。
对linux内核而言,线程是独特的进程,内核将两个线程所组成的进程,看作 两个共享内核资源(地址空间,所打开的文件等)的进程。
int sched_yield(void);
linux虽然是抢占式,但也提供了一个系统调用,让进程可以主动放出执行权。
若存在其他可运行进程,sched_yield 的调用进程会暂停,内核会将其放到可运行进程列表尾部,并选出一个新的进程运行。
若不存在任何其他进程,sched_yield 的调用进程不会暂停,而继续执行。
首先进程调度应该交给内核,因为内核能看到所有的进程,以安排最优调度策略。
sched_yield的可能用途
do { while (producer_not_ready()) sched_yield(); process_data(); } while (!time_to_quit());
但是,unix上不会这样写,因为又更好的方法:事件驱动。
如上面用一个管道代替 sched_yield。
总之,unix程序应该把目标放在依赖可阻塞的fd的事件驱动解决方案上。
linux根据进程优先级对进程进行调度,
优先级会影响进程何时运行,和运行多久。
nice值为 [-20, 19] 默认为0。
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);
IO优先级 影响 进程IO请求,IO调度程序会先服务IO优先级高的进程的请求。
默认情况下,IO调度程序使用进程nice值确定IO优先级。因此设置nice值自动变更IO优先级。
由于多核环境下,若进程移动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