linux调度器源码分析 - 初始化(二)
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/
引言
上期文章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)
在start_kerenl函数中,进行了系统启动过程中几乎所有重要的初始化(有一部分在boot中初始化,有一部分在start_kernel之前的汇编代码进行初始化),包括内存、页表、必要数据结构、信号、调度器、硬件设备等。而这些初始化是由谁来负责的?就是由init_task这个进程。init_task是静态定义的一个进程,也就是说当内核被放入内存时,它就已经存在,它没有自己的用户空间,一直处于内核空间中运行,并且也只处于内核空间运行。当它执行到最后,将start_kernel中所有的初始化执行完成后,会在内核中启动一个kernel_init内核线程和一个kthreadd内核线程,kernel_init内核线程执行到最后会通过execve系统调用执行转变为我们所熟悉的init进程,而kthreadd内核线程是内核用于管理调度其他的内核线程的守护线程。在最后init_task将变成一个idle进程,用于在CPU没有进程运行时运行它,它在此时仅仅用于空转。
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 = , ptr; /* 计算所需要分配的数据结构空间 */
#ifdef CONFIG_FAIR_GROUP_SCHED
alloc_size += * nr_cpu_ids * sizeof(void **);
#endif #ifdef CONFIG_RT_GROUP_SCHED
alloc_size += * 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 = ;
/* CPU负载 */
rq->calc_load_active = ;
/* 负载下次更新时间 */
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 = ; j < CPU_LOAD_IDX_MAX; j++)
rq->cpu_load[j] = ;
/* 该队列最后一次更新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 = ;
rq->active_balance = ;
rq->next_balance = jiffies;
rq->push_cpu = ;
rq->cpu = i;
rq->online = ;
rq->idle_stamp = ;
rq->avg_idle = *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 = ;
#endif
#ifdef CONFIG_NO_HZ_FULL
/* 也是动态时钟才使用的标志位,用于保存上次调度tick发生时间 */
rq->last_sched_tick = ;
#endif
#endif
/* 初始化运行队列定时器,这个是高精度定时器,但是只是初始化,这时并没有使用 */
init_rq_hrtick(rq);
atomic_set(&rq->nr_iowait, );
}
/* 设置 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 = ;
}
总结
调度器的初始化还是比较简单的,毕竟调度器的核心不在此,重头戏在它的运行时处理,之后的文章会详细分析调度器的运行时处理。
linux调度器源码分析 - 初始化(二)的更多相关文章
- linux调度器源码分析 - 运行(四)
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 引言 之前的文章已经将调度器的数据结构.初始化.加入进程都进行了分析,这篇文章将主要说明调度器是如何在程序稳定运 ...
- Linux 内核调度器源码分析 - 初始化
导语 上篇系列文 混部之殇-论云原生资源隔离技术之CPU隔离(一) 介绍了云原生混部场景中CPU资源隔离核心技术:内核调度器,本系列文章<Linux内核调度器源码分析>将从源码的角度剖析内 ...
- linux调度器源码分析 - 概述(一)
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 引言 调度器作为操作系统的核心部件,具有非常重要的意义,其随着linux内核的更新也不断进行着更新.本系列文章通 ...
- linux调度器源码分析 - 新进程加入(三)
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 引言 之前的文章已经介绍了调度器已经初始化完成,现在只需要加入一个周期定时器tick驱动它进行周期调度即可,而加 ...
- Linux进程调度与源码分析(二)——进程生命周期与task_struct进程结构体
1.进程生命周期 Linux操作系统属于多任务操作系统,系统中的每个进程能够分时复用CPU时间片,通过有效的进程调度策略实现多任务并行执行.而进程在被CPU调度运行,等待CPU资源分配以及等待外部事件 ...
- linux中断源码分析 - 初始化(二)
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 本篇文章主要讲述源码中是如何对中断进行一系列的初始化的. 回顾 在上一篇概述中,介绍了几个对于中断来说非常重要的 ...
- spark[源码]-DAG调度器源码分析[二]
前言 根据图片上的结构划分我们不难发现当rdd触发action操作之后,会调用SparkContext的runJob方法,最后调用的DAGScheduler.handleJobSubmitted方法完 ...
- Hadoop 三大调度器源码分析及编写自己的调度器
如要转载,请注上作者和出处. 由于能力有限,如有错误,请大家指正. 须知: 我们下载的是hadoop-2.7.3-src 源码. 这个版本默认调度器是Capacity调度器. 在2.0.2-alph ...
- 一步步实现windows版ijkplayer系列文章之二——Ijkplayer播放器源码分析之音视频输出——视频篇
一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...
随机推荐
- linux学习笔记-conky配置开机启动方法
我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 一.常用桌面的配置方法 创建启动文件并加入以下配置 ~/.config/autostart/conky.desktop [Des ...
- html5新特性学习笔记
1.语义化标签兼容问题(语义化标签只支持ie8以上,不包括ie8) 解决方法一:该标签的css中加上display:block; 通过DOM的方式创建这个标签 document.createEleme ...
- JS 中的 __proto__ 、prototype、constructor
首先 先解释这三个属性: (1) prototype : 它是函数独有的,从一个函数指向一个对象(函数的原型),含义是函数的原型对象,也就是这个函数所创建的实例的原型对象.(普通函数的该属性没有作用 ...
- 云计算的三种服务模式:IaaS, PaaS, SaaS
IaaS: Infrastructure as a Service 基础设施即服务 PaaS: Platform as a Service 平台即服务 SaaS: Software as a Serv ...
- JS数组迭代方法
先说 every()和 some(),它们都用于查询数组中的项是否满足某个条件. every(): var numbers = [1,2,3,4,5,4,3,2,1]; var everyResult ...
- HTML5是什么,以及优点和缺点
HTML5是超文本标记语言HTML的第五次重大修改 HTML 5 的第一份正式草案已于2008年1月22日公布 2013年5月6日, HTML 5.1正式草案公布 HTML5的优缺点是什么?作为HTM ...
- LeanCloud云引擎相关问题
(1).Windows 用户可以在 Github releases 页面 根据操作系统版本下载最新的 32 位 或 64 位 msi 安装包进行安装,安装成功之后在 Windows 命令提示符(或 P ...
- (网页)textarea去掉回车换行
转自CSDN: 1,把textarea内输入的内容中有回车换行的转成<br />传给后台, var content = $("#text").val().replace ...
- BigDecimal遇到的问题,大伙也说说
一:相除精度丢失的问题 BigDecimal的api除法相对加减乘要实现的复杂多了,只介绍常用的我遇到的问题: 问题:两数相除,如果9/3=3整除没问题,但是10/3=0.33333333...... ...
- 消除TortoiseSVN 检出到(checkout)桌面上显示一堆问号
之前不小心直接将版本库的内容检出到桌面,后才发现桌面上的文件图标都变成了问号,新建文件夹也同样如此. 为了解决这个问题,采用如下方法(任何一个检出文件夹均可这样操作): 1.删除桌面隐藏的.SVN文件 ...