linux内核—进程调度时机
Posted 为了维护世界和平_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux内核—进程调度时机相关的知识,希望对你有一定的参考价值。
目录
调度时机的分类
- 主动调度
- 周期调度
- 唤醒进程的时候
- 创建进程的时候
主动调度
进程在用户模式下运行,无法直接调用schedule(),只能通过系统调用进入内核模式,如果系统调用需要等待某个资源,如互斥锁或信号量,就会把进程状态设置为睡眠状态,调用schedule()函数来调度进程。
内核也可以调用sched_yield让出处理器,这种情况不会睡眠。
三种主动调度
1、直接调用schedule函数
2、调用有条件的调度函数cond_resched()。非抢占内核,函数cond_resched判断当前进程是否设置了需要重新调度标志,如果设置了,就重新调度。在抢占内核中,此函数为空,没有作用
3、如果需要等待某个资源,那么把进程的状态设置为睡眠状态,然后调用schedule
周期调度
避免进程不主动让出处理器。内核依靠周期性的时钟中断,夺回处理器控制权。时钟中断处理程序检查当前进程的执行时间有没有超过限额,如果超过了,设置需要重新调度的标志。
当时钟中断处理程序准备把处理器还给被打断的进程时,如果被打断的进程在用户模式下运行,就检查有没有需要设置重新调度标志,如果设置了,调用schedule函数调度其他进程。
1)限期调度类的周期调度
task_tick_dl->update_curr_dl
2)实时调度类的周期调度 task_tick_rt
static void task_tick_rt(struct rq *rq, struct task_struct *p, int queued)
struct sched_rt_entity *rt_se = &p->rt;
update_curr_rt(rq);//更新时间
update_rt_rq_load_avg(rq_clock_pelt(rq), rq, 1);
watchdog(rq, p);
/*
* RR tasks need a special form of timeslice management.
* FIFO tasks have no timeslices.
*/
//先进先出的进程调度不是轮流调度,直接退出
if (p->policy != SCHED_RR)
return;
//当前是RR进程,则减少时间片,没有用完时间片,那么返回
if (--p->rt.time_slice)
return;
//时间片用完,重置时间片100ms
p->rt.time_slice = sched_rr_timeslice;//(100 * HZ / 1000)
/*
* Requeue to the end of queue if we (and all of our ancestors) are not
* the only element on the queue
*/
//如果该进程不是唯一进程,则排到队尾。为啥强调不是唯一进程?
for_each_sched_rt_entity(rt_se)
if (rt_se->run_list.prev != rt_se->run_list.next)
requeue_task_rt(rq, p, 0);
resched_curr(rq);
return;
3)公平调度类的周期调度 task_tick_fair
static void
entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
update_curr(cfs_rq);
update_load_avg(cfs_rq, curr, UPDATE_TG);
update_cfs_group(curr);
//如果公平调度队列的进程数量超过1,那么调用check_preempt_tick
if (cfs_rq->nr_running > 1)
check_preempt_tick(cfs_rq, curr);
static void
check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
unsigned long ideal_runtime, delta_exec;
struct sched_entity *se;
s64 delta;
ideal_runtime = sched_slice(cfs_rq, curr);
//当前调度实体的运行时间超过了理想的运行时间
delta_exec = curr->sum_exec_runtime - curr->prev_sum_exec_runtime;
if (delta_exec > ideal_runtime)
resched_curr(rq_of(cfs_rq));//重新设置需要重新调度的标志位
clear_buddies(cfs_rq, curr);
return;
//当前调度实体的运行时间小于最小粒度,退出
if (delta_exec < sysctl_sched_min_granularity)
return;
//公平调度的实体
se = __pick_first_entity(cfs_rq);
//当前调度实体的虚拟运行时间和公平运行队列中第一个调度实体的虚拟运行时间差值
delta = curr->vruntime - se->vruntime;
if (delta < 0)
return;
//差值大于理想的运行时间
if (delta > ideal_runtime)
resched_curr(rq_of(cfs_rq));//设置重新调度标志
4)中断返回时调度
如果进程在用户模式下运行,那么中断抢占时,ARM64架构的中断处理程序入口e10_irq。
中断处理程序处理完后,跳转到ret_to_user以返回用户模式。
Ret_to_user判断返回进程描述符的成员thread_info.flags有没有设置标志位集合_TIF_WORK_MASK
如果设置了,则跳转到work_pending,标号调用函数do_notify_resume。
唤醒进程时抢占
wake_up_process->try_to_wake_up->check_preempt_curr
check_preempt_wakeup()
- 如果被唤醒的进程和当前进程属于相同的调度类,那么调度类的check_preempt_curr方法以检查是否可以抢占当前进程
- 如果被唤醒的进程所属调度类的优先级高于当前进程所属的调度类优先级,那么给当前进程设置需要重新调度的标志。
创建进程时抢占
系统调用fork,clone时,新进程可能抢占当前进程,使用内核函数kernel_thread创建新内核线程的时候,新的内核线程可能抢占当前进程。
内核抢占
内核抢占,指当前进程在内核模式下运行的时候可以被其他进程抢占,打开宏CONFIG_PREEMPT。如果不设置,不会抢占,现象是,如果一个进程在内核模式下运行很长时间,将会导致交互进程等到很长时间,响应慢。内核抢占就是解决这个问题。
- 个人计算机在桌面系统要求响应速度快,适合使用抢占式内核;
- 服务器要求业务的吞吐量高,适合使用非抢占式内核。
进程的thread_info结构体有一个类型为int的preempt_count,抢占计数器。
进程在内核模式下运行,可以调用preempt_disable禁止其他进程抢占
调用preempt_enable把抢占计数器部分加1
local_bh_disable禁止软中断抢占,把抢占计数器的软中断计数部分加2;__do_softirq()在执行软中断之前把软中断计数部分加1
中断处理程序会把抢占计数的硬中断计数部分加1,表示在硬中断上下文里面。
不可屏蔽中断的处理程序会把抢占计数器的不可屏蔽中断计数部分和硬中断计数部分分别加1,表示在不可屏蔽中断上下文里面。
进程在内核模式运行的时候,如果抢占计数值不是0,那么其他进程不能抢占。如果禁止软中断抢占,那么同时也禁止了其他进程抢占。
- 开启内核抢占 preempt_enable()
- 开启软中断抢占 local_hb_enable()
- 释放自旋锁时抢占 spin_unlock
- 中断处理程序返回内核模式抢占
Arm64中断处理入口的函数el1_irq 中断处理程序处理完成后,如果进程的抢占计数器是0,并且设置了重新调度的标志,那么调用函数el1_preempt,函数调用preempt_schedule_irq以执行抢占调度。如果被选中的进程也设置了需要调度的重新调度的标志位,那么继续执行抢占调度。
参考
https://course.0voice.com/v1/course/intro?courseId=2&agentId=0
以上是关于linux内核—进程调度时机的主要内容,如果未能解决你的问题,请参考以下文章
Linux内核线程kernel thread详解--Linux进程的管理与调度
Linux用户抢占和内核抢占详解(概念, 实现和触发时机)--Linux进程的管理与调度(二十)