Linux内核调度器源码分析初始

北京看皮肤科的医院 http://pf.39.net/bdfyy/bdfjc/180416/6171978.html

赖滨,腾讯云高级工程师,7+年专注于操作系统相关技术。目前负责腾讯云原生OS的研发,以及OS/虚拟化的性能优化工作。

导语

上篇系列文混部之殇-论云原生资源隔离技术之CPU隔离(一)介绍了云原生混部场景中CPU资源隔离核心技术:内核调度器,本系列文章《Linux内核调度器源码分析》将从源码的角度剖析内核调度的具体原理和实现,我们将以Linuxkernel5.4版本(TencentOSServer3默认内核版本)为对象,从调度器子系统的初始化代码开始,分析Linux内核调度器的设计与实现。

调度器(Scheduler)子系统是内核的核心子系统之一,负责系统内CPU资源的合理分配,需要能处理纷繁复杂的不同类型任务的调度需求,还需要能处理各种复杂的并发竞争环境,同时还需要兼顾整体吞吐性能和实时性要求(本身是一对矛盾体),其设计与实现都极具挑战。

为了能够理解Linux调度器的设计与实现,我们将以Linuxkernel5.4版本(TencentOSServer3默认内核版本)为对象,从调度器子系统的初始化代码开始,分析Linux内核调度器的设计与实现。

本(系列)文通过分析Linux调度器(主要针对CFS)的设计与实现,希望能够让读者了解:

调度器的基本概念调度器的初始化(包括调度域相关的种种)进程的创建、执行与销毁进程切换原理与实现CFS进程调度策略(单核)如何在全局系统的调度上保证CPU资源的合理使用如何平衡CPU缓存热度与CPU负载之间的关系很special的调度器features分析调度器的基本概念

在分析调度器的相关代码之前,需要先了解一下调度器涉及的核心数据(结构)以及它们的作用

运行队列(rq)

内核会为每个CPU创建一个运行队列,系统中的就绪态(处于Running状态的)进程(task)都会被组织到内核运行队列上,然后根据相应的策略,调度运行队列上的进程到CPU上执行。

调度类(sched_class)

内核将调度策略(sched_class)进行了高度的抽象,形成调度类(sched_class)。通过调度类可以将调度器的公共代码(机制)和具体不同调度类提供的调度策略进行充分解耦,是典型的OO(面向对象)的思想。通过这样的设计,可以让内核调度器极具扩展性,开发者通过很少的代码(基本不需改动公共代码)就可以增加一个新的调度类,从而实现一种全新的调度器(类),比如,deadline调度类就是3.x中新增的,从代码层面看只是增加了dl_sched_class这个结构体的相关实现函数,就很方便的添加了一个新的实时调度类型。

目前的5.4内核,有5种调度类,优先级从高到底分布如下:

stop_sched_class:

优先级最高的调度类,它与idle_sched_class一样,是一个专用的调度类型(除了migration线程之外,其他的task都是不能或者说不应该被设置为stop调度类)。该调度类专用于实现类似activebalance或stopmachine等依赖于migration线程执行的“紧急”任务。

dl_sched_class:

deadline调度类的优先级仅次于stop调度类,它是一种基于EDL算法实现的实时调度器(或者说调度策略)。

rt_sched_class:

rt调度类的优先级要低于dl调度类,是一种基于优先级实现的实时调度器。

fair_sched_class:

CFS调度器的优先级要低于上面的三个调度类,它是基于公平调度思想而设计的调度类型,是Linux内核的默认调度类。

idle_sched_class:

idle调度类型是swapper线程,主要是让swapper线程接管CPU,通过cpuidle/nohz等框架让CPU进入节能状态。

调度域(sched_domain)

调度域是在2.6里引入内核的,通过多级调度域引入,能够让调度器更好的适应硬件的物理特性(调度域可以更好的适配CPU多级缓存以及NUMA物理特性对负载均衡所带来的挑战),实现更好的调度性能(sched_domain是为CFS调度类负载均衡而开发的机制)。

