news 2026/11/1 17:36:29

pick_next_task()

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
pick_next_task()

pick_next_task()是 Linux 内核调度器的 **“决策核心”,它的核心作用是从当前 CPU 的运行队列(runqueue)中,按照调度类的优先级顺序,挑选出下一个最应该获得 CPU 执行权的进程 **,是schedule()函数中最关键的 “选进程” 步骤。

该函数位于kernel/sched/core.c,本身是一个架构无关的通用函数,最终会调用各个调度类的专属pick_next_task方法完成具体挑选逻辑。

函数核心作用

  1. 遵循调度类优先级排序:按照内核预设的调度类优先级,从高到低依次检查各调度类是否有可运行进程(硬实时 > 软实时 > 普通进程 > 空闲进程)。
  2. 委托专属调度类挑选:对每个有可运行进程的调度类,调用其自身实现的pick_next_task方法,完成该类内的最优进程挑选。
  3. 返回最终候选进程:一旦从某个调度类中挑选到有效进程,立即返回该进程,确保高优先级调度类的进程优先获得 CPU。
  4. 兜底选择空闲进程:若所有调度类都无可用进程,最终返回当前 CPU 的idle进程(空循环占用 CPU,避免 CPU 处于无任务状态)。

函数原型(以 Linux 5.10 为例)

static inline struct task_struct * pick_next_task(struct rq *rq, struct task_struct *prev, bool preempt)
  • 参数:
    • rq:指向当前 CPU 的运行队列(runqueue),所有可运行进程都挂载在此队列中。
    • prev:指向当前即将被换下 CPU 的进程(上一个运行进程)。
    • preempt:标记是否为抢占式调度(true表示高优先级进程抢占当前进程,false表示当前进程主动放弃 CPU)。
  • 返回值:指向挑选出的下一个待运行进程的task_struct指针(绝不会返回NULL,最坏返回idle进程)。

核心实现流程(基于 Linux 5.10)

pick_next_task()的核心逻辑是「高优先级调度类优先遍历 + 专属调度类内挑选」,简化版代码如下:

