Linux进程调度CFS算法实现分析

本文深入解析Linux的完全公平调度器(CFS)的实现原理,包括进程调度时机、CFS调度实体、虚拟运行时间(vruntime)的更新及进程选择策略。通过跟踪源码,阐述了CFS如何维护红黑树、如何处理进程的创建、唤醒和优先级变化,以及如何确保公平性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

网上讲CFS的文章很多,可能版本不一,理解不尽相同。我以问题追溯方式,跟踪源码写下我对CFS的理解,有的问题我也还没理解透,欢迎对内核有兴趣的朋友一起交流学习,源码版本是与LKD3配套的Linux2.6.34

背景知识:

(1) Linux的调度器类主要实现两类进程调度算法:实时调度算法完全公平调度算法(CFS),实时调度算法SCHED_FIFO和SCHED_RR,按优先级执行,一般不会被抢占。直到实时进程执行完,才会执行普通进程。而大多数的普通进程,用的就是CFS算法。

(2) 进程调度的时机

①进程状态转换时刻:进程终止、进程睡眠;

②当前进程的”时间片”用完;

③主动让出处理器,用户调用sleep()或者内核调用schedule();

④从中断,系统调用或异常返回时;

(3) 每个进程task_struct中都有一个struct sched_entity se成员,这就是调度器的实体结构,进程调度算法实际上就是管理所有进程的这个se。

点击(此处)折叠或打开

  1. struct task_struct {
  2.     volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
  3.     void *stack;
  4.     atomic_t usage;
  5.     unsigned int flags;    /* per process flags, defined below */
  6.     unsigned int ptrace;

  7.     int lock_depth;        /* BKL lock depth */

  8. #ifdef CONFIG_SMP
  9. #ifdef __ARCH_WANT_UNLOCKED_CTXSW
  10.     int oncpu;
  11. #endif
  12. #endif

  13.     int prio, static_prio, normal_prio;
  14.     unsigned int rt_priority;
  15.     const struct sched_class *sched_class;
  16.     struct sched_entity se; //进程调度实体
  17.     struct sched_rt_entity rt;
  18.     …
  19. }


CFS基于一个简单的理念:所有任务都应该公平的分配处理器。理想情况下,n个进程的调度系统中,每个进程获得1/n处理器时间,所有进程的vruntime也是相同的。

CFS完全抛弃了时间片的概念,而是分配一个处理器使用比来度量。

每个进程一个调度周期内分配的时间(类似于传统的“时间片”)跟三个因素有关:进程总数,优先级,调度周期

其他进程调度详细基础知识参见上篇博文:http://blog.chinaunix.net/uid-24708340-id-3778555.html

1.理解CFS的首先要理解vruntime的含义

简单说vruntime就是该进程的运行时间,但这个时间是通过优先级和系统负载等加权过的时间,而非物理时钟时间,按字面理解为虚拟运行时间,也很恰当。

每个进程的调度实体se都保存着本进程的虚拟运行时间。

点击(此处)折叠或打开

  1. struct sched_entity {
  2.     struct load_weight    load;        /* for load-balancing */
  3.     struct rb_node        run_node;
  4.     struct list_head    group_node;
  5.     unsigned int        on_rq;

  6.     u64            exec_start;
  7.     u64            sum_exec_runtime;
  8.     u64            vruntime; //虚拟运行时间
  9.     u64            prev_sum_exec_runtime;

  10. }

而进程相关的调度方法如下

点击(此处)折叠或打开

  1. static const struct sched_class fair_sched_class = {
  2.     .next            = &idle_sched_class,
  3.     .enqueue_task        = enqueue_task_fair,
  4.     .dequeue_task        = dequeue_task_fair,
  5.     .yield_task        = yield_task_fair,

  6.     .check_preempt_curr    = check_preempt_wakeup,

  7.     .pick_next_task        = pick_next_task_fair,
  8.     .put_prev_task        = put_prev_task_fair,

  9. #ifdef CONFIG_SMP
  10.     .select_task_rq        = select_task_rq_fair,

  11.     .rq_online        = rq_online_fair,
  12.     .rq_offline        = rq_offline_fair,

  13.     .task_waking        = task_waking_fair,
  14. #endif

  15.     .set_curr_task = set_curr_task_fair,
  16.     .task_tick        = task_tick_fair,
  17.     .task_fork        = task_fork_fair,

  18.     .prio_changed        = prio_changed_fair,
  19.     .switched_to        = switched_to_fair,

  20.     .get_rr_interval    = get_rr_interval_fair,

  21. #ifdef CONFIG_FAIR_GROUP_SCHED
  22.     .task_move_group    = task_move_group_fair,
  23. #endif
  24. };

2.vruntime的值如何跟新?

时钟中断产生时,会依次调用tick_periodic()-> update_process_times()->scheduler_tick()

点击(此处)折叠或打开

  1. void scheduler_tick(void)
  2. {

  3.     raw_spin_lock(&rq->lock);
  4.     update_rq_clock(rq);
  5.     update_cpu_load(rq);
  6.     curr->sched_class->task_tick(rq, curr, 0); //执行调度器tick,更新进程vruntime
  7.     raw_spin_unlock(&rq->lock);

  8. }
  9. task_tick_fair ()调用entity_tick()如下:
  10. static void entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
  11. {
  12.     update_curr(cfs_rq);

  13.     if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT))
  14.         check_preempt_tick(cfs_rq, curr); //检查当前进程是否需要调度
  15. }

这里分析两个重要函数update_curr()check_preempt_tick()

点击(此处)折叠或打开

  1. static void update_curr(struct cfs_rq *cfs_rq)
  2. {
  3.     struct sched_entity *curr = cfs_rq->curr;
  4.     u64 now = rq_of(cfs_rq)->clock;
  5.     unsigned long delta_exec;

  6.     if (unlikely(!curr))
  7.         return;

  8. // delta_exec获得最后一次修改后,当前进程所运行的实际时间
  9.     delta_exec = (unsigned long)(now - curr->exec_start);
  10.     if (!delta_exec)
  11.         return;

  12.     __update_curr(cfs_rq, curr, delta_exec);
  13.     curr->exec_start = now; //运行时间已保存,更新起始执行时间

  14.     if (entity_is_task(curr)) {
  15.         struct task_struct *curtask = task_of(curr);

  16.         trace_sched_stat_runtime(curtask, delta_exec, curr->vrun
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值