当前位置: 首页 > news >正文

Linux内核任务调度时机总结

一、调度机制概述

Linux内核的任务调度主要基于两个核心机制:

  1. 直接调用schedule():主动让出CPU
  2. 设置TIF_NEED_RESCHED标志:延迟调度,在合适的时机检查该标志并调用schedule()

核心调度函数__schedule()(位于kernel/sched/core.c)的注释清楚地说明了驱动调度器进入的主要方式:

/* * __schedule() is the main scheduler function. * * The main means of driving the scheduler and thus entering this function are: * * 1. Explicit blocking: mutex, semaphore, waitqueue, etc. * * 2. TIF_NEED_RESCHED flag is checked on interrupt and userspace return * paths. For example, see arch/x86/entry_64.S. * * To drive preemption between tasks, the scheduler sets the flag in timer * interrupt handler sched_tick(). * * 3. Wakeups don't really cause entry into schedule(). They add a * task to the run-queue and that's it. */

二、主动调度(Voluntary Scheduling)

1. schedule() 直接调用

位置:kernel/sched/core.c

asmlinkage __visiblevoid__schedschedule(void){structtask_struct*tsk=current;sched_submit_work(tsk);do{preempt_disable();__schedule(SM_NONE);sched_preempt_enable_no_resched();}while(need_resched());sched_update_worker(tsk);}

触发场景:

  • 任务主动阻塞(等待资源)
  • 内核同步原语(mutex, semaphore, completion等)
  • 等待队列操作

2. sched_yield() 系统调用

位置:kernel/sched/syscalls.c