static inline struct task_struct * pick_next_task(struct rq *rq, struct task_struct *prev, bool preempt) { const struct sched_class *class; struct task_struct *next; // 快速路径优化:优先检查上一个进程的调度类(大部分场景下,下一个进程仍属于同一调度类) // 减少遍历所有调度类的开销,提升调度效率 if (likely(prev->sched_class == &fair_sched_class || prev->sched_class == &idle_sched_class)) { // 1. 先检查高优先级调度类(DL > RT) class = sched_class_highest; // 指向优先级最高的调度类(DL 硬实时) while (class != &fair_sched_class) { // 遍历到 CFS 调度类为止 // 调用该调度类的 pick_next_task 方法,挑选进程 next = class->pick_next_task(rq, prev, preempt); if (next) // 若挑选到有效进程,直接返回 return next; // 切换到下一个优先级更低的调度类 class = class->next; } // 2. 检查 CFS 普通调度类(大部分进程属于此类) next = fair_sched_class.pick_next_task(rq, prev, preempt); if (next) return next; // 3. 若快速路径无结果,进入慢速路径(遍历所有调度类) class = sched_class_highest; } else { // 慢速路径:上一个进程属于特殊调度类,直接遍历所有调度类 class = prev->sched_class->next; } // 慢速路径:从最高优先级调度类开始,依次遍历所有调度类 for (; class; class = class->next) { next = class->pick_next_task(rq, prev, preempt); if (next) return next; } // 兜底:无任何可运行进程,返回当前 CPU 的 idle 进程 return idle_task(rq); }
  1. 调度类优先级顺序 :内核预设的调度类优先级从高到低为:DL_SCHED_CLASS(硬实时调度类)>RT_SCHED_CLASS(软实时调度类)>CFS_SCHED_CLASS(完全公平调度类,普通进程默认)>IDLE_SCHED_CLASS(空闲调度类)

    • DL:用于要求绝对截止时间的任务(如工业控制、航空航天),优先级最高,一旦有可运行进程,直接抢占其他所有进程。
    • RT:用于要求快速响应的任务(如音频、视频播放),优先级高于普通进程,支持 99 个实时优先级(0-98,数值越大优先级越高)。
    • CFS:用于普通用户态进程(如bash、chrome),通过vruntime(虚拟运行时间)保证所有进程公平获得 CPU 时间。
    • IDLE:仅当无其他可运行进程时运行,负责执行 CPU 空闲时的低功耗操作。
  2. 快速路径与慢速路径

    • 快速路径:大部分场景下,当前进程是CFS或IDLE类进程,下一个进程也大概率属于CFS类,优先遍历DL、RT、CFS,减少遍历开销,提升调度效率(占调度场景的 90% 以上)。
    • 慢速路径:当当前进程是DL或RT类特殊进程时,直接遍历所有调度类,确保不遗漏高优先级进程。
  3. 专属调度类的pick_next_task:实现pick_next_task()本身只是 “调度员”,真正的 “挑选逻辑” 由各调度类自身实现:

    • CFS 调度类:核心逻辑是从运行队列的红黑树中,找到vruntime(虚拟运行时间)最小的进程(红黑树的最左节点),保证 “先到先得” 的公平性。关键代码逻辑:
      struct task_struct *pick_next_task_fair(struct rq *rq, struct task_struct *prev, bool preempt) { struct sched_entity *se; struct task_struct *next; // 找到红黑树中 vruntime 最小的调度实体 se = pick_next_entity(rq, &rq->cfs); // 转换为 task_struct 进程指针 next = task_of(se); return next; }
    • RT 调度类:核心逻辑是遍历实时优先级链表(0-98),从最高优先级链表中找到第一个可运行进程(链表头节点),保证高优先级实时进程优先执行。关键代码逻辑:
      struct task_struct *pick_next_task_rt(struct rq *rq, struct task_struct *prev, bool preempt) { struct rt_prio_array *array = &rq->rt.rt_prio_array; int prio; // 从最高优先级(98)开始遍历链表 for (prio = MAX_RT_PRIO - 1; prio >= 0; prio--) { if (!list_empty(&array->queue[prio])) { // 返回链表中的第一个进程 return list_first_entry(&array->queue[prio], struct task_struct, rt_entry); } } return NULL; }
    • IDLE 调度类:直接返回当前 CPU 的idle进程,无复杂挑选逻辑。
  4. 兜底返回idle进程 :若所有调度类都无可用进程,最终返回idle_task(rq)(当前 CPU 的空闲进程),该进程会执行空循环或低功耗指令(如 ARM64 的wfe),直到有新进程被唤醒加入运行队列。

函数的关联(schedule()/wake_up_*)

  1. wake_up_new_task()/wake_up_process():将进程加入对应调度类的运行队列(红黑树 / 优先级链表),为pick_next_task()提供 “候选进程池”。
  2. pick_next_task():在schedule()执行过程中,从 “候选进程池” 中挑选最优进程,是schedule()的 “决策核心”。
  3. schedule():接收pick_next_task()返回的候选进程,完成上下文切换,让该进程获得 CPU 执行权。

简单说:wake_up_*是 “把选手送进对应赛道”,pick_next_task()是 “从所有赛道中挑选最有资格上场的选手”,schedule()是 “让选手上场比赛”。

架构相关差异(以 ARM64 为例)

pick_next_task()的核心逻辑(调度类遍历、进程挑选)是架构无关的,与架构相关的差异仅体现在:

  1. 运行队列(rq)的获取:ARM64 异构架构(大小核)中,每个 CPU 对应独立的rq,pick_next_task()仅操作当前 CPU 的rq,确保进程在指定核组内被挑选。
  2. idle进程的实现:ARM64 的idle进程会根据 CPU 型号(大核 / 小核)执行不同的低功耗指令(如wfi/wfe),pick_next_task()仅负责返回该idle进程,不参与低功耗逻辑。
  3. 缓存优化:ARM64 中,pick_next_task()挑选进程时,会间接优先选择与当前进程共享 L2/L3 缓存的进程(由 CFS 调度类的vruntime优化实现),减少缓存失效开销。

常见问题与注意事项

  1. 实时进程饥饿普通进程:DL/RT调度类优先级高于CFS,若实时进程长期占用 CPU,会导致普通CFS进程 “饥饿”(无法获得 CPU 执行权),需合理设置实时进程的运行时间。
  2. CFS 红黑树失衡:若频繁创建 / 销毁CFS进程,可能导致红黑树失衡,影响pick_next_task()的挑选效率,内核已通过优化红黑树插入 / 删除逻辑缓解该问题。
  3. 异构架构调度不均:ARM64 大小核架构中,若未合理配置进程的 CPU 亲和性,pick_next_task()可能将 CPU 密集型进程调度到小核,导致性能瓶颈,需通过sched_setaffinity()绑定进程到大核。

总结

  1. pick_next_task()是调度器的决策核心,核心职责是按调度类优先级从运行队列中挑选最优下一个进程。
  2. 核心逻辑分为快速路径(优先遍历 CFS 相关调度类)和慢速路径(遍历所有调度类),兼顾效率与完整性。
  3. 具体挑选逻辑由各调度类专属实现(CFS 选最小vruntime,RT 选最高优先级),最终兜底返回idle进程。
  4. 它是schedule()与各调度类之间的桥梁,不负责上下文切换,仅负责 “选进程”。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 5:06:46

内存泄漏测试全流程解析:从工具到实战,软件测试从业者必读指南

内存泄漏的隐蔽性与测试价值 在软件测试领域,内存泄漏如同潜伏的"内存吸血鬼",它不会导致程序崩溃,却会持续消耗系统资源,最终引发性能下降甚至系统瘫痪。对于测试从业者而言,掌握内存泄漏检测技术不仅能提…

作者头像 李华
网站建设 2026/10/31 5:45:48

【异常】解决 Windows OpenSSH “Bad owner or permissions“ 权限问题

在 Windows 环境下使用 OpenSSH(例如 Git Bash、CMD 或 PowerShell)连接远程服务器时,经常会遇到权限校验失败的问题。本文记录了该错误的现象、原因分析及通过命令行快速修复的方法。 一、报错内容 在终端执行 SSH 连接命令时,出现如下错误提示,导致连接被中断: C:\Us…

作者头像 李华
网站建设 2026/10/30 20:34:10

将PTC许可证管理深度融入企业内控与合规体系

PTC许可证管理深度融入企业内控与合规体系,是时候行动了 在2025年,企业合规已成为不可忽视的核心议题,是对于那些从事需合规性审查的行业,比如化工、医药、食品、制造等,PTC许可证管理的深度融入内控体系,…

作者头像 李华
网站建设 2026/11/1 11:10:01

JVM垃圾回收(GC)核心原理全解析(从垃圾判断到调优实战)

JVM垃圾回收(GC)核心原理全解析(从垃圾判断到调优实战) 一、GC 的核心本质 GC(Garbage Collection)是 JVM 自动管理内存的核心机制,核心目标是: 识别并回收堆内存中 “不再被使用…

作者头像 李华
网站建设 2026/10/7 10:42:03

SuperMap GIS基础产品FAQ集锦(20260202)

一、SuperMap iServer 问题1:iServer服务列表中没有“空间分析服务”这一项,咨询如何添加或启用。 11.3.0 【解决办法】使用工作空间发布空间分析服务即可。 问题2:在x64 Linux系统上部署iServer 12.0.0版本,启动时报错&#x…

作者头像 李华