进程模型2-进程优先级
内核版本 | 架构 | 作者 | GitHub | CSDN |
---|---|---|---|---|
Linux-3.0.1 | armv7-A |
下图来自 蜗窝科技 的《Linux调度器:进程优先级》
优先级的修改
在用户空间影响进程的参数有 nice value 和 scheduling priority,但是他们本身并不是优先级只是影响优先级的计算参数。对于普通进程通过 nice value 来修改进程优先级,nice value 范围 -20~19,通过修改 nice value 改变进程优先级从而修改普通进程获取 cpu 资源的比例。
对于实时进程通过 scheduling priority 来修改进程优先级, 实时进程的scheduling priority 的范围是 1~99,当然,虽然普通进程也有 scheduling priority 但是被设定为 0。
设置普通进程的进程优先级
#include <sys/time.h>
#include <sys/resource.h>
int setpriority(int which, int who, int prio);
参数:
- which :
- PRIO_PROCESS,设置指定进程的优先级,此时who的取值为进程ID
- PRIO_PGRP,设置进程组的所有进程的优先级,此时who的取值为进程组的ID
- PRIO_USER,设置用户拥有的所有进程的优先级,此时who的取值为实际用户ID
- prio:
- 此处的prio并非正真的内核优先级,实则为nice值,取值范围 [-20, 19]之间的任意值。
返回值:
0:设置成功
<0:错误原因存于errno
- 此处的prio并非正真的内核优先级,实则为nice值,取值范围 [-20, 19]之间的任意值。
设置实时进程的进程优先级
#include <sched.h>
int sched_setparam(pid_t pid, const struct sched_param *param);
参数:
- pid :
- 设置指定pid进程的优先级
- param:
- 带有优先级的参数
返回值:
0:设置成功
<0:错误原因存于errno
- 带有优先级的参数
优先级的计算
优先级相关宏
#define MAX_USER_RT_PRIO 100
#define MAX_RT_PRIO MAX_USER_RT_PRIO
#define MAX_PRIO (MAX_RT_PRIO + 40)
#define DEFAULT_PRIO (MAX_RT_PRIO + 20)
/*
* Convert user-nice values [ -20 ... 0 ... 19 ]
* to static priority [ MAX_RT_PRIO..MAX_PRIO-1 ],
* and back.
*/
#define NICE_TO_PRIO(nice) (MAX_RT_PRIO + (nice) + 20)
#define PRIO_TO_NICE(prio) ((prio) - MAX_RT_PRIO - 20)
#define TASK_NICE(p) PRIO_TO_NICE((p)->static_prio)
优先级的类型
名称 | 字段 | 描述 |
---|---|---|
静态优先级 | static_prio | 保存静态优先级, 是进程创建时默认从父进程复制而来, 可以通过nice 和setpriority() 系统调用来进行修改, 否则在进程运行期间会一直保持不变 |
实时优先级 | rt_priority | 用于保存实时优先级,可以通过 sched_setscheduler()系统调用来进行修改 |
普通优先级 | normal_prio | 此值又称为归一化优先级,表示基于进程的静态优先级static_prio和调度策略计算出的优先级,这个优先级在进程调度过程中保持不变,用于存放在临时调整优先级后返回调整前优先级的值 |
动态优先级 | prio | 是调度器使用的优先级,在特殊情况下内核会提高进程优先级,正常情况下prio=normal_prio |
静态优先级
静态优先级如果后期没有修改则 static_prio 的值是在 fork 时从父进程复制而来,用户修改是通过 nice 的值使用 NICE_TO_PRIO(nice) 宏设置,可以看到,nice 的取值范围是[-20, 19],而 static_prio = 120 + nice 范围在[100, 139],static_prio 的值越小,表明进程的静态优先级越高。
普通进程的调度策略中优先级实际上表示了进程对cpu占有情况,在内核代码中可以发现当修改了静态优先级后必须要使用 set_load_weight§ 重新设置cpu 的载入权重,从而将优先级和cpu载入权重关联起来。
实时优先级
rt_priority 值的范围是 [0 ~ 99],它并不是实时进程真实的优先级,而是在计算动态优先级时使用的计算参数,只对实时进程有效,通过 rt_priority 计算真正优先级的公式为 MAX_RT_PRIO - 1 - rt_priority,可以看出 rt_priority 的值越大,计算出的值就越小,对应的实时进程的优先级就越高。
普通优先级
normal_prio 又成为归一化优先级,值取决于调度策略,用于将实时进程和普通进程的优先级进行统一,在实时进程中 normal_prio = MAX_RT_PRIO-1 - p->rt_priority,在普通进程中 normal_prio = static_prio = 120 + nice,其值在没有调用其他设置接口的情况下,调度过程中保持不变,用以还原优先级临时提高后优先级的恢复。
动态优先级
prio 是调度器真正使用到的值,在调度期间可能应为防止优先级翻转会临时提高动态优先级的等级,在正常情况下对于普通进程来说 prio = normal_prio = static_prio,对于实时进程来说互斥锁等待链表上没有阻塞其他进程时 prio = normal_prio,否则选择阻塞链表上最小的 prio 值和当前优先级进行比较,获取最小值,将实时进程的动态优先级进行临时提高。
优先级代码走读
进程优先级参数
struct task_struct {
...
int prio, static_prio, normal_prio;
unsigned int rt_priority;
...
};
fork时优先级的继承
do_fork
└── copy_process
├── sched_fork
└── pid_task
```
```c
void sched_fork(struct task_struct *p)
{
...
/*
* Revert to default priority/policy on fork if requested. p->static_prio来自子进程继承父进程的普通优先级
*/
/* 1. 检查从父进程是否因为某种原因需要将子进程在fork时重置为默认调度策略 */
if (unlikely(p->sched_reset_on_fork)) {
/* static_prio 是从父进程继承下来的 */
/* 1.1 父进程是实时进程时调度策略也需要恢复(此时不能使用 rt_prio()来判断因为 prio可能被调整过了) */
if (p->policy == SCHED_FIFO || p->policy == SCHED_RR) {
p->policy = SCHED_NORMAL; /* 将子进程修改到普通调度策略 */
p->normal_prio = p->static_prio;/* 普通进程的普通优先级设置为父进程的静态优先级(临时调整为实时调度策略时不会修改static_prio,所以可以直接使用父进程的) */
}
/* 1.2 普通进程的 nice 的值小于0的情况(100~119) */
if (PRIO_TO_NICE(p->static_prio) < 0) {
p->static_prio = NICE_TO_PRIO(0);/* 重新设置静态优先级为120(DEFAULT_PRIO) */
p->normal_prio = p->static_prio; /* 普通进程的常规优先级等于静态优先级 */
set_load_weight(p); /* 重新设置普通进程的cpu调度权重 */
}
/*
* We don't need the reset flag anymore after the fork. It has
* fulfilled its duty:子进程无需设置调度恢复,因为子进程都还没创建完何来调度恢复
*/
p->sched_reset_on_fork = 0;
}
/*
* Make sure we do not leak PI boosting priority to the child.
* 2. 子进程的动态优先级被设置为父进程普通优先级(普通优先级在临时修改调度策略的过程中不会被修改),可以保证优先级临时提高不会传递到子进程.
*/
p->prio = current->normal_prio;
...
put_cpu();
}
修改普通进程优先级
setpriority
└── set_one_prio
└── set_user_nice
└── effective_prio
└── normal_prio
└──__normal_prio
void set_user_nice(struct task_struct *p, long nice)
{
int old_prio, delta, on_rq;
unsigned long flags;
struct rq *rq;
if (TASK_NICE(p) == nice || nice < -20 || nice > 19)
return;
rq = task_rq_lock(p, &flags);
/* 1. 判断为实时进程时设置只static_prio,此时nice设置不会对实时进程优先级有影响 */
if (task_has_rt_policy(p)) {
p->static_prio = NICE_TO_PRIO(nice);
goto out_unlock;
}
on_rq = p->on_rq;
if (on_rq)
dequeue_task(rq, p, 0);
/* 2. 根据nice值计算出普通进程静态优先级:120 + nice */
p->static_prio = NICE_TO_PRIO(nice);
/* 3. 重新设置了普通进程的静态优先级也需要修改cpu载入权重,不同的static_prio对应不同的载入权重 */
set_load_weight(p);
old_prio = p->prio;
/* 4. 获取普通进程的动态优先级(实时进程已经从1处退出),优先级在100~139正常范围的时prio = static_prio,
优先级临时被修改为 0~99范围时或本身就是实时进程时 nice 修改不生效 prio = prio */
p->prio = effective_prio(p);
delta = p->prio - old_prio;
if (on_rq) {
enqueue_task(rq, p, 0);
/* delta < 0表示优先级提升需要重新调度,delta > 0且运行则表示优先级调小了需要让出cpu也需要调度 */
if (delta < 0 || (delta > 0 && task_running(rq, p)))
resched_task(rq->curr);
}
out_unlock:
task_rq_unlock(rq, p, &flags);
}
static int effective_prio(struct task_struct *p)
{
/* 1. 获取常规优先级 */
p->normal_prio = normal_prio(p);
if (!rt_prio(p->prio)) /* 非实时进程时返回 normal_prio */
return p->normal_prio;
return p->prio; /* 实时进程或者是临时将时将优先级提升到 1~99 范围的则有效优先级就是prio */
}
static inline int normal_prio(struct task_struct *p)
{
int prio;
/* 1. 实时进程的动态优先级等于 100-1-实时优先级 */
if (task_has_rt_policy(p))
prio = MAX_RT_PRIO-1 - p->rt_priority;
else /* 普通进程的动态优先级等于静态优先级 p->static_prio */
prio = __normal_prio(p);
return prio;
}
注意:对于普通进程动态优先级被修改到 1~99的情况下设置 nice 来调整进程优先级时无效的。
修改实时程优先级
sched_setparam
└── do_sched_setscheduler
└── sched_setscheduler
└── __sched_setscheduler
└── __setscheduler
static int __sched_setscheduler(struct task_struct *p, int policy,
const struct sched_param *param, bool user)
{
int retval, oldprio, oldpolicy = -1, on_rq, running;
unsigned long flags;
const struct sched_class *prev_class;
struct rq *rq;
int reset_on_fork;
/* may grab non-irq protected spin_locks */
BUG_ON(in_interrupt());
recheck:
/* 1. 确认是仅修改优先级还是有可能也修改调度策略 */
if (policy < 0) {/* 负数时表示只修改参数(进程优先级),调度策略不修改 */
reset_on_fork = p->sched_reset_on_fork;
policy = oldpolicy = p->policy;
} else {
reset_on_fork = !!(policy & SCHED_RESET_ON_FORK); /* 检查是否设置了子进程创建时恢复默认策略 */
policy &= ~SCHED_RESET_ON_FORK;
if (policy != SCHED_FIFO && policy != SCHED_RR &&
policy != SCHED_NORMAL && policy != SCHED_BATCH &&
policy != SCHED_IDLE)
return -EINVAL;
}
/*
* Valid priorities for SCHED_FIFO and SCHED_RR are
* 1. MAX_USER_RT_PRIO-1, valid priority for SCHED_NORMAL,
* SCHED_BATCH and SCHED_IDLE is 0.
*/
if (param->sched_priority < 0 ||
(p->mm && param->sched_priority > MAX_USER_RT_PRIO-1) ||
(!p->mm && param->sched_priority > MAX_RT_PRIO-1))
return -EINVAL;
/* 2. 实时进程调度策略时优先级可以设置为 MAX_USER_RT_PRIO-1,非普通进程调度策略时只能设置优先级为0 */
if (rt_policy(policy) != (param->sched_priority != 0))
return -EINVAL;
/* 3. 检查进程是否有CAP_SYS_NICE的能力,如果没有则不能随意修改调度策略和优先级 */
if (user && !capable(CAP_SYS_NICE)) {
/* 3.1 在没有CAP_SYS_NICE能力时不能将调度策略修改为动态进程调度策略,不能将通过rt_priority将进程优先级提高 */
if (rt_policy(policy)) {
unsigned long rlim_rtprio =
task_rlimit(p, RLIMIT_RTPRIO);
/* can't set/change the rt policy */
if (policy != p->policy && !rlim_rtprio) /* 如果想将普通进程调度策略修改为实时进程调度策略会引发错误返回,只能从实时进程调度策略向下修改 */
return -EPERM;
/* can't increase priority */
if (param->sched_priority > p->rt_priority && /* rt_priority的值越大动态优先级值就越小,优先级就越高,此时不能将优先级提高否则会出错 */
param->sched_priority > rlim_rtprio)
return -EPERM;
}
if (p->policy == SCHED_IDLE && policy != SCHED_IDLE) { /* 不能将随意将空闲调度策略修改为其他策略 */
if (!can_nice(p, TASK_NICE(p)))
return -EPERM;
}
...
}
...
/* 4. 设置进程动态优先级,可能会发生动态优先级临时提高的情况 */
__setscheduler(rq, p, policy, param->sched_priority);
...
return 0;
}
static void __setscheduler(struct rq *rq, struct task_struct *p, int policy, int prio)
{
p->policy = policy;
/* 1. 设置动态优先级 */
p->rt_priority = prio;
/* 2. 通过rt_priority计算实时进程的普通优先级 */
p->normal_prio = normal_prio(p);
/* we are holding p->pi_lock already */
/* 3. 设置进程调度器使用的动态优先级,a.互斥量阻塞链表上没有其他任务时直接返回normal_prio,b.选择互斥量阻塞链表上任务的最高优先级进行优先级提升 */
p->prio = rt_mutex_getprio(p);
/* 4. 根据优先级重新设置调度类 */
if (rt_prio(p->prio))
p->sched_class = &rt_sched_class;
else
p->sched_class = &fair_sched_class;
/* 重新设置cpu载入权重 */
set_load_weight(p);
}
🌀路西法 的CSDN博客拥有更多美文等你来读。