进程优先级(nice值)
调度模型
Linux 与大多数其他UNIX 实现一样,调度进程使用CPU的默认模型是循环时间共享。在这种模型中,每个进程轮流使用CPU一段时间,这段时间被称为时间片或量子
使用循环时间共享算法满足:
- 公平性:每个进程都有机会使用CPU
- 响应度:一个进程无需等待CPU太长时间
nice值
使用

在传统的UNIX实现中,只有特权进程可以赋给自己或者其他进程更高的优先级,非特权级进程只能降低自己的优先级(降低自己的优先级,变相的增高其他进程的CPU获得机率,这就对其他进程友好(nice)了)
使用fork创建的子进程会继承nice值,并且在exec中仍旧会保持
影响
nice值只是表明了一种被调度的倾向,它并不会直接干扰调度,它只是作为一种权重因素,高特权级(低nice值)的进程更容易被内核调度器调度
获取和修改优先级

参数:
- who:进程id或者用户id
- which: 用于确定参数who如何被解释
- PRIO_PROCESS: 操作 进程ID 为who的 进程,如果who为0,表示使用调用者的进程ID
- PRIO_PGRP: 操作 进程组ID 为who的 进程组中的所有成员,如果who为0,表示使用调用者的进程组
- PRIO_USER: 操作 所有 真实用户ID 为who的 进程。如果who为0,那么使用调用者的真实用户ID
对于getpriority,如果有多个符合的进程,则返回优先级最高的那个
需要注意的是,getpriority成功调用可能会返回-1(nice值是-1),为了与失败调用区分开,我们需要使用 errno,在使用前将 errno 清零,如果失败,errno 会有新值
errno = 0; /* Because successful call may return -1 */
int priority = getpriority(which, who);
if (priority == -1 && errno != 0) {
perror("getpriority");
exit(1);
}
实时进程调度
硬实时与软实时
硬实时
-
实时应用必须要为外部输入提供担保最大响应时间,为了满足这种条件,内核必须提供工具让高优先级的进程能够立即获得CPU的控制权,抢占当前运行的所有进程
-
高优先级进程应该能够保持互斥地访问CPU直至它完成或自动释放CPU
-
实时应用应该能够精确地控制其组件进程的调度顺序
软实时(POSIX 实时)
- 允许控制调度哪个进程使用CPU
实时调度策略
实际上,每个优先级级别都维护着一个可运行的进程队列,下一个运行的进程是从优先级最高的非空队列的队头选取出来的。
- SCHED_RR 策略
在SCHED_RR(循环)策略中,优先级相同的进程以循环时间分享的方式执行。进程每次使用CPU的时间为一个固定长度的时间片。
进程持有对CPU的控制权,直到以下某个事件发生

前两个事件中,进程会被插入与其优先级别对应的队列的末尾
最后一个事件中,被抢占的进程会在高优先级进程执行完(阻塞或终止)后,恢复执行,即被抢占的进程位于与其优先级对应的队列的开头

该函数可用于获取时间片
- SCHED_FIFO 策略
SCHED_FIFO(先入先出,first-in,first-out)策略与 SCHED_RR 策略类似,它们之间最主要的差别在于在SCHED_FIFO 策略中不存在时间片。
进程持有对CPU的控制权,直到以下某个事件发生

第一个事件和最后一个事件同上
实时进程调用 API
实时优先级范围

返回 1~99
修改调度策略和优先级

参数:
-
pid: 要操作的进程ID
-
policy: 确定进程的调度策略
![1]()
-
param: 用于扩展,sched_priority 指定优先级
![1]()
成功调用sched_setscheduler()会将 pid 指定的进程移到与其优先级级别对应的队列的队尾
通过fork()创建的子进程会继承父进程的调度策略和优先级,并且在exec()调用中会保持这些信息。

上一个函数的功能子集,修改进程的调度策略,但不修改优先级,成功调用同样会插入队尾
获取调度策略和优先级

防止实时进程锁住系统
由于SCHED_RR和SCHED_FIFO进程会抢占所有低优先级的进程(如运行这个程序的shell),因此在开发使用这些策略的应用程序时需要小心可能会发生失控的实时进程因一直占住CPU而导致锁住系统的情况。
方法:
-
使用 setrlimit()设置一个合理的低软 CPU 时间组员限制
- 进程消耗太多CPU时间,会收到
SIGXCPU信号,该信号默认杀死进程
- 进程消耗太多CPU时间,会收到
-
使用 alarm()设置一个警报定时器
- 进程运行时间超出
alarm()指定的秒数,会被SIGALARM信号杀死
- 进程运行时间超出
-
创建一个拥有高实时优先级的看门狗进程
- 看门狗进程用于监控其他进程,当发现某个进程的行为异常,看门狗进程降低其优先级或者发送信号停止或终止它
避免子进程进程特权调度策略
我们前面谈到,fork会让子进程继承父进程的调度策略和优先级,如果不想让子进程继承,可以将 sched_setscheduler 中的 policy 参数设置为 SCHED_RESET_ON_FORK,系统会将原本的调度策略(即policy的原本值)与该标记进行或操作
该标志带来的效果:

释放CPU
实时进程可以通过两种方式自愿释放 CPU:通过调用一个阻塞进程的系统调用(如从终端中read())或调用sched_yield()。

仅适用于 实时进程,该函数将当前进程插入到对应优先级队列的队尾,然后运行队头的进程,如果队列中只有一个进程,那就什么都不做
CPU 亲和力
当一个进程在一个多处理器系统上被重新调度时无需在上一次执行的CPU上运行。之所以会在另一个 CPU 上运行的原因是原来的CPU处于忙碌状态。
进程切换CPU会对性能造成一定的影响,因为CPU的高速缓冲器可以存储进程的一部分数据,提高进程的性能,如果切换CPU执行,需要丢弃原CPU的缓存数据,避免因为多CPU造成的不一致性
软CPU亲和力 和 硬CPU亲和力
软CPU亲和力
在条件允许的情况下进程重新被调度到原来的 CPU 上运行
硬CPU亲和力
显式地将其限制在可用CPU中的一个或一组 CPU 上运行
这样做可以:
- 充分利用缓存,提高新能
- 避免或减少并发带来的竞争和CPU缓存未命中
设置和获取CPU亲和力

sched_setaffinity()系统调用设置了 pid 指定的进程的 CPU 亲和力。如果pid为0,那么调用进程的CPU亲和力就会被改变。 赋给进程的CPU亲和力由set指向的cpu_set_t结构来指定。
cpu_set_t 数据类型实现为一个位掩码,通过以下宏进行操作

参数:
- pid:要操作的进程ID
- len:CPU集合位图的大小,通常是sizeof(cpu_set_t),可能是为了跨平台?
- set: CPU集合位图,指定了在哪些CPU上运行,即CPU亲和力
CPU集合中的CPU从0开始编号

sched_getaffinity()系统调用获取 pid 指定的进程的 CPU 亲和力掩码。如果pid为0,那么就返回调用进程的CPU亲和力掩码
如果目标进程的CPU亲和力掩码并没有被修改过,那么sched_getaffinity()返回包含系统中所有CPU的集合。
sched_getaffinity()执行时 不会进行权限检查,非特权进程能够获取系统上所有进程的CPU亲和力掩码
通过fork()创建的子进程会继承其父进程的CPU亲和力掩码并且在exec()调用之间掩码会得以保留


posted on
浙公网安备 33010602011771号