切换调度类
Linux 调度流程分析(1)
切换调度类
用户可以通过 int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param)
函数来更改一个任务的调度类。
这个函数实际是通过系统调用 sched_setscheduler() 系统调用来实现的。该系统调用在 kernel/sched/core.c 中定义,系统调用原型为:
SYSCALL_DEFINE3(sched_setscheduler, pid_t, pid, int, policy, struct sched_param __user*, param)
该系统调用其实是对 int do_sched_setscheduler()
函数的封装。在这个函数中,系统首先找到 pid 对应的任务再通过 _sched_setscheduler()
函数将用户识别的 struct sched_param 参数转化成 struct sched_attr 格式送给 __sched_setscheduler()
去实际处理。
这几个函数的调用关系如下:
sched_setscheduler()
===> SYSCALL_DEFINE3(sched_setscheduler, pid_t, pid, int, policy, struct sched_param __user*, param)
===> do_sched_setscheduler()
===> sched_setscheduler()
===> _sched_setscheduler()
===> __sched_setscheduler()
int __sched_setscheduler(struct task_struct *p, const struct sched_attr * attr, bool user, bool pi)
函数是真正去更改任务调度的实现。
- 在这个函数中,会首先对参数进行检查:
- 检查新策略的有效性;
- 检查传入的标志位是否合法;
- 检查优先级是否超限;
- 检查新调度策略与新优先级是否匹配:
- 优先级 1-99 属于 rt 调度类;
- 优先级 0 代表 cfs 调度类;
- 优先级 -1 代表 dl 调度类;
- 普通用户权限检查:
- 不允许设置 schedutil kworker 的参数;
- 通过
security_task_setscheduler()
函数检查是否允许更改任务的调度策略; - 如果不具有 CAP_SYS_NICE 权限,还要有以下约束:
- 对于 cfs 调度类,只能降低 nice 值;
- 不能更改 rt 调度类的策略,且优先级不能超限
- 不能更改为 dl 调度策略;
- 对于 idle 调度策略的 cfs 调度类进程,可以将其提升为 normal 调度策略的进程;
- 普通进程只能修改与自己具有相同所有者的进程的调度策略;
- 普通进程无法修改 sched_reset_on_fork 的标志;
-
其次会对该任务所在的运行队列进行时间统计;
-
如果要更改到 rt/dl 调度类,则需要检查带宽设定是否正确,或者是否有足够的带宽来让其正常工作;
-
在设定优先级之前,需要先检查优先级翻转可能带来的问题,从而保证优先级设定之后不会带来优先级翻转的问题;
-
如果该任务在运行队列上,则需要调用切换之前的调度类的
dequeue()
函数先将该任务出队; -
如果该任务正在运行中,即是当前任务,则需要调用切换之前的调度类的
put_prev_task()
函数,在任务切换调度类之前做一些处理工作,例如统计时间信息等; -
通过
__setscheduler_params() & __setscheduler_prio()
函数进行调度类、调度策略和优先级的设定,此时任务已经从前一个调度类切换到了一个新的调度类上; -
设定任务的 uclamp 限制;
-
更改调度类后,任务的状态要和切换之前保持一直:
- 原来在运行队列上,切换后要通过新调度类的
enqueue()
函数将其入队; - 如果原来在运行中,则切换后要通过新调度类的
set_next_task()
函数将其设定成新调度类运行队列上的当前任务;
- 原来在运行队列上,切换后要通过新调度类的
-
如果调度类改变,则通过调用前一个调度类的
switched_from()
函数和新调度类的switched_to()
函数来通知前一个调度类和后一个调度类做一些处理工作,例如抢占等; -
如果没有更改调度类,只是更改了优先级,则通过调度类的
prio_change()
函数通知调度类; -
最后进行一次负载均衡;