引言
上期文章linux调度器源码分析 - 概述(一)已经把调度器相关的数据结构介绍了一遍,本篇着重通过代码说明调度器在系统启动初始化阶段是如何初始化和工作的。通过上期文章我们知道,在多核CPU和SMP系统中,每个CPU(多核COU中的每个核)都有自己的struct rq队列,而rq队列中又有着自己的struct cfs_rq和struct rt_rq。在初始化时就是对这三个结构进行初始化。
init_task和init进程
当linux启动时,最先会通过汇编代码进行硬件和CPU的初始化,最后会跳转到C代码,而最初跳转到的C代码入口为
- /* 代码地址: linux/init/Main.c */
- asmlinkage __visible void __init start_kernel(void)
sched_init
在start_kernel中对调度器进行初始化的函数就是sched_init,其主要工作为
- 对相关数据结构分配内存
- 初始化root_task_group
- 初始化每个CPU的rq队列(包括其中的cfs队列和实时进程队列)
- 将init_task进程转变为idle进程
需要说明的是init_task在这里会被转变为idle进程,但是它还会继续执行初始化工作,相当于这里只是给init_task挂个idle进程的名号,它其实还是init_task进程,只有到最后init_task进程开启了kernel_init和kthreadd进程之后,才转变为真正意义上的idle进程。
- /* 代码路径: 内核源代码目录/kernel/sched/Core.c */
-
- /* 执行到此时内核只有一个进程init_task,current就为init_task。之后的init进程在初始化到最后的rest_init中启动 */
- void __init sched_init(void)
- {
- int i, j;
- unsigned long alloc_size = 0, ptr;
-
- /* 计算所需要分配的数据结构空间 */
- #ifdef CONFIG_FAIR_GROUP_SCHED
- alloc_size += 2 * nr_cpu_ids * sizeof(void **);
- #endif
-
- #ifdef CONFIG_RT_GROUP_SCHED
- alloc_size += 2 * nr_cpu_ids * sizeof(void **);
- #endif
- #ifdef CONFIG_CPUMASK_OFFSTACK
- alloc_size += num_possible_cpus() * cpumask_size();
- #endif
- if (alloc_size) {
- /* 分配内存 */
- ptr = (unsigned long)kzalloc(alloc_size, GFP_NOWAIT);
-
- #ifdef CONFIG_FAIR_GROUP_SCHED
- /* 设置 root_task_group 每个CPU上的调度实体指针se */
- root_task_group.se = (struct sched_entity **)ptr;
- ptr += nr_cpu_ids * sizeof(void **);
-
- /* 设置 root_task_group 每个CPU上的CFS运行队列指针cfs_rq */
- root_task_group.cfs_rq = (struct cfs_rq **)ptr;
- ptr += nr_cpu_ids * sizeof(void **);
-
- #endif /* CONFIG_FAIR_GROUP_SCHED */
- #ifdef CONFIG_RT_GROUP_SCHED
- /* 设置 root_task_group 每个CPU上的实时调度实体指针se */
- root_task_group.rt_se = (struct sched_rt_entity **)ptr;
- ptr += nr_cpu_ids * sizeof(void **);
-
- /* 设置 root_task_group 每个CPU上的实时运行队列指针rt_rq */
- root_task_group.rt_rq = (struct rt_rq **)ptr;
- ptr += nr_cpu_ids * sizeof(void **);
-
- #endif /* CONFIG_RT_GROUP_SCHED */
- #ifdef CONFIG_CPUMASK_OFFSTACK
- for_each_possible_cpu(i) {
- per_cpu(load_balance_mask, i) = (void *)ptr;
- ptr += cpumask_size();
- }
- #endif /* CONFIG_CPUMASK_OFFSTACK */
- }
- /* 初始化实时进程的带宽限制,用于设置实时进程在CPU中所占用比的 */
- init_rt_bandwidth(&def_rt_bandwidth,
- global_rt_period(), global_rt_runtime());
- init_dl_bandwidth(&def_dl_bandwidth,
- global_rt_period(), global_rt_runtime());
-
- #ifdef CONFIG_SMP
- /* 初始化默认的调度域,调度域包含一个或多个CPU,负载均衡是在调度域内执行的,相互之间隔离 */
- init_defrootdomain();
- #endif
-
- #ifdef CONFIG_RT_GROUP_SCHED
- /* 初始化实时进程的带宽限制,用于设置实时进程在CPU中所占用比的 */
- init_rt_bandwidth(&root_task_group.rt_bandwidth,
- global_rt_period(), global_rt_runtime());
- #endif /* CONFIG_RT_GROUP_SCHED */
-
- #ifdef CONFIG_CGROUP_SCHED
- /* 将分配好空间的 root_task_group 加入 task_groups 链表 */
- list_add(&root_task_group.list, &task_groups);
- INIT_LIST_HEAD(&root_task_group.children);
- INIT_LIST_HEAD(&root_task_group.siblings);
- /* 自动分组初始化,每个tty(控制台)动态的创建进程组,这样就可以降低高负载情况下的桌面延迟 */
- autogroup_init(&init_task);
-
- #endif /* CONFIG_CGROUP_SCHED */
- /* 遍历设置每一个CPU */
- for_each_possible_cpu(i) {
- struct rq *rq;
- /* 获取CPUi的rq队列 */
- rq = cpu_rq(i);
- /* 初始化rq队列的自旋锁 */
- raw_spin_lock_init(&rq->lock);
- /* CPU运行队列中调度实体(sched_entity)数量为0 */
- rq->nr_running = 0;
- /* CPU负载 */
- rq->calc_load_active = 0;
- /* 负载下次更新时间 */
- rq->calc_load_update = jiffies + LOAD_FREQ;
- /* 初始化CFS运行队列 */
- init_cfs_rq(&rq->cfs);
- /* 初始化实时进程运行队列 */
- init_rt_rq(&rq->rt, rq);
- init_dl_rq(&rq->dl, rq);
- #ifdef CONFIG_FAIR_GROUP_SCHED
- root_task_group.shares = ROOT_TASK_GROUP_LOAD;
- INIT_LIST_HEAD(&rq->leaf_cfs_rq_list);
- /*
- * How much cpu bandwidth does root_task_group get?
- *
- * In case of task-groups formed thr' the cgroup filesystem, it
- * gets 100% of the cpu resources in the system. This overall
- * system cpu resource is divided among the tasks of
- * root_task_group and its child task-groups in a fair manner,
- * based on each entity's (task or task-group's) weight
- * (se->load.weight).
- *
- * In other words, if root_task_group has 10 tasks of weight
- * 1024) and two child groups A0 and A1 (of weight 1024 each),
- * then A0's share of the cpu resource is:
- *
- * A0's bandwidth = 1024 / (10*1024 + 1024 + 1024) = 8.33%
- *
- * We achieve this by letting root_task_group's tasks sit
- * directly in rq->cfs (i.e root_task_group->se[] = NULL).
- */
- /* 初始化CFS的带宽限制,用于设置普通进程在CPU中所占用比的 */
- init_cfs_bandwidth(&root_task_group.cfs_bandwidth);
- init_tg_cfs_entry(&root_task_group, &rq->cfs, NULL, i, NULL);
- #endif /* CONFIG_FAIR_GROUP_SCHED */
-
- rq->rt.rt_runtime = def_rt_bandwidth.rt_runtime;
- #ifdef CONFIG_RT_GROUP_SCHED
- init_tg_rt_entry(&root_task_group, &rq->rt, NULL, i, NULL);
- #endif
- /* 初始化该队列所保存的每个CPU的负载情况 */
- for (j = 0; j < CPU_LOAD_IDX_MAX; j++)
- rq->cpu_load[j] = 0;
- /* 该队列最后一次更新cpu_load的时间值为当前 */
- rq->last_load_update_tick = jiffies;
-
- #ifdef CONFIG_SMP
- /* 这些参数都是负载均衡使用的 */
- rq->sd = NULL;
- rq->rd = NULL;
- rq->cpu_capacity = SCHED_CAPACITY_SCALE;
- rq->post_schedule = 0;
- rq->active_balance = 0;
- rq->next_balance = jiffies;
- rq->push_cpu = 0;
- rq->cpu = i;
- rq->online = 0;
- rq->idle_stamp = 0;
- rq->avg_idle = 2*sysctl_sched_migration_cost;
- rq->max_idle_balance_cost = sysctl_sched_migration_cost;
-
- INIT_LIST_HEAD(&rq->cfs_tasks);
- /* 将CPU运行队列加入到默认调度域中 */
- rq_attach_root(rq, &def_root_domain);
- #ifdef CONFIG_NO_HZ_COMMON
- /* 动态时钟使用的标志位,初始时动态时钟是不使用的 */
- rq->nohz_flags = 0;
- #endif
- #ifdef CONFIG_NO_HZ_FULL
- /* 也是动态时钟才使用的标志位,用于保存上次调度tick发生时间 */
- rq->last_sched_tick = 0;
- #endif
- #endif
- /* 初始化运行队列定时器,这个是高精度定时器,但是只是初始化,这时并没有使用 */
- init_rq_hrtick(rq);
- atomic_set(&rq->nr_iowait, 0);
- }
- /* 设置 init_task 进程的权重 */
- set_load_weight(&init_task);
-
- #ifdef CONFIG_PREEMPT_NOTIFIERS
- /* 初始化通知链 */
- INIT_HLIST_HEAD(&init_task.preempt_notifiers);
- #endif
-
- /*
- * The boot idle thread does lazy MMU switching as well:
- */
- atomic_inc(&init_mm.mm_count);
- enter_lazy_tlb(&init_mm, current);
-
- /*
- * Make us the idle thread. Technically, schedule() should not be
- * called from this thread, however somewhere below it might be,
- * but because we are the idle thread, we just pick up running again
- * when this runqueue becomes "idle".
- */
- /* 将当前进程初始化为idle进程,idle进程用于当CPU没有进程可运行时运行,空转 */
- init_idle(current, smp_processor_id());
- /* 下次负载更新时间(是一个相对时间) */
- calc_load_update = jiffies + LOAD_FREQ;
-
- /*
- * During early bootup we pretend to be a normal task:
- */
- /* 设置idle进程使用CFS调度策略 */
- current->sched_class = &fair_sched_class;
-
- #ifdef CONFIG_SMP
- zalloc_cpumask_var(&sched_domains_tmpmask, GFP_NOWAIT);
- /* May be allocated at isolcpus cmdline parse time */
- if (cpu_isolated_map == NULL)
- zalloc_cpumask_var(&cpu_isolated_map, GFP_NOWAIT);
- idle_thread_set_boot_cpu();
- set_cpu_rq_start_time();
- #endif
- init_sched_fair_class();
- /* 这里只是标记调度器开始运行了,但是此时系统只有一个init_task(idle)进程,并且定时器都还没启动。并不会调度到其他进程,也没有其他进程可供调度 */
- scheduler_running = 1;
- }
总结
调度器的初始化还是比较简单的,毕竟调度器的核心不在此,重头戏在它的运行时处理,之后的文章会详细分析调度器的运行时处理。