sched_init
void __init sched_init(void)
{
unsigned long ptr = 0;
int i;
/* Make sure the linker didn't screw up */
BUG_ON(&idle_sched_class + 1 != &fair_sched_class ||
&fair_sched_class + 1 != &rt_sched_class ||
&rt_sched_class + 1 != &dl_sched_class);
#ifdef CONFIG_SMP
BUG_ON(&dl_sched_class + 1 != &stop_sched_class);
#endif
wait_bit_init();
// 初始化等待队列
...
init_rt_bandwidth(&def_rt_bandwidth, global_rt_period(), global_rt_runtime());
// 初始化(控制全局的)rt使用带宽
init_dl_bandwidth(&def_dl_bandwidth, global_rt_period(), global_rt_runtime());
// 初始化(控制全局的)dl使用带宽
...
for_each_possible_cpu(i) {
// 遍历所有可用的cpu,为每个cpu初始化相关结构
struct rq *rq;
// 每个CPU的主要运行队列数据结构
rq = cpu_rq(i);
raw_spin_lock_init(&rq->lock);
rq->nr_running = 0;
rq->calc_load_active = 0;
rq->calc_load_update = jiffies + LOAD_FREQ;
init_cfs_rq(&rq->cfs);
// 初始化cfs运行队列
init_rt_rq(&rq->rt);
// 初始化rt运行队列(Real-Time class)
init_dl_rq(&rq->dl);
// 初始化dl运行队列(Deadline class)
...
rq->rt.rt_runtime = def_rt_bandwidth.rt_runtime;
// global_rt_runtime(0.95s)
hrtick_rq_init(rq);
// 内核定时器初始化(高分辨率定时器时间片,在禁用中断的情况下从hardirq上下文运行)
atomic_set(&rq->nr_iowait, 0);
}
set_load_weight(&init_task, false);
// 设置init_task权重
mmgrab(&init_mm);
// init_mm即使在完成所属任务后也不会被释放,并且之前必须使用mmget_not_zero访问它
enter_lazy_tlb(&init_mm, current);
// init_mm切换到内核线程,enter_lazy_tlb是来自调度程序的提示,表示我们正在输入内核线程或其他没有mm的上下文
init_idle(current, smp_processor_id());
// 为给定的CPU设置一个空闲线程,从技术上讲,schedule()不应该从这个线程(current)调用,当此运行队列变为“空闲”时,我们只是重新开始运行
calc_load_update = jiffies + LOAD_FREQ;
...
psi_init();
// 初始化统计管理结构
init_uclamp();
// 初始化uclamp
scheduler_running = 1;
}
? sched_init函数用于初始化每个可用的cpu队列,每个CPU维护自己的rq数据结构,CPU之间使用ipi通信(SMP模式下)。
schedule
asmlinkage __visible void __sched schedule(void)
{
struct task_struct *tsk = current;
sched_submit_work(tsk);
? schedule函数用于强制申请进程调度,进入sched_submit_work(tsk)函数:
static inline void sched_submit_work(struct task_struct *tsk)
{
if (!tsk->state) //如果当前进程为空闲状态,直接返回
return;
/*
* If a worker went to sleep, notify and ask workqueue whether
* it wants to wake up a task to maintain concurrency.
* As this function is called inside the schedule() context,
* we disable preemption to avoid it calling schedule() again
* in the possible wakeup of a kworker and because wq_worker_sleeping()
* requires it.
*/
if (tsk->flags & (PF_WQ_WORKER | PF_IO_WORKER)) { // 如果当前进程有工作队列任务或IO队列任务
preempt_disable(); // 内核抢占计数加1,防止被(调度)执行
if (tsk->flags & PF_WQ_WORKER)
wq_worker_sleeping(tsk); // 如果工作队列任务没有运行或者工作队列任务没有任务,直接返回,或者唤醒一个空闲的进程(工作池为运行状态并且列表不为空)
else
io_wq_worker_sleeping(tsk); //如果IO队列任务处于活跃状态或运行状态,直接返回,或者唤醒管理者创建一个任务
preempt_enable_no_resched(); // 内核抢占计数减1,但不立即抢占式调度
}
if (tsk_is_pi_blocked(tsk)) // 检测tsk的死锁检测器是否为空,不为空直接返回
return;
/*
* If we are going to sleep and we have plugged IO queued,
* make sure to submit it to avoid deadlocks.
*/
if (blk_needs_flush_plug(tsk))
blk_schedule_flush_plug(tsk); // 异步执行unplug,最终定时器定时触发kblockd将request下发给驱动程序
}
? sched_submit_work用于处理/提交当前进程的队列进展,防止后续出现死锁情况,返回schedule函数:
do {
preempt_disable(); // 内核抢占计数加1,防止被(调度)执行
__schedule(false);
? 进入__schedule(false)函数:
static void __sched notrace __schedule(bool preempt)
{
struct task_struct *prev, *next;
unsigned long *switch_count;
unsigned long prev_state;
struct rq_flags rf;
struct rq *rq;
int cpu;
cpu = smp_processor_id(); // 获取当前的cpu id
rq = cpu_rq(cpu); // 获取rq数据结构
prev = rq->curr; // 当前cpu标记为prev
schedule_debug(prev, preempt);
if (sched_feat(HRTICK))
hrtick_clear(rq);
local_irq_disable(); // 设置当前CPU的中断屏蔽位
rcu_note_context_switch(preempt); // 更新rcu状态,记录本cpu经过qs
...
rq_lock(rq, &rf); //为rq加锁
smp_mb__after_spinlock();
/* Promote REQ to ACT */
rq->clock_update_flags <<= 1;
update_rq_clock(rq);
switch_count = &prev->nivcsw;
prev_state = prev->state; // 获得前一个任务状态
if (!preempt && prev_state) {
if (signal_pending_state(prev_state, prev)) { // 如果任务有TASK_INTERRUPTIBLE标识或者挂起为真
prev->state = TASK_RUNNING;
} else {
...
deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK); // cpu的队列中删除任务
...
}
next = pick_next_task(rq, prev, &rf); // 获取下一个的进程(优先级相等)
clear_tsk_need_resched(prev); // 清理上一个进程的TIF_NEED_RESCHED标志
clear_preempt_need_resched(); // PREEMPT_OFFSET | PREEMPT_NEED_RESCHED
if (likely(prev != next)) {
rq->nr_switches++;
/*
* RCU users of rcu_dereference(rq->curr) may not see
* changes to task_struct made by pick_next_task().
*/
RCU_INIT_POINTER(rq->curr, next); // cpu的rq结构当前(指向)为下一个进程
++*switch_count;
psi_sched_switch(prev, next, !task_on_rq_queued(prev)); // 统计调度为下一个进程
trace_sched_switch(preempt, prev, next); // 捕获下一个进程
/* Also unlocks the rq: */
rq = context_switch(rq, prev, next, &rf); // 从当前进程切换到下一个进程
} else {
rq->clock_update_flags &= ~(RQCF_ACT_SKIP|RQCF_REQ_SKIP);
rq_unlock_irq(rq, &rf);
}
balance_callback(rq); // head = rq->balance_callback; next = head->next; ? rq->balance_callback = rq->balance_callback->next;
? __schedule函数实现了当前进程到下一个进程的调转,及当前进程的状态保存,回到schedule函数:
sched_preempt_enable_no_resched(); // 内核抢占计数减1,但不立即抢占式调度
} while (need_resched()); // 如果thread_info->flags包含TIF_NEED_RESCHED标志,表示需要执行调度
sched_update_worker(tsk); // 恢复之前睡眠的队列任务
}
? schedule调用后至少执行一次__schedule函数,之后通过TIF_NEED_RESCHED标志确定是否调度。
|