网上讲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。
点击(此处)折叠或打开
- struct task_struct {
- volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
- void *stack;
- atomic_t usage;
- unsigned int flags; /* per process flags, defined below */
- unsigned int ptrace;
-
- int lock_depth; /* BKL lock depth */
-
- #ifdef CONFIG_SMP
- #ifdef __ARCH_WANT_UNLOCKED_CTXSW
- int oncpu;
- #endif
- #endif
-
- int prio, static_prio, normal_prio;
- unsigned int rt_priority;
- const struct sched_class *sched_class;
- struct sched_entity se; //进程调度实体
- struct sched_rt_entity rt;
- …
- }
CFS基于一个简单的理念:所有任务都应该公平的分配处理器。理想情况下,n个进程的调度系统中,每个进程获得1/n处理器时间,所有进程的vruntime也是相同的。
CFS完全抛弃了时间片的概念,而是分配一个处理器使用比来度量。
每个进程一个调度周期内分配的时间(类似于传统的“时间片”)跟三个因素有关:进程总数,优先级,调度周期
其他进程调度详细基础知识参见上篇博文:http://blog.chinaunix.net/uid-24708340-id-3778555.html
1.理解CFS的首先要理解vruntime的含义
简单说vruntime就是该进程的运行时间,但这个时间是通过优先级和系统负载等加权过的时间,而非物理时钟时间,按字面理解为虚拟运行时间,也很恰当。
每个进程的调度实体se都保存着本进程的虚拟运行时间。
点击(此处)折叠或打开
- struct sched_entity {
- struct load_weight load; /* for load-balancing */
- struct rb_node run_node;
- struct list_head group_node;
- unsigned int on_rq;
-
- u64 exec_start;
- u64 sum_exec_runtime;
- u64 vruntime; //虚拟运行时间
- u64 prev_sum_exec_runtime;
- …
- }
而进程相关的调度方法如下
点击(此处)折叠或打开
- static const struct sched_class fair_sched_class = {
- .next = &idle_sched_class,
- .enqueue_task = enqueue_task_fair,
- .dequeue_task = dequeue_task_fair,
- .yield_task = yield_task_fair,
-
- .check_preempt_curr = check_preempt_wakeup,
-
- .pick_next_task = pick_next_task_fair,
- .put_prev_task = put_prev_task_fair,
-
- #ifdef CONFIG_SMP
- .select_task_rq = select_task_rq_fair,
-
- .rq_online = rq_online_fair,
- .rq_offline = rq_offline_fair,
-
- .task_waking = task_waking_fair,
- #endif
-
- .set_curr_task = set_curr_task_fair,
- .task_tick = task_tick_fair,
- .task_fork = task_fork_fair,
-
- .prio_changed = prio_changed_fair,
- .switched_to = switched_to_fair,
-
- .get_rr_interval = get_rr_interval_fair,
-
- #ifdef CONFIG_FAIR_GROUP_SCHED
- .task_move_group = task_move_group_fair,
- #endif
- };
2.vruntime的值如何跟新?
时钟中断产生时,会依次调用tick_periodic()-> update_process_times()->scheduler_tick()
点击(此处)折叠或打开
- void scheduler_tick(void)
- {
- …
- raw_spin_lock(&rq->lock);
- update_rq_clock(rq);
- update_cpu_load(rq);
- curr->sched_class->task_tick(rq, curr, 0); //执行调度器tick,更新进程vruntime
- raw_spin_unlock(&rq->lock);
- …
- }
- task_tick_fair ()调用entity_tick()如下:
- static void entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
- {
- update_curr(cfs_rq);
- …
- if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT))
- check_preempt_tick(cfs_rq, curr); //检查当前进程是否需要调度
- }
这里分析两个重要函数update_curr()和check_preempt_tick()
点击(此处)折叠或打开
- static void update_curr(struct cfs_rq *cfs_rq)
- {
- struct sched_entity *curr = cfs_rq->curr;
- u64 now = rq_of(cfs_rq)->clock;
- unsigned long delta_exec;
-
- if (unlikely(!curr))
- return;
-
- // delta_exec获得最后一次修改后,当前进程所运行的实际时间
- delta_exec = (unsigned long)(now - curr->exec_start);
- if (!delta_exec)
- return;
-
- __update_curr(cfs_rq, curr, delta_exec);
- curr->exec_start = now; //运行时间已保存,更新起始执行时间
-
- if (entity_is_task(curr)) {
- struct task_struct *curtask = task_of(curr);
-
- trace_sched_stat_runtime(curtask, delta_exec, curr->vrun