SYSCALL_DEFINE0(sched_yield){do_sched_yield();return0;}staticvoiddo_sched_yield(void){structrq_flagsrf;structrq*rq;rq=this_rq_lock_irq(&rf);schedstat_inc(rq->yld_count);current->sched_class->yield_task(rq);preempt_disable();rq_unlock_irq(rq,&rf);sched_preempt_enable_no_resched();schedule();// 直接调用调度器}

触发条件: 用户空间调用sched_yield()系统调用,主动让出CPU。

3. cond_resched() 条件调度

位置:kernel/sched/core.c

int__sched__cond_resched(void){if(should_resched(0)&&!irqs_disabled()){preempt_schedule_common();return1;}...return0;}

触发条件:

  • 仅在非抢占式内核(CONFIG_PREEMPTION=n)中有效
  • 内核代码显式调用cond_resched()检查是否需要调度
  • 常用于长循环中提供调度点

4. 睡眠/阻塞导致的调度

示例: mutex_lock
位置:kernel/locking/mutex.c

// 在mutex_lock中,当无法获取锁时set_current_state(state);for(;;){if(__mutex_trylock(lock))gotoacquired;...schedule_preempt_disabled();// 调用调度器...}

触发场景:

  • 互斥锁竞争
  • 信号量操作
  • 等待队列(waitqueue)
  • 完成量(completion)
  • IO等待

三、抢占式调度(Preemptive Scheduling)

1. 用户抢占(User Preemption)

触发时机: 从内核返回用户空间时检查TIF_NEED_RESCHED标志

位置:kernel/entry/common.c

__always_inlineunsignedlongexit_to_user_mode_loop(structpt_regs*regs,unsignedlongti_work){while(ti_work&EXIT_TO_USER_MODE_WORK){local_irq_enable_exit_to_user(ti_work);if(ti_work&(_TIF_NEED_RESCHED|_TIF_NEED_RESCHED_LAZY))schedule();// 返回用户空间前调度...ti_work=read_thread_flags();}returnti_work;}

调用路径:

syscall_exit_to_user_mode() -> __syscall_exit_to_user_mode_work() -> exit_to_user_mode_prepare() -> exit_to_user_mode_loop() // 检查并调度

2. 内核抢占(Kernel Preemption)

前提: 内核配置了CONFIG_PREEMPTION

2.1 preempt_schedule() - preempt_enable时抢占

位置:kernel/sched/core.c

/* * This is the entry point to schedule() from in-kernel preemption * off of preempt_enable. */asmlinkage __visiblevoid__sched notracepreempt_schedule(void){/* * If there is a non-zero preempt_count or interrupts are disabled, * we do not want to preempt the current task. Just return.. */if(likely(!preemptible()))return;preempt_schedule_common();}

触发机制:
位置:include/linux/preempt.h

#definepreempt_enable()\do{\barrier();\if(unlikely(preempt_count_dec_and_test()))\__preempt_schedule();\// 抢占点barrier();\}while(0)

preempt_count减为0且TIF_NEED_RESCHED被设置时,触发抢占调度。

2.2 preempt_schedule_irq() - 中断返回内核态时抢占

位置:kernel/sched/core.c

asmlinkage __visiblevoid__schedpreempt_schedule_irq(void){enumctx_stateprev_state;/* Catch callers which need to be fixed */BUG_ON(preempt_count()||!irqs_disabled());prev_state=exception_enter();do{preempt_disable();local_irq_enable();__schedule(SM_PREEMPT);local_irq_disable();sched_preempt_enable_no_resched();}while(need_resched());exception_exit(prev_state);}

调用位置:kernel/entry/common.c

voidraw_irqentry_exit_cond_resched(void){if(!preempt_count()){rcu_irq_exit_check_preempt();if(need_resched())preempt_schedule_irq();// 中断返回时抢占}}

触发条件:

  • 从中断返回内核态
  • preempt_count为0
  • TIF_NEED_RESCHED标志被设置

四、时钟中断触发的调度

1. sched_tick() - 时钟滴答处理

位置:kernel/sched/core.c

voidsched_tick(void){intcpu=smp_processor_id();structrq*rq=cpu_rq(cpu);structtask_struct*donor;structrq_flagsrf;...rq_lock(rq,&rf);donor=rq->donor;update_rq_clock(rq);...// 调用调度类的task_tick方法donor->sched_class->task_tick(rq,donor,0);...}

调用路径:

timer_interrupt -> update_process_times() // kernel/time/timer.c -> sched_tick()

2. CFS调度类的task_tick_fair()

位置:kernel/sched/fair.c

staticvoidtask_tick_fair(structrq*rq,structtask_struct*curr,intqueued){structcfs_rq*cfs_rq;structsched_entity*se=&curr->se;for_each_sched_entity(se){cfs_rq=cfs_rq_of(se);entity_tick(cfs_rq,se,queued);}...}

entity_tick()调用update_curr()检查时间片:
位置:kernel/sched/fair.c

staticvoidupdate_curr(structcfs_rq*cfs_rq){...if(cfs_rq->nr_queued==1)return;if(resched||did_preempt_short(cfs_rq,curr)){resched_curr_lazy(rq);// 设置重调度标志clear_buddies(cfs_rq,curr);}}

3. RT调度类的task_tick_rt()

位置:kernel/sched/rt.c

staticvoidtask_tick_rt(structrq*rq,structtask_struct*p,intqueued){...// RR任务的时间片管理if(p->policy!=SCHED_RR)return;if(--p->rt.time_slice)return;p->rt.time_slice=sched_rr_timeslice;// 时间片用完,重新排队并设置重调度标志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;}}}

五、睡眠/唤醒导致的调度

1. 进程唤醒 - try_to_wake_up()

位置:kernel/sched/core.c

inttry_to_wake_up(structtask_struct*p,unsignedintstate,intwake_flags){...// 将任务加入运行队列// 如果被唤醒的任务优先级更高,触发抢占if(p->sched_class->task_woken)p->sched_class->task_woken(rq,p);...}

wake_up_process():
位置:kernel/sched/core.c

intwake_up_process(structtask_struct*p){returntry_to_wake_up(p,TASK_NORMAL,0);}

2. 唤醒抢占检查 - wakeup_preempt()

位置:kernel/sched/core.c

voidwakeup_preempt(structrq*rq,structtask_struct*p,intflags){structtask_struct*donor=rq->donor;if(p->sched_class==donor->sched_class)donor->sched_class->wakeup_preempt(rq,p,flags);elseif(sched_class_above(p->sched_class,donor->sched_class))resched_curr(rq);// 高优先级调度类,立即抢占...}

3. CFS唤醒抢占检查 - check_preempt_wakeup_fair()

位置:kernel/sched/fair.c

staticvoidcheck_preempt_wakeup_fair(structrq*rq,structtask_struct*p,intflags){...// 检查新唤醒的任务是否应该抢占当前任务if(pick_eevdf(cfs_rq)==pse)gotopreempt;return;preempt:resched_curr_lazy(rq);// 设置重调度标志}

六、resched_curr() - 设置重调度标志

位置:kernel/sched/core.c

/* * reshed_curr - mark rq's current task 'to be rescheduled now'. * * On UP this means the setting of the need_resched flag, on SMP it * might also involve a cross-CPU call to trigger the scheduler on * the target CPU. */staticvoid__resched_curr(structrq*rq,inttif){structtask_struct*curr=rq->curr;structthread_info*cti=task_thread_info(curr);intcpu;...if(cpu==smp_processor_id()){set_ti_thread_flag(cti,tif);// 本地CPU直接设置标志if(tif==TIF_NEED_RESCHED)set_preempt_need_resched();return;}if(set_nr_and_not_polling(cti,tif)){if(tif==TIF_NEED_RESCHED)smp_send_reschedule(cpu);// 远程CPU发送IPI}...}voidresched_curr(structrq*rq){__resched_curr(rq,TIF_NEED_RESCHED);}

七、其他调度时机

1. 优先级变更

当任务的优先级改变时,可能需要重新调度:

位置:kernel/sched/fair.c

staticvoidprio_changed_fair(structrq*rq,structtask_struct*p,intoldprio){...if(task_current_donor(rq,p)){if(p->prio>oldprio)resched_curr(rq);// 优先级降低,需要调度}elsewakeup_preempt(rq,p,0);}

2. 负载均衡

当进行CPU间负载均衡时,可能触发调度:

位置:kernel/sched/core.c

// 在sched_tick()中#ifdefCONFIG_SMPif(!scx_switched_all()){rq->idle_balance=idle_cpu(cpu);sched_balance_trigger(rq);// 触发负载均衡}#endif

3. 进程创建

新进程创建后唤醒时:

位置:kernel/sched/core.c

// wake_up_new_task()中wakeup_preempt(rq,p,0);// 检查是否需要抢占

4. cgroup调度组变更

当任务在cgroup调度组之间移动时,可能触发调度。


八、调度时机总结表

调度类型触发机制调用路径配置依赖
主动调度schedule()直接调用阻塞、等待、yield
条件调度cond_resched()内核长循环中非抢占内核
用户抢占返回用户空间exit_to_user_mode_loop()
内核抢占preempt_enable()__preempt_schedule()CONFIG_PREEMPTION
中断返回抢占中断返回内核态preempt_schedule_irq()CONFIG_PREEMPTION
时钟中断调度sched_tick()task_tick_xxx()
唤醒调度try_to_wake_up()wakeup_preempt()
优先级变更__sched_setscheduler()prio_changed_xxx()

九、关键标志位

位置:arch/x86/include/asm/thread_info.h

#defineTIF_NEED_RESCHED3/* rescheduling necessary */#defineTIF_NEED_RESCHED_LAZY4/* Lazy rescheduling needed */

这些标志位存储在thread_info->flags中,用于标识是否需要进行调度。TIF_NEED_RESCHED_LAZY用于延迟抢占模式。


十、调度检查点总结

Linux内核在以下位置检查TIF_NEED_RESCHED标志:

  1. 返回用户空间时: 系统调用返回、异常返回、中断返回用户态
  2. preempt_enable()时: 抢占计数器归零且开启内核抢占
  3. 中断返回内核态时: preempt_schedule_irq()
  4. 显式调用cond_resched()时: 非抢占内核中的显式调度点

十一、核心设计理念

调度器的核心设计理念是:在需要调度时设置标志位,在安全的检查点进行实际的上下文切换,这既保证了系统的响应性,又确保了内核数据结构的一致性。

这种设计确保了:

  • 安全性: 只在内核数据结构一致的点进行上下文切换
  • 响应性: 通过抢占机制保证高优先级任务及时响应
  • 灵活性: 支持多种调度策略和配置选项

总结

Linux内核任务调度的主要时机总结如下:

  1. 主动调度 - 进程直接调用schedule()让出CPU,包括:
    - 阻塞操作(mutex、semaphore、waitqueue等)
    - sched_yield()系统调用
    - cond_resched()条件调度
  2. 用户抢占 - 从内核返回用户空间时检查TIF_NEED_RESCHED标志
  3. 内核抢占 - 需配置CONFIG_PREEMPTION:
    - preempt_enable()时检查抢占
    - 中断返回内核态时抢占
  4. 时钟中断调度 - sched_tick()检测时间片耗尽
  5. 唤醒调度 - 高优先级进程被唤醒时触发抢占
  6. 其他时机 - 优先级变更、负载均衡、进程创建等

End

http://www.jsqmd.com/news/402664/

相关文章:

  • 农产品销售系统毕设:从零构建高可用电商后端的技术选型与实现
  • 定稿前必看!顶流之选的降AI率平台 —— 千笔·专业降AIGC智能体
  • 2026年广州沛纳海手表维修推荐:多场景服务评价,针对网点覆盖与配件痛点 - 十大品牌推荐
  • [AI提效-3]-提示词工程 - 常见的提示词框架对比:他们的特点、优点、缺点、框架内容、使用场景以及示例 - 豆包版
  • Dify智能客服实战:从零搭建高可用对话系统的保姆级教程
  • [AI提效-4]-提示词工程 - 常见的提示词框架对比:他们的特点、优点、缺点、框架内容、使用场景以及示例-千问版
  • 2026最新!全网爆红的AI论文网站 —— 千笔写作工具
  • Java ATM机自动取款机毕业设计:从单机模拟到高并发实战架构
  • 小白也行的机器学习预测股票
  • Java银行智能客服系统入门指南:从架构设计到核心代码实现
  • 学霸同款!专科生专属AI论文平台 —— 千笔·专业学术智能体
  • AI辅助开发实战:使用Cherry Studio高效部署火山引擎应用
  • Claude-Code-Router在火山方舟上的高效配置实践:从架构设计到性能优化
  • ChatGPT充值方法实战指南:从API密钥到支付集成的完整解决方案
  • WebRTC开发实战:解决CMake警告‘srtp未找到‘的完整指南
  • CLGRU语音模型入门指南:从零搭建到实战避坑
  • 智能问答客服系统架构设计与实现:从技术选型到生产环境避坑指南
  • Python爬虫毕业设计效率提升实战:从单线程到异步并发架构演进
  • 一文讲透|8个降AI率平台测评:专科生必看!降AI率攻略全在这
  • 新手也能上手,AI论文平台 千笔·专业论文写作工具 VS 灵感风暴AI
  • Redux selector深度解析
  • 利用CopUI TTS提升开发效率:从技术选型到生产环境实践
  • 电商智能客服提示词:从设计原理到工程落地的最佳实践
  • SpringBoot + Vue 前后端分离毕设实战:从项目搭建到部署上线的完整链路
  • Context Engineering与Prompt Engineering实战:如何提升大模型应用开发效率
  • AI智能客服流程优化实战:从架构设计到性能调优
  • 打架行为识别数据集:公共安全与智能安防的异常行为检测数据
  • 基于若依框架的毕设实战:从模块定制到生产级部署避坑指南
  • 互联网大厂Java面试实战:Spring Boot与微服务在电商场景的应用
  • AI辅助开发实战:基于智能体重秤毕业设计的端到端技术实现