一文读懂 | CPU负载均衡实现

在《一文读懂 | 进程怎么绑定 CPU》这篇文章中介绍过,在 Linux 内核中会为每个 CPU 创建一个可运行进程队列,由于每个 CPU 都拥有一个可运行进程队列,那么就有可能会出现每个可运行进程队列之间的进程数不一样的问题,这就是所谓的 负载不均衡 问题,如下图所示:

(图1)

最极端的情况是,一个 CPU 的可运行进程队列拥有非常多的进程,而其他 CPU 的可运行进程队列为空,这就是著名的 一核有难,多核围观,如下图:

(图2)

为了避免这个问题的出现,Linux 内核实现了 CPU 可运行进程队列之间的负载均衡。接下来,我们将会介绍 CPU 间的负载均衡的实现原理。

本文使用的内核版本为:Linux-2.6.23

CPU 间负载均衡原理

CPU 间负载不均衡的根本原因就是,CPU 的可运行进程队列中的进程数量不均衡导致的。所以,要解决 CPU 间负载不均衡的方法就是:将最繁忙的 CPU 可运行进程队列的一些进程迁移到其他比较空闲的 CPU 中,从而达到 CPU 间负载均衡的目的。

当然,在 2.6.0 版本的内核的确是这样实现的,我们可以看看其实现代码:

static void 
load_balance(runqueue_t *this_rq, int idle, cpumask_t cpumask)
{int imbalance, idx, this_cpu = smp_processor_id();runqueue_t *busiest;prio_array_t *array;struct list_head *head, *curr;task_t *tmp;// 1. 找到最繁忙的 CPU 运行队列busiest = find_busiest_queue(this_rq, this_cpu, idle, &imbalance, cpumask);if (!busiest)goto out;...head = array->queue + idx;curr = head->prev;skip_queue:// 2. 从最繁忙运行队列中取得一个进程tmp = list_entry(curr, task_t, run_list);...// 3. 把进程从最繁忙的可运行队列中迁移到当前可运行队列中pull_task(busiest, array, tmp, this_rq, this_cpu);...
}

load_balance 函数主要用于解决 CPU 间负载均衡问题,其主要完成以下 3 个步骤:

  • 从所有 CPU 的可运行队列中找到最繁忙的可运行队列。

  • 从最繁忙可运行队列中取得一个进程。

  • 把进程从最繁忙的可运行队列中迁移到当前可运行队列中。

这是 2.6.0 版本的解决方案,但这个方案并不是最优的,因为现代 CPU 架构是非常复杂的,比如一个物理 CPU 有多个核心(多核),而每个核心又可以通过超线程(Hyper-Threading)来实现多个逻辑 CPU,如下图所示:

(图3)

如上图所示,一个物理 CPU 中拥有 4 个核心,而每个核心又拥有 2 个超线程。在 Linux 内核中,会为每个超线程定义一个可运行进程队列,所以 Linux 内核会为上面的 CPU 定义 8 个可运行进程队列。

现在问题来了,在上面的 CPU 架构中,不同的可运行队列之间的进程迁移代价是不一样的。因为同一个核心的不同超线程共用了所有的缓存,所以同一个核心不同超线程间的进程迁移代价是最小的。

而同一个物理 CPU 不同核心间也会共用某些缓存,所以不同核心间的进程迁移的代价会比同一核心不同超线程间的进程迁移稍大。由于现在很多主板都支持安装多个物理 CPU,而不同物理 CPU 间基本不会共用缓存,所以不同物理 CPU 间的进程迁移代价最大。如下图所示(图中的 L1、L2 和 L3 分别指一级、二级和三级缓存):

(图4)

为了解决进程迁移成本的问题,新版本的 Linux 内核引入了 调度域 和 调度组

调度域与调度组

从前面的分析可知,根据 CPU 的物理架构可以划分为:不同的物理 CPU、相同 CPU 不同的核心、相同核心不同的超线程等,如下图所示:

(图5)

在 Linux 内核中,把这个层级成为 调度域。从前面的分析可知,越下层的调度域共用的缓存就越多,所以在进程迁移时,优先从底层的调度域开始进行。

由于内核为每个超线程定义一个可运行队列,所以图 3 中的 CPU 拥有 8 个可运行队列。而根据不同的调度域,可以把这 8 个可运行队列划分为不同的 调度组,如下图所示:

(图6)

如上图所示,由于每个超线程都拥有一个可运行队列,所以图 3 的 CPU 拥有 8 个可运行队列,而这些可运行队列可以根据不同的核心来划分为 4 个调度组,而这 4 个调度组可以根据不同的物理 CPU 来划分成 1 个调度组。