调度组(sched_group)

调度组是与调度域一起被引入内核的,它会与调度域一起配合,协助CFS调度器完成多核间的负载均衡。

根域(root_domain)

根域主要是负责实时调度类(包括dl和rt调度类)负载均衡而设计的数据结构,协助dl和rt调度类完成实时任务的合理调度。在没有用isolate或者cpusetcgroup修改调度域的时候,那么默认情况下所有的CPU都会处于同一个根域。

组调度(group_sched)

为了能够对系统里的资源进行更精细的控制,内核引入了cgroup机制来进行资源控制。而group_sched就是cpucgroup的底层实现机制,通过cpucgroup我们可以将一些进程设置为一个cgroup,并且通过cpucgroup的控制接口配置相应的带宽和share等参数,这样我们就可以按照group为单位,对CPU资源进行精细的控制。

调度器初始化(sched_init)

下面进入正题,开始分析内核调度器的初始化流程,希望能通过这里的分析,让大家了解:

运行队列是如何被初始化的

组调度是如何与rq关联起来的(只有关联之后才能通过group_sched进行组调度)

CFS软中断SCHED_SOFTIRQ注册

调度初始化(sched_init)

start_kernel

----setup_arch

----build_all_zonelists

----mm_init

----sched_init调度初始化

调度初始化位于start_kernel相对靠后的位置,这个时候内存初始化已经完成,所以可以看到sched_init里面已经可以调用kzmalloc等内存申请函数了。

sched_init需要为每个CPU初始化运行队列(rq)、dl/rt的全局默认带宽、各个调度类的运行队列以及CFS软中断注册等工作。

接下来我们看看sched_init的具体实现(省略了部分代码):

