欢迎光临散文网 会员登陆 & 注册

Linux内核的进程负载均衡机制

2021-04-10 21:14 作者:后端攻城狮哇  | 我要投稿

概述

在多核系统中,为了更好的利用多CPU并行能力,进程调度器可以将进程负载尽可能的平均到各个CPU上。再具体实现中,如何选择将进程迁移到的目标CPU,除了考虑各个CPU的负载平衡,还需要将Cache利用纳入权衡因素。同时,对于进程A唤醒进程B这个模型,还做了特殊的处理。本文分析以Centos kernel 3.10.0-975源码为蓝本。

SMP负载均衡模型

问题

如果只是将CPU负载平均的分布在各个CPU上,那么就无所谓需要调度域。但是由于Cache以及内存Numa的存在,使得进程最好能迁移到与之前运行所在CPU更'近'的CPU上。

以我们常用的Intel X86为例。Cache基本视图如下图:

从Cache和内存访问的视角,如果进程负载均衡需要把进程A迁移到另一个CPU上,

  • 如果目标CPU和进程A之前所在CPU正好是同一个物理CPU同一个核心上(超线程),那么Cache利用率最好,毕竟L1,L2和L3中还是'热'的。

  • 如果目标CPU和进程A之前所在CPU正好是同一个物理CPU但不同核心上(多核),那么Cache利用率次之,L3中还有'热'数据。

  • 如果目标CPU和进程A之前所在CPU正好是同一个NUMA但是不同物理CPU上(多NUMA结构),虽然Cache已经是'冷'了,但至少内存访问还是在本NUMA中。

  • 如果目标CPU和进程A之前所在CPU在不同NUMA中,不但Cache是'冷'的,跨NUMA内存还有惩罚,此时内存访问速度最差。

SMP组织

为了更好地利用Cache,内核将CPU(如果开启了超线程,那么以逻辑CPU为单位,否则以物理CPU核心为单位)组织成了调度域。

逻辑视角

假设某机器为2路4核8核心CPU,它的CPU调度域逻辑上如下图:

2路NUMA最为简单,如果是4路NUMA,那么这个视图在NUMA层级将会复杂很多,因为跨NUMA访问根据访问距离导致访问延时还不相同,这部分最后讨论。

分层视角

所有CPU一共分为三个层次:SMT,MC,NUMA,每层都包含了所有CPU,但是划分粒度不同。根据Cache和内存的相关性划分调度域,调度域内的CPU又划分一次调度组。越往下层调度域越小,越往上层调度域越大。进程负载均衡会尽可能的在底层调度域内部解决,这样Cache利用率最优。

从分层的视角分析,下图是调度域实际组织方式,每层都有per-cpu数组保存每个CPU对应的调度域和调度组,它们是在初始化时已经提前分配的内存。值得注意的是

  • 每个CPU对应的调度域数据结构都包含了有效的内容,比如说SMT层中,CPU0和CPU1对应的不同调度域数据结构,内容是一模一样的。

  • 每个CPU对应的调度组数据结构不一定包含了有效内容,比如说MC层中,CPU0和CPU1指向不同的struct sched_domain,但是sched_domain->groups指向的调度组确是同样的数据结构,这些调度组组成了环。

单CPU视角

从单个CPU的视角分析,下图是调度域实际组织方式。

每个CPU的进程运行队列有一个成员指向其所在调度域。从最低层到最高层。

我们可以在/proc/sys/kernel/sched_domain/cpuX/ 中看到CPU实际使用的调度域个数以及每个调度域的名字和配置参数。

负载均衡时机

  • 周期性调用进程调度程序scheduler_tick()->trigger_load_balance()中,通过软中断触发负载均衡。

  • 某个CPU上无可运行进程,__schedule()准备调度idle进程前,会尝试从其它CPU上pull一批进程过来。

周期性负载均衡

CPU对应的运行队列数据结构中记录了下一次周期性负载均衡的时间,当超过这个时间点后,将触发SCHED_SOFTIRQ软中断来进行负载均衡。


以下是rebalance_domains()函数核心流程,值得注意的是,每个层级的调度间隔不是固定的,而是临时计算出来,他在一个可通过proc接口配置的最小值和最大值之间。

以下是对CPU的每个层级调度域调用load_balance()函数核心流程,目的是把一些进程迁移到指定的CPU(该场景就是当前CPU)。

以我的服务器为例,观察不同层级调度域的调度间隔范围,时间单位为jiffies。

可见,SMT负载均衡频率最高,越往上层越低。这也符合体系结构特点,在越低层次迁移进程代价越小(Cache利用率高),所以可以更加频繁一点。

CPU进入idle前负载均衡

当进程调度函数__schedule()把即将切换到idle进程前,会发生一次负载均衡来避免当前CPU空闲。

s


核心函数idle_balance()。基本上也是尽可能在低层调度域中负载均衡。


其它需要用到SMP负载均衡模型的时机

内核运行中,还有部分情况中需要用掉SMP负载均衡模型来确定最佳运行CPU:

  • 进程A唤醒进程B时,try_to_wake_up()中会考虑进程B将在哪个CPU上运行。

  • 进程调用execve()系统调用时。

  • fork出子进程,子进程第一次被调度运

唤醒进程时

当A进程唤醒B进程时,假设都是普通进程,那么将会调用try_to_wake_up()->select_task_rq()->select_task_rq_fair()

调用execve()系统调用时


fork的子进程第一次被调度运行时

do_fork()->wake_up_new_task()

Linux、C/C++技术交流群:960994558整理了一些个人觉得比较好的学习书籍、大厂面试题、和热门技术教学视频资料共享在里面(包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等等.),有需要的可以自行添加哦!~


Linux内核的进程负载均衡机制的评论 (共 条)

分享到微博请遵守国家法律