在linux内核中 , 调度器(scheduler)扮演着至关重要的角色,决定了哪个进程将获得CPU的执行时间 。本文将深入剖析内核中调度器的代码实现,从入口函数开始,一步步分析如何选择下一个要执行的进程 。让我们一同揭开这个内核之谜 。
![Linux 内核调度器源码解析:从调度入口到挑选下一个进程](http://img.jiangsulong.com/231127/1644244449-0.jpg)
文章插图
调度器入口Linux调度器入口函数定义在kernel/sched/core.c中:
asmlinkage __visible void __sched schedule(void){// 获取当前任务结构体的指针struct task_struct *tsk = current;// 将任务提交到调度工作队列中sched_submit_work(tsk);// 进入调度循环 , 直到没有需要被调度的任务do {// 禁用抢占preempt_disable();// 调用实际的调度函数 __schedule,并传入调度策略参数 SM_NONE__schedule(SM_NONE);// 启用抢占,但不进行重新调度sched_preempt_enable_no_resched();} while (need_resched()); // 循环直到没有需要重新调度的任务// 更新工作队列中的任务状态sched_update_worker(tsk);}EXPORT_SYMBOL(schedule);
调度器的入口函数是schedule,首先获取当前任务结构体的指针,然后将任务提交到调度工作队列中,接着进入一个循环 , 该循环会禁用抢占,调用实际的调度函数__schedule , 并在循环结束后启用抢占 。循环会一直执行 , 直到没有需要重新调度的任务为止 。最后,函数会更新工作队列中任务的状态 。函数最后export导出schedule函数以供其他部分使用 。static void __sched __schedule(bool preempt){struct task_struct *prev, *next;unsigned long *switch_count;struct rq *rq;prev = current;rq = this_rq();switch_count = &prev->nivcsw;// 获取下一个要运行的进程next = pick_next_task(rq);// 切换到下一个进程context_switch(rq, prev, next, switch_count);// 如果需要抢占,启用抢占if (preempt)need_resched();}
} 这里,__schedule函数负责实际的调度操作 。首先 , 它获取了当前任务结构体的指针(prev)、运行队列(rq)以及切换计数器(switch_count) 。然后,通过调用pick_next_task函数,它选择下一个要运行的进程(next) 。最后,通过context_switch函数 , 它进行进程切换,将CPU控制权移交给下一个进程 。具体如何挑选下一个需要运行的进程,就要扒开pick_next_task函数 。
pick_next_task
/* * 选择下一个要运行的任务 。*/static inline struct task_struct *__pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf){const struct sched_class *class; // 定义调度类指针struct task_struct *p; // 定义任务结构体指针// 优化:如果前一个任务是公平调度类中的任务,且运行队列中的任务数与CFS队列中的任务数相等,// 则可以直接选择下一个公平类任务,因为其他调度类的任务无法抢占CPU 。if (likely(!sched_class_above(prev->sched_class, &fAIr_sched_class) &&rq->nr_running == rq->cfs.h_nr_running)) {p = pick_next_task_fair(rq, prev, rf); // 选择下一个公平调度类任务if (unlikely(p == RETRY_TASK)) // 如果选择任务失败,需要重新尝试goto restart;if (!p) {put_prev_task(rq, prev);p = pick_next_task_idle(rq); // 如果没有可运行任务 , 则选择下一个空转调度类任务}return p;}restart:put_prev_task_balance(rq, prev, rf); // 将前一个任务放回队列,进行重新平衡// 遍历所有调度类for_each_class(class) {p = class->pick_next_task(rq); // 选择下一个任务if (p)return p;}BUG(); // 如果没有可运行任务,引发BUG 。空转类应该始终有可运行的任务 。}
这段代码是用于选择下一个要运行的任务的函数 。首先 , 它检查是否可以优化选择下一个任务,如果前一个任务是公平调度类中的任务,并且运行队列中的任务数与CFS队列中的任务数相等,就可以直接选择下一个公平调度类任务 。如果选择任务失败,会重新尝试,然后如果没有可运行任务,将选择下一个空转调度类任务 。如果不满足优化条件,将会重新平衡队列,然后遍历所有的调度类,选择下一个任务 。如果没有可运行任务,将引发BUG , 因为空转类应该始终有可运行的任务 。struct task_struct *pick_next_task_fair(struct rq *rq, struct task_struct *prev, struct rq_flags *rf){struct cfs_rq *cfs_rq = &rq->cfs; // 获取CFS队列struct sched_entity *se; // 定义调度实体指针struct task_struct *p; // 定义任务结构体指针int new_tasks;again:// 如果没有可运行的公平调度任务,跳转到idle标签if (!sched_fair_runnable(rq))goto idle;#ifdef CONFIG_FAIR_GROUP_SCHED// 如果没有前一个任务,或者前一个任务不属于公平调度类 , 跳转到simple标签if (!prev || prev->sched_class != &fair_sched_class)goto simple;do {struct sched_entity *curr = cfs_rq->curr;// 如果当前任务存在if (curr) {// 如果当前任务在队列上,则更新其运行时间if (curr->on_rq)update_curr(cfs_rq);elsecurr = NULL;// 如果CFS队列的运行时间不正常,跳转到idle标签if (unlikely(check_cfs_rq_runtime(cfs_rq))) {cfs_rq = &rq->cfs;// 如果没有可运行任务 , 跳转到idle标签if (!cfs_rq->nr_running)goto idle;goto simple;}}// 选择下一个调度实体 , 并切换到相应的CFS队列se = pick_next_entity(cfs_rq, curr);cfs_rq = group_cfs_rq(se);} while (cfs_rq);// 获取与选定实体关联的任务结构体p = task_of(se);// 如果前一个任务不等于选定任务,进行任务切换if (prev != p) {struct sched_entity *pse = &prev->se;while (!(cfs_rq = is_same_group(se, pse))) {int se_depth = se->depth;int pse_depth = pse->depth;if (se_depth <= pse_depth) {put_prev_entity(cfs_rq_of(pse), pse);pse = parent_entity(pse);}if (se_depth >= pse_depth) {set_next_entity(cfs_rq_of(se), se);se = parent_entity(se);}}put_prev_entity(cfs_rq, pse);set_next_entity(cfs_rq, se);}goto done;simple:#endif// 如果有前一个任务,将其放回队列if (prev)put_prev_task(rq, prev);do {// 选择下一个调度实体,并切换到相应的CFS队列se = pick_next_entity(cfs_rq, NULL);set_next_entity(cfs_rq, se);cfs_rq = group_cfs_rq(se);} while (cfs_rq);// 获取与选定实体关联的任务结构体p = task_of(se);done: __maybe_unused;#ifdef CONFIG_SMP// 将下一个正在运行的任务移动到队列的前面list_move(&p->se.group_node, &rq->cfs_tasks);#endif// 如果启用高精度定时器,开始高精度定时if (hrtick_enabled_fair(rq))hrtick_start_fair(rq, p);// 更新不适合运行的任务状态update_misfit_status(p, rq);return p;idle:// 如果没有rf标志 , 返回NULLif (!rf)return NULL;// 尝试进行新的空闲平衡操作new_tasks = newidle_balance(rq, rf);// 如果新的平衡操作失败,返回RETRY_TASK标志if (new_tasks < 0)return RETRY_TASK;// 如果有新的可运行任务,回到again标签重新选择if (new_tasks > 0)goto again;// 如果队列即将变为空闲状态,检查是否需要更新时钟pelt的lost_idle_timeupdate_idle_rq_clock_pelt(rq);return NULL;}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Linux中Netstat命令最常用的五个用法
- Linux技巧:使用cURL将输出保存到文件
- Linux协程艺术:探秘ucontext函数族的神奇世界
- 深入Linux内核:探秘进程实现的神秘世界
- 构建基于Linux的物联网应用程序:传感器和数据处理
- Mac使用CLion连接Linux进行远程开发
- 如何使用 Linux Xargs 命令,看这篇就够了
- XXL-JOB真的要凉了?出现了一个王炸级别的分布式任务调度与计算框架?
- Linux 黑话解释:Linux 中的 Super 键是什么?
- 揭秘 Linux 调度策略与 CFS 调度算法:解锁内核的奥秘