void__initsched_init(void){unsignedlongptr=0;inti;/**初始化全局默认的rt和dl的CPU带宽控制数据结构**这里的rt_bandwidth和dl_bandwidth是用来控制全局的DL和RT的使用带宽,防止实时进程*CPU使用过多,从而导致普通的CFS进程出现饥饿的情况*/init_rt_bandwidth(def_rt_bandwidth,global_rt_period(),global_rt_runtime());init_dl_bandwidth(def_dl_bandwidth,global_rt_period(),global_rt_runtime());#ifdefCONFIG_SMP/**初始化默认的根域**根域是dl/rt等实时进程做全局均衡的重要数据结构,以rt为例*root_domain-cpupri是这个根域范围内每个CPU上运行的RT任务的最高优先级,以及*各个优先级任务在CPU上的分布情况,通过cpupri的数据,那么在rtenqueue/dequeue*的时候,rt调度器就可以根据这个rt任务分布情况来保证高优先级的任务得到优先*运行*/init_defrootdomain();#endif#ifdefCONFIG_RT_GROUP_SCHED/**如果内核支持rt组调度(RT_GROUP_SCHED),那么对RT任务的带宽控制将可以用cgroup*的粒度来控制每个group里rt任务的CPU带宽使用情况**RT_GROUP_SCHED可以让rt任务以cpucgroup的形式来整体控制带宽*这样可以为RT带宽控制带来更大的灵活性(没有RT_GROUP_SCHED的时候,只能控制RT的全局*带宽使用,不能通过指定group的形式控制部分RT进程带宽)*/init_rt_bandwidth(root_task_group.rt_bandwidth,global_rt_period(),global_rt_runtime());#endif/*CONFIG_RT_GROUP_SCHED*//*为每个CPU初始化它的运行队列*/for_each_possible_cpu(i){structrq*rq;rq=cpu_rq(i);raw_spin_lock_init(rq-lock);/**初始化rq上cfs/rt/dl的运行队列*每个调度类型在rq上都有各自的运行队列,每个调度类都是各自管理自己的进程*在pick_next_task()的时候,内核根据调度类优先级的顺序,从高到底选择任务*这样就保证了高优先级调度类任务会优先得到运行**stop和idle是特殊的调度类型,是为专门的目的而设计的调度类,并不允许用户*创建相应类型的进程,所以内核也没有在rq里设计对应的运行队列*/init_cfs_rq(rq-cfs);init_rt_rq(rq-rt);init_dl_rq(rq-dl);#ifdefCONFIG_FAIR_GROUP_SCHED/**CFS的组调度(group_sched),可以通过cpucgroup来对CFS进行进行控制*可以通过cpu.shares来提供group之间的CPU比例控制(让不同的cgroup按照对应*的比例来分享CPU),也可以通过cpu.cfs_quota_us来进行配额设定(与RT的*带宽控制类似)。CFSgroup_sched带宽控制是容器实现的基础底层技术之一**root_task_group是默认的根task_group,其他的cpucgroup都会以它做为*parent或者ancestor。这里的初始化将root_task_group与rq的cfs运行队列*关联起来,这里做的很有意思,直接将root_task_group-cfs_rq[cpu]=rq-cfs*这样在cpucgroup根下的进程或者cgrouptg的sched_entity会直接加入到rq-cfs*队列里,可以减少一层查找开销。*/root_task_group.shares=ROOT_TASK_GROUP_LOAD;INIT_LIST_HEAD(rq-leaf_cfs_rq_list);rq-tmp_alone_branch=rq-leaf_cfs_rq_list;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;#ifdefCONFIG_RT_GROUP_SCHED/*初始化rq上的rt运行队列,与上面的CFS的组调度初始化类似*/init_tg_rt_entry(root_task_group,rq-rt,NULL,i,NULL);#endif#ifdefCONFIG_SMP/**这里将rq与默认的def_root_domain进行关联,如果是SMP系统,那么后面*在sched_init_smp的时候,内核会创建新的root_domain,然后替换这里*def_root_domain*/rq_attach_root(rq,def_root_domain);#endif/*CONFIG_SMP*/}/**注册CFS的SCHED_SOFTIRQ软中断服务函数*这个软中断住要是周期性负载均衡以及nohzidleloadbalance而准备的*/init_sched_fair_class();scheduler_running=1;}多核调度初始化(sched_init_smp)

多核调度初始化主要是完成调度域/调度组的初始化(当然根域也会做,但相对而言,根域的初始化会比较简单)。

Linux是一个可以跑在多种芯片架构,多种内存架构(UMA/NUMA)上运行的操作系统,所以Linux需要能够适配多种物理结构,所以它的调度域设计与实现也是相对比较复杂的。

调度域实现原理

在讲具体的调度域初始化代码之前,我们需要先了解调度域与物理拓扑结构之间的关系(因为调度域的设计是与物理拓扑结构息息相关的,如果不理解物理拓扑结构,那么就没有办法真正理解调度域的实现)

CPU的物理拓扑图

我们假设一个计算机系统(与intel芯片类似,但缩小CPU核心数,以方便表示):

双socket的计算机系统,每个socket都是2核4线程组成,那么这个计算机系统就应该是一个4核8线程的NUMA系统(上面只是intel的物理拓扑结构,而像AMDZEN架构采用了chiplet的设计,它在MC与NUMA域之间会多一层DIE域)。

第一层(SMT域):

如上图的CORE0,2个超线程构成了SMT域。对于intelcpu而言,超线程共享了L1与L2(甚至连storebuffe都在一定程度上共享),所以SMT域之间互相迁移是没有任何缓存热度损失的。

第二层(MC域):

如上图CORE0与CORE1,他们位于同一个SOCKET,属于MC域。对于intelcpu而言,他们一般共享LLC(一般是L3),在这个域里进程迁移虽然会失去L1与L2的热度,但L3的缓存热度还是可以保持的。

第三层(NUMA域):

如上图的SOCKET0和SOCKET1,它们之间的进程迁移会导致所有缓存热度的损失,会有较大的开销,所以NUMA域的迁移需要相对的谨慎。

正是由于这样的硬件物理特性(不同层级的缓存热度、NUMA访问延迟等硬件因素),所以内核抽象了sched_domain和sched_group来表示这样的物理特性。在做负载均衡的时候,根据相应的调度域特性,做不同的调度策略(例如负载均衡的频率、不平衡的因子以及唤醒选核逻辑等),从而在CPU负载与缓存亲和性上做更好的平衡。

调度域具体实现

接下来我们可以看看内核如何在上面的物理拓扑结构上建立调度域与调度组的

内核会根据物理拓扑结构建立对应层次的调度域,然后在每层调度域上再建立相应的调度组。调度域在做负载均衡,是在对应层次的调度域里找到负载最重的busiestsg(sched_group),然后再判断buiestsg与localsg(前CPU所在的调度组)的负载是否不均。如果存在负载不均的情况,则会从buiestsg里选择buisestcpu,然后进行2个CPU间的负载平衡。

SMT域是最底层的调度域,可以看到每个超线程对就是一个smtdomain。smtdomain里有2个sched_group,而每个sched_group则只会有一个CPU。所以smt域的负载均衡就是执行超线程间的进程迁移,这个负载均衡的时间最短,条件最宽松。

而对于不存在超线程的架构(或者说芯片没有开启超线程),那么最底层域就是MC域(这个时候就只有2层域,MC与NUMA)。这样MC域里每个CORE都是一个sched_group,内核在调度的时候也可以很好的适应这样的场景。

MC域则是socket上CPU所有的CPU组成,而其中每个sg则为上级smtdomain的所有CPU构成。所以对于上图而言,MC的sg则由2个CPU组成。内核在MC域这样设计,可以让CFS调度类在唤醒负载均衡以及空闲负载均衡时,要求MC域的sg间需要均衡。

这个设计对于超线程来说很重要,我们在一些实际的业务里也可以观察到这样的情况。例如,我们有一项编解码的业务,发现它在某些虚拟机里的测试数据较好,而在某些虚拟机里的测试数据较差。通过分析后发现,这是由于是否往虚拟机透传超线程信息导致的。当我们向虚拟机透传超线程信息后,虚拟机会形成2层调度域(SMT与MC域),而在唤醒负载均衡的时候,CFS会倾向于将业务调度到空闲的sg上(即空闲的物理CORE,而不是空闲的CPU),这个时候业务在CPU利用率不高(没有超过40%)的时候,可以更加充分的利用物理CORE的性能(还是老问题,一个物理CORE上的超线程对,它们同时运行CPU消耗型业务时,所获得的性能增益只相当于单线程1.2倍左右。),从而获得较好的性能增益。而如果没有透传超线程信息,那么虚拟机只有一层物理拓扑结构(MC域),那么由于业务很可能被调度通过一个物理CORE的超线程对上,这样会导致系统无法充分利用物理CORE的性能,从而导致业务性能偏低。

NUMA域则是由系统里的所有CPU构成,SOCKET上的所有CPU构成一个sg,上图的NUMA域由2个sg构成。NUMA的sg之间需要有较大的不平衡时(并且这里的不平衡是sg级别的,即要sg上所有CPU负载总和与另外一个sg不平衡),才能进行跨NUMA的进程迁移(因为跨NUMA的迁移会导致L1L2L3的所有缓存热度损失,以及可能引发更多的跨NUMA内存访问,所以需要小心应对)。

从上面的介绍可以看到,通过sched_domain与sched_group的配合,内核能够适配各种物理拓扑结构(是否开启超线程、是否开启使用NUMA),高效的使用CPU资源。

smp_init

/**Calledbybootprocessortoactivatetherest.**在SMP架构里,BSP需要将其他的非bootcp全部bringup*/void__initsmp_init(void){intnum_nodes,num_cpus;unsignedintcpu;/*为每个CPU创建其idlethread*/idle_threads_init();/*向内核注册cpuhp线程*/cpuhp_threads_init();pr_info("BringingupsecondaryCPUs...\n");/**FIXME:Thisshouldbedoneinuserspace--RR**如果CPU没有online,则用cpu_up将其bringup*/for_each_present_cpu(cpu){if(num_online_cpus()=setup_max_cpus)break;if(!cpu_online(cpu))cpu_up(cpu);}.............}

在真正开始sched_init_smp调度域初始化之前,需要先bringup所有非bootcpu,保证这些CPU处于ready状态,然后才能开始多核调度域的初始化。

sched_init_smp

那这里我们来看看多核调度初始化具体的代码实现(如果没有配置CONFIG_SMP,那么则不会执行到这里的相关实现)

sched_init_numa

sched_init_numa()是用来检测系统里是否为NUMA,如果是的则需要动态添加NUMA域。

/**Topologylist,bottom-up.**Linux默认的物理拓扑结构**这里只有三级物理拓扑结构,NUMA域是在sched_init_numa()自动检测的*如果存在NUMA域,则会添加对应的NUMA调度域**注:这里默认的default_topology调度域可能会存在一些问题,例如*有的平台不存在DIE域(intel平台),那么就可能出现LLC与DIE域重叠的情况*所以内核会在调度域建立好后,在cpu_attach_domain()里扫描所有调度*如果存在调度重叠的情况,则会destroy_sched_domain对应的重叠调度域*/staticstructsched_domain_topology_leveldefault_topology[]={#ifdefCONFIG_SCHED_SMT{cpu_smt_mask,cpu_smt_flags,SD_INIT_NAME(SMT)},#endif#ifdefCONFIG_SCHED_MC{cpu_coregroup_mask,cpu_core_flags,SD_INIT_NAME(MC)},#endif{cpu_cpu_mask,SD_INIT_NAME(DIE)},{NULL,},};

Linux默认的物理拓扑结构

/**NUMA调度域初始化(根据硬件信息创建新的sched_domain_topology物理拓扑结构)**内核在默认情况下并不会主动添加NUMAtopology,需要根据配置(如果开启了NUMA)*如果开启了NUMA,这里就要根据硬件拓扑信息来判断是否需要添加*sched_domain_topology_level域(只有添加了这个域之后,内核才会在后面初始化*sched_domain的时候创建NUMADOMAIN)*/voidsched_init_numa(void){.................../**这里会根据distance检查是否存在NUMA域(甚至存在多级NUMA域),然后根据*情况将其更新到物理拓扑结构里。后面的建立调度域的时候,就会这个新的*物理拓扑结构来建立新的调度域*/for(j=1;jlevel;i++,j++){tl[i]=(structsched_domain_topology_level){.mask=sd_numa_mask,.sd_flags=cpu_numa_flags,.flags=SDTL_OVERLAP,.numa_level=j,SD_INIT_NAME(NUMA)};}sched_domain_topology=tl;sched_domains_numa_levels=level;sched_max_numa_distance=sched_domains_numa_distance[level-1];init_numa_topology_type();}

检测系统的物理拓扑结构,如果存在NUMA域则需要将其加到sched_domain_topology里,后面就会根据sched_domain_topology这个物理拓扑结构来建立相应的调度域。

sched_init_domains

下面接着分析sched_init_domains这个调度域建立函数

/**Setupschedulerdomainsandgroups.Fornowthisjustexcludesisolated*CPUs,butcouldbeusedtoexcludeotherspecialcasesinthefuture.*/intsched_init_domains(conststructcpumask*cpu_map){interr;zalloc_cpumask_var(sched_domains_tmpmask,GFP_KERNEL);zalloc_cpumask_var(sched_domains_tmpmask2,GFP_KERNEL);zalloc_cpumask_var(fallback_doms,GFP_KERNEL);arch_update_cpu_topology();ndoms_cur=1;doms_cur=alloc_sched_domains(ndoms_cur);if(!doms_cur)doms_cur=fallback_doms;/**doms_cur[0]表示调度域需要覆盖的cpumask**如果系统里用isolcpus=对某些CPU进行了隔离,那么这些CPU是不会加入到调度*域里面,即这些CPU不会参与到负载均衡(这里的负载均衡包括DL/RT以及CFS)。*这里用cpu_maphousekeeping_cpumask(HK_FLAG_DOMAIN)的方式将isolate*cpu去除掉,从而在保证建立的调度域里不包含isolatecpu*/cpumask_and(doms_cur[0],cpu_map,housekeeping_cpumask(HK_FLAG_DOMAIN));/*调度域建立的实现函数*/err=build_sched_domains(doms_cur[0],NULL);register_sched_domain_sysctl();returnerr;}

/**BuildscheddomainsforagivensetofCPUsandattachthescheddomains*totheindividualCPUs*/staticintbuild_sched_domains(conststructcpumask*cpu_map,structsched_domain_attr*attr){enums_allocalloc_state=sa_none;structsched_domain*sd;structs_datad;structrq*rq=NULL;inti,ret=-ENOMEM;structsched_domain_topology_level*tl_asym;boolhas_asym=false;if(WARN_ON(cpumask_empty(cpu_map)))gotoerror;/**Linux里的绝大部分进程都为CFS调度类,所以CFS里的sched_domain将会被频繁*的访问与修改(例如nohz_idle以及sched_domain里的各种统计),所以sched_domain*的设计需要优先考虑到效率问题,于是内核采用了percpu的方式来实现sched_domain*CPU间的每级sd都是独立申请的percpu变量,这样可以利用percpu的特性解决它们*间的并发竞争问题(1、不需要锁保护2、没有cachline伪共享)*/alloc_state=__visit_domain_allocation_hell(d,cpu_map);if(alloc_state!=sa_rootdomain)gotoerror;tl_asym=asym_cpu_capacity_level(cpu_map);/**SetupdomainsforCPUsspecifiedbythecpu_map:**这里会遍历cpu_map里所有CPU,为这些CPU创建与物理拓扑结构对应(*for_each_sd_topology)的多级调度域。**在调度域建立的时候,会通过tl-mask(cpu)获得cpu在该级调度域对应*的span(即cpu与其他对应的cpu组成了这个调度域),在同一个调度域里*的CPU对应的sd在刚开始的时候会被初始化成一样的(包括sd-pan、*sd-imbalance_pct以及sd-flags等参数)。*/for_each_cpu(i,cpu_map){structsched_domain_topology_level*tl;sd=NULL;for_each_sd_topology(tl){intdflags=0;if(tl==tl_asym){dflags

=SD_ASYM_CPUCAPACITY;has_asym=true;}sd=build_sched_domain(tl,cpu_map,attr,sd,dflags,i);if(tl==sched_domain_topology)*per_cpu_ptr(d.sd,i)=sd;if(tl-flagsSDTL_OVERLAP)sd-flags

=SD_OVERLAP;if(cpumask_equal(cpu_map,sched_domain_span(sd)))break;}}/**Buildthegroupsforthedomains**创建调度组**我们可以从2个调度域的实现看到sched_group的作用*1、NUMA域2、LLC域**numasched_domain-span会包含NUMA域上所有的CPU,当需要进行均衡的时候*NUMA域不应该以cpu为单位,而是应该以socket为单位,即只有socket1与socket2*极度不平衡的时候才在这两个SOCKET间迁移CPU。如果用sched_domain来实现这个*抽象则会导致灵活性不够(后面的MC域可以看到),所以内核会以sched_group来*表示一个cpu集合,每个socket属于一个sched_group。当这两个sched_group不平衡*的时候才会允许迁移**MC域也是类似的,CPU可能是超线程,而超线程的性能与物理核不是对等的。一对*超线程大概等于1.2倍于物理核的性能。所以在调度的时候,我们需要考虑超线程*对之间的均衡性,即先要满足CPU间均衡,然后才是CPU内的超线程均衡。这个时候*用sched_group来做抽象,一个sched_group表示一个物理CPU(2个超线程),这个时候*LLC保证CPU间的均衡,从而避免一种极端情况:超线程间均衡,但是物理核上不均衡*的情况,同时可以保证调度选核的时候,内核会优先实现物理线程,只有物理线程*用完之后再考虑使用另外的超线程,让系统可以更充分的利用CPU算力*/for_each_cpu(i,cpu_map){for(sd=*per_cpu_ptr(d.sd,i);sd;sd=sd-parent){sd-span_weight=cpumask_weight(sched_domain_span(sd));if(sd-flagsSD_OVERLAP){if(build_overlap_sched_groups(sd,i))gotoerror;}else{if(build_sched_groups(sd,i))gotoerror;}}}/**CalculateCPUcapacityforphysicalpackagesandnodes**sched_group_capacity是用来表示sg可使用的CPU算力**sched_group_capacity是考虑了每个CPU本身的算力不同(最高主频设置不同、*ARM的大小核等等)、去除掉RT进程所使用的CPU(sg是为CFS准备的,所以需要*去掉CPU上DL/RT进程等所使用的CPU算力)等因素之后,留给CFSsg的可用算力(因为*在负载均衡的时候,不仅应该考虑到CPU上的负载,还应该考虑这个sg上的CFS*可用算力。如果这个sg上进程较少,但是sched_group_capacity也较小,也是*不应该迁移进程到这个sg上的)*/for(i=nr_cpumask_bits-1;i=0;i--){if(!cpumask_test_cpu(i,cpu_map))continue;for(sd=*per_cpu_ptr(d.sd,i);sd;sd=sd-parent){claim_allocations(i,sd);init_sched_groups_capacity(i,sd);}}/*Attachthedomains*/rcu_read_lock();/**将每个CPU的rq与rd(root_domain)进行绑定,并且会检查sd是否有重叠*如果是的则需要用destroy_sched_domain()将其去掉(所以我们可以看到*intel的服务器是只有3层调度域,DIE域其实与LLC域重叠了,所以在这里*会被去掉)*/for_each_cpu(i,cpu_map){rq=cpu_rq(i);sd=*per_cpu_ptr(d.sd,i);/*UseREAD_ONCE()/WRITE_ONCE()toavoidload/storetearing:*/if(rq-cpu_capacity_origREAD_ONCE(d.rd-max_cpu_capacity))WRITE_ONCE(d.rd-max_cpu_capacity,rq-cpu_capacity_orig);cpu_attach_domain(sd,d.rd,i);}rcu_read_unlock();if(has_asym)static_branch_inc_cpuslocked(sched_asym_cpucapacity);if(rqsched_debug_enabled){pr_info("rootdomainspan:%*pbl(maxcpu_capacity=%lu)\n",cpumask_pr_args(cpu_map),rq-rd-max_cpu_capacity);}ret=0;error:__free_domain_allocs(d,alloc_state,cpu_map);returnret;}

到目前为止,我们已经将内核的调度域构建起来了,CFS可以利用sched_domain来完成多核间的负载均衡了。

结语

本文主要介绍了内核调度器的基本概念,并通过分析5.4内核中调度器的初始化代码,介绍了调度域、调度组等基本概念的具体落地方式。整体上,5.4内核相比3.x内核,在调度器初始化逻辑,以及调度器相关的基本设计(概念/关键结构)上没有本质的变化,也从侧面印证了内核调度器设计的“稳定”和“优雅”。

预告:本系列下一篇文章将聚焦Linux内核调度器的基本原理和基础框架构及相关源码,敬请期待。

往期精选推荐

全球边缘计算大会深圳站,SuperEdge开源边缘云生态分享

GettingStartedandBeyond|云原生应用负载均衡选型指南

腾讯云内核容器产品团队招聘啦~有才华的你,一定要来!

混部之殇-论云原生资源隔离技术之CPU隔离(一)

腾讯云内核容器产品团队长期招聘中~预览时标签不可点收录于话题#个上一篇下一篇



转载请注明地址:http://www.sanbaicaoasb.com/sczz/8557.html
  • 上一篇文章:
  • 下一篇文章:
  • 热点文章

    • 没有热点文章

    推荐文章

    • 没有推荐文章