由于越底层的调度域共用的缓存越多,所以对 CPU 可运行队列进行负载均衡时,优先从底层调度域开始。比如把 Thread0 可运行队列的进程迁移到 Thread1 可运行队列的代价要比迁移到 Thread2 可运行队列的小,这是由于 Thread0 与 Thread1 属于同一个核心,同一个核心共用所有的 CPU 缓存。

在 Linux 内核中,调度域使用 sched_domain 结构表示,而调度组使用 sched_group 结构表示。我们来看看 sched_domain 结构的定义:

struct sched_domain {struct sched_domain *parent;    /* top domain must be null terminated */struct sched_domain *child;     /* bottom domain must be null terminated */struct sched_group  *groups;    /* the balancing groups of the domain */cpumask_t            span;      /* span of all CPUs in this domain */...
};

下面介绍一下 sched_domain 结构各个字段的作用:

  • parent:由于调度域是分层的,上层调度域是下层的调度域的父亲,所以这个字段指向的是当前调度域的上层调度域。

  • child:如上所述,这个字段用来指向当前调度域的下层调度域。

  • groups:每个调度域都拥有一批调度组,所以这个字段指向的是属于当前调度域的调度组列表。

  • span:这个字段主要用来标记属于当前调度域的 CPU 列表(每个位表示一个 CPU)。

我们接着分析一下 sched_group 结构,其定义如下:

struct sched_group {struct sched_group *next;cpumask_t           cpumask;...
};

下面介绍一下 sched_group 结构各个字段的作用:

  • next:指向属于同一个调度域的下一个调度组。

  • cpumask:用于标记属于当前调度组的 CPU 列表(每个位表示一个 CPU)。

它们之间的关系如下图所示:

(图7)

CPU 间负载均衡实现

要实现 CPU 间的负载均衡,只需要将最繁忙的可运行队列中的一部分进程迁移到空闲的可运行队列中即可。但由于 CPU 缓存的原因,对使用不同的 CPU 缓存的可运行队列之间进行进程迁移,将会导致缓存丢失,从而导致性能损耗。所以,Linux 内核会优先对使用相同 CPU 缓存的可运行队列之间进行进程迁移。

1. CPU 间负载均衡触发时机

当 CPU 的负载不均衡时,内核就需要对 CPU 进行负载均衡。负载均衡的触发时机比较多,如进程被创建、进程被唤醒、进程休眠和时钟中断等,这里我们介绍一下在时钟中断时怎么进行 CPU 间的负载均衡。

在 Linux 内核中是通过 rq 结构来描述一个可运行进程队列的,它有个名为 sd 的字段用于指向其所属的 调度域 层级的最底层,如下所示:

struct rq {...struct sched_domain *sd;...
}

它与调度域和调度组的关系如下图所示:

(图8)

在时钟中断下半部处理中,会通过调用 run_rebalance_domains 函数来对 CPU 进行负载均衡处理,而 run_rebalance_domains 接着会通过调用 rebalance_domains 函数来完成负载均衡的工作,其实现如下:

static inline void 
rebalance_domains(int cpu, enum cpu_idle_type idle)
{int balance = 1;struct rq *rq = cpu_rq(cpu);unsigned long interval;struct sched_domain *sd;unsigned long next_balance = jiffies + 60*HZ;int update_next_balance = 0;// 遍历可运行队列的调度组层级 (从最底层开始)for_each_domain(cpu, sd) {...// 由于对 CPU 进行负载均衡可能会导致 CPU 缓存丢失// 所以对 CPU 进行负载均衡不能太频繁, 必须隔一段时间才能进行// 这里就是判断上次进行负载均衡与这次的间隔是否已经达到合适的时间// 如果时间间隔已经达到一段时间, 那么就调用 load_balance 函数进行负载均衡if (time_after_eq(jiffies, sd->last_balance + interval)) {if (load_balance(cpu, rq, sd, idle, &balance)) {idle = CPU_NOT_IDLE;}sd->last_balance = jiffies;}...}...
}

由于每个 CPU(超线程)都有一个可运行队列,而 rebalance_domains 函数的工作就是获取当前 CPU (超线程)的可运行队列,然后从最底层开始遍历其调度域层级(由于越底层的调度域,进行进程迁移的代价越小)。

由于对 CPU 进行负载均衡可能会导致 CPU 缓存丢失,所以对 CPU 进行负载均衡不能太频繁(需要隔一段时间才能进行)。那么在对 CPU 进行负载均衡前,就需要判断上次进行负载均衡与这次的时间间隔是否合理。如果时间间隔合理, 那么就调用 load_balance 函数对调度域进行负载均衡。

load_balance 函数实现如下:

static int
load_balance(int this_cpu, struct rq *this_rq, struct sched_domain *sd,enum cpu_idle_type idle, int *balance)
{...redo:// 1. 从调度域中找到一个最繁忙的调度组group = find_busiest_group(sd, this_cpu, &imbalance, idle, &sd_idle,&cpus, balance);...// 2. 从最繁忙的调度组中找到一个最繁忙的运行队列busiest = find_busiest_queue(group, idle, imbalance, &cpus);...if (busiest->nr_running > 1) {...// 3. 从最繁忙的运行队列中迁移一些任务到当前任务队列ld_moved = move_tasks(this_rq, this_cpu, busiest, imbalance, sd, idle,&all_pinned);...}...return 0;
}

load_balance 函数主要完成 3 个工作:

  • 从 调度域 中找到一个最繁忙的 调度组

  • 从最繁忙的 调度组 中找到一个最繁忙的 可运行队列

  • 从最繁忙的 可运行队列 中迁移一些任务到当前 可运行队列

这样就完成了 CPU 间的负载均衡处理。


推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

专辑|C语言

我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~

嵌入式Linux

微信扫描二维码,关注我的公众号

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/465783.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NA-NP-IE系列实验28:HDLC 和PPP 封装

实验28:HDLC 和PPP 封装<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />1. 实验目的通过本实验&#xff0c;读者可以掌握如下技能&#xff1a;&#xff08;1&#xff09; 串行链路上的封装概念&#xff08;2&#xff09; HDLC 封…

使用git提交到github,每次都要输入用户名和密码的解决方法

使用git提交文件到github,每次都要输入用户名和密码&#xff0c;操作起来很麻烦&#xff0c;以下方法可解决&#xff0c;记录以下。 原因&#xff1a;在clone 项目的时候&#xff0c;使用了 https方式&#xff0c;而不是ssh方式。 默认clone 方式是&#xff1a;https 切换到&am…

回家一趟

大家好&#xff0c;我是写代码的篮球球痴。最近休年假回了一趟家里&#xff0c;决定回来也比较仓促&#xff0c;那天在公司的36楼发呆&#xff0c;觉得心里有点东西&#xff0c;然后就特别想回家看看。从晚上8&#xff1a;30出发&#xff0c;到第二天的中午&#xff0c;我从广东…

Spring MVC 使用介绍(二)—— DispatcherServlet

一、Hello World示例 1、引入依赖 <dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version><scope>provided</scope> </dependency> <dependency&g…

单片机如何检测市电通断?(应用甚广~)

我们在topemic网站上分享过一篇题为"单片机检测220V交流电通断电路"的文章&#xff0c;目前有近万次阅读&#xff0c;在这里做个总结分享给没有读过该文的公众号朋友。废话不多说&#xff0c;直接上图&#xff1a;该电路工作原理如下&#xff1a;当220V断开时&#x…

十年经验工程师为何被裁?

事件去年年底&#xff0c;公司来了一位工作十余年的工程师&#xff0c;据说软件硬件都会&#xff0c;应聘的岗位是XX算法工程师。比较巧的是&#xff0c;这位工程师是我上一家公司隔壁部门的同事。我们均来自大厂&#xff0c;但是是国企&#xff0c;二线城市。所以尽管他在前公…

飞康CEO:敢于向传统的灾备法则说“不”

近日&#xff0c;借美国飞康软件公司创办人兼首席执行官胡艾瑞徵先生访华期间&#xff0c;比特网记者对其进行了独家专访。 飞康软件公司成立于2000年&#xff0c;在过去的9年中&#xff0c; IPStor已经发展成为飞康包括整合重复数据删除功能的虚拟磁带库(VTL)、持续数据保护(C…

做10多年测试的老何

题图&#xff1a;老何是我的同事&#xff0c;他也喜欢篮球&#xff0c;因为篮球&#xff0c;我们两的话题比较多。老何做了十几年的测试工作。我们比较有猿粪的是&#xff0c;12年我在TCL&#xff0c;当时他也在TCL工业研究院&#xff0c;我们两的办公室也就相邻两栋楼。不过那…

推荐一个C++大佬

这里向大家推荐一个优质C公众号&#xff0c;号主程序喵&#xff0c;硕士毕业&#xff0c;浸淫C多年&#xff0c;帮助过不少C新手入门和进阶&#xff0c;可以说一句“精通C”啦。他搞过人脸识别&#xff0c;研究过自动驾驶&#xff0c;人生就是不断的挑战自我&#xff0c;现在从…

Silverlight HLSL实现背景滚动

一个Silverlight HLSL的简单例子&#xff0c;通过HLSL实现图片上的像素点的的水平移动&#xff0c;从而实现一个滚动背景的效果。 首先把Shader写出来吧。我这里借助了Shazzam &#xff0c;界面如下&#xff1a; 下面是我的HLSL&#xff1a; sampler2D input : register(s0);//…

极简的 PNG 编码函数 svpng(),用来学习C语言,真的很爽

这个是在知乎上看到的大神写的文章&#xff0c;如果是学习C语言入门的&#xff0c;我觉得可以从这个入手&#xff0c;特别是对图像感兴趣的。文章中提到的「我」&#xff0c;指的是「Milo Yip」大神。1. 什么是png格式图片&#xff1f;相对地&#xff0c;PNG&#xff08;Por…

STM32项目(六)—— 中文电子捡货标签

STM32项目&#xff08;六&#xff09;—— 中文电子捡货标签 宗旨&#xff1a;技术的分享是有限的&#xff0c;分享的精神是无限的。 传统物流行业仓储拣货采用纸单作业&#xff0c;拣货完成后再进行验货、出货&#xff0c;容易造成拣货错误、拣货速度与效率低、新员工培训时间…

如何把Linux工具里的“军刀”BusyBox移植到RT-Thread Smart?

RT-Thread Smart 系列连载序号内容1《当“树莓派”遇上RT-Thread Smart——应用编程入门》2《RT-Thread Smart和树莓派&#xff1a;wget & cURL网络客户端》3《如何把Linux工具里的“军刀”BusyBox移植到RT-Thread Smart&#xff1f;》4sdl图形类应用5dropbear及ssh server…

STM32项目(七) —— 智能仓库管理系统

智能仓库管理系统 随着经济的发展&#xff0c;对企业的生产经营要求提高&#xff0c;企业必须综合利用各种先进技术&#xff0c;在网络与信息技术的支持下&#xff0c;改进现在的生产经营模式和组织结构&#xff0c;增加利润。随着企业规模的扩大。高效方便的仓库管理系统&…

花三千块钱求推荐一个靠谱的C++工程师

直接说重点有个朋友想招一个C工程师&#xff0c;因为项目的原因&#xff0c;不可能现在招新人培养&#xff0c;想招到一个熟练C的工程师&#xff0c;所以想在公众号里面广而告之。如果是你推荐的人入职后&#xff0c;就可以获得三千奖励&#xff0c;如果是你本人入职&#xff0…

老外码农酒后吐槽,该说的不该说的全说了!!

上个月&#xff0c;一个有着10年码龄的程序猿喝高了&#xff0c;在社交网站Reddit上吐槽。然后被疯狂转发&#xff0c;点赞。可说是讲出了咱们很多码农的心声。咱们国内这边也有不少版本了。我们也凑凑热闹翻译一版。跟大家一块儿欣赏一下&#xff1a;今天是有点儿高了&#xf…

Intel官宣开发RISC-V处理器:明年首发7nm工艺

近日业界盛传&#xff0c;Intel计划以20亿美元收购RISC-V IP供应商SiFive——后者的产品已被80多家公司采纳&#xff0c;设计了200多种产品&#xff0c;出货量极大&#xff0c;广泛用于各种加速器。虽然双方对于收购都拒绝置评&#xff0c;但深入合作已经展开。Intel官方宣布&a…

SCCM2007 R2的部署前准备,SCCM系列之一

SCCM 2007 R2的部属前准备<?xml:namespace prefix o />System Center 是微软著名的管理平台软件&#xff0c;在实现Microsoft的MOF&ITIL的IT管理理念的过程中发挥了很重要的作用&#xff0c;System Center可以在MOF的每一个运维象限中都有对应的System Center产品协…

硬件基础 —— 电阻

硬件基础 —— 电阻1、电阻基本知识电阻&#xff1a;在电路中对电流有阻碍作用并且造成能量消耗的部分。主要物理特性是变电能为热能&#xff08;耗能元件&#xff09;&#xff0c;符号R&#xff08;单位&#xff1a;Ω&#xff09;。2、电阻的作用&#xff1a;分流、限流、分压…

史上最全的LED点灯程序,你都掌握了吗?

摘要&#xff1a;你点亮过多少板子的LED灯呢&#xff1f;有很多小伙伴留言说讲一下STM32、FPGA、Liunx他们之间有什么不同。不同点很多&#xff0c;口说无凭&#xff0c;今天就来点亮一下STM32、FPGA和Liunx板子的LED灯&#xff0c;大家大致看一下点灯流程和点灯环境以及点灯流…