linux在多核处理器上的负载均衡原理_多核服务器为什么没有平均分配任务-程序员宅基地

技术标签: linux调度  负载均衡  linux  内核  

现在互联网公司使用的都是多CPU(多核)的服务器了,Linux操作系统会自动把任务分配到不同的处理器上,并尽可能的保持负载均衡。那Linux内核是怎么做到让各个CPU的压力均匀的呢?

做一个负载均衡机制,重点在于:

1. 何时检查并调整负载情况?
 

2. 如何调整负载?

先看第一个问题。

如果让我这样的庸俗程序员来设计,我第一个想到的就是每隔一段时间检查一次负载是否均衡,不均则调整之,这肯定不是最高效的办法,但肯定是实现上最简单的。实际上,2.6.20版linux kernel的确使用软中断来定时调整多CPU上的压力(调用函数run_rebalance_domains),每秒1次

但每秒一次还是不能满足要求,对很多应用来说,1秒太长了,一秒钟内如果发生负载失衡对很多web应用都是不能接受的,何况其他实时应用。最好kernel能够紧跟进程的变化来调整。

那么,好,我们在进程创建和进程exit的时候检查并调整负载呢?可以,但是不完整,一个进程创建以后如果频繁的睡眠、醒来、睡眠、醒来,它这样折腾对CPU的负载是有影响的,你就不管它了吗?说到底,我们其实关注的是进程是否在使用CPU,而不是它是否诞生了。所以,我们应该在进程睡眠和醒来这两个时间点检查CPU们的负载。

再看第二个问题,怎么调整负载呢?从最繁忙的那个CPU上挪一个进程到最闲的那个CPU上,如果负载还不均衡,就再挪一个进程,如果还不均衡,继续挪....这也是个最笨的方法,但它却真的是linux CPU负载均衡的核心,不过实际的算法在此基础上有很多细化。对于Intel的CPU,压缩在同一个chip上的多核是共享同一个L2的(如下图,里面的一个Processor其实就是一个chip),如果任务能尽可能的分配在同一个chip上,L2 cache就可以继续使用,这对运行速度是有帮助的。所以除非“很不均衡”,否则尽量不要把一个chip上的任务挪到其他chip上。

于是,为了应对这种CPU core之间的异质性——在不同的core之间迁移任务,代价不同——Linux kernel引入了sched_domain和sched_group的概念。sched_domain和sched_group的具体原理,可参考刘勃的文章英文资料

【代码剖析】

 

SMP负载均衡检查或调整在两个内核函数里发生:

1. schedule()。当进程调用了sleep、usleep、poll、epoll、pause时,也就是调用了可能睡去的操作时都会转为内核代码里对schedule()函数的调用。

2. try_to_wake_up() 。说白了就是进程刚才睡了,现在要醒来,那醒来以后跑在哪个CPU上呢?这个选择CPU的过程,也就是负载均衡的过程。

我们先看schedule()的代码,我们忽略函数前面那些和负载均衡无关的代码(本文代码以内核2.6.20版为准):

[kernel/sched.c --> schedule() ]

 

  3489     cpu = smp_processor_id();
  3490     if (unlikely(!rq->nr_running)) {
   3491         idle_balance(cpu, rq);
  3492         if (!rq->nr_running) {
  3493             next = rq->idle;
  3494             rq->expired_timestamp = 0;
  3495             wake_sleeping_dependent(cpu);
  3496             goto switch_tasks;
  3497         }
  3498     }

每个CPU都有一个运行队列即这里的 rq,运行队列里放着该CPU要运行的进程,如果运行队列里没有进程了,就说明当前CPU没有可调度的任务了,那就要调用idle_balance从其它CPU上“平衡”一些(就是挪一些)进程到当前rq里。

 
再看 idle_balance()的实现:

[kernel/sched.c --> idle_balance()]
 
  2806 /*
  2807  * idle_balance is called by schedule() if this_cpu is about to become
  2808  * idle. Attempts to pull tasks from other CPUs.
  2809  */
  2810 static void idle_balance(int this_cpu, struct rq *this_rq)
  2811 {
  2812     struct sched_domain *sd;
  2813     int pulled_task = 0;
  2814     unsigned long next_balance = jiffies + 60 *  HZ;
  2815
  2816     for_each_domain(this_cpu, sd) {
  2817         unsigned long interval;
  2818
  2819         if (!(sd->flags & SD_LOAD_BALANCE))
  2820             continue;
  2821
  2822         if (sd->flags & SD_BALANCE_NEWIDLE)
  2823             /* If we've pulled tasks over stop searching: */
  2824             pulled_task = load_balance_newidle(this_cpu,
  2825                                 this_rq, sd);
  2826
  2827         interval = msecs_to_jiffies(sd->balance_interval);
  2828         if (time_after(next_balance, sd->last_balance + interval))
  2829             next_balance = sd->last_balance + interval;
  2830         if (pulled_task)
  2831             break;
  2832     }
  2833     if (!pulled_task)
  2834         /*
  2835          * We are going idle. next_balance may be set based on
  2836          * a busy processor. So reset next_balance.
  2837          */
  2838         this_rq->next_balance = next_balance;
  2839 }

 
从子 sched_domain到父sched_domain遍历该CPU对应的domain(2816行),并调用load_balance_newidle,我们继续:

[kernel/sched.c --> load_balance_newidle()]
 
2730 static int
  2731 load_balance_newidle(int this_cpu, struct rq *this_rq, struct sched_domain *sd)
  2732 {
  2733     struct sched_group *group;
  2734     struct rq *busiest = NULL;
  2735     unsigned long imbalance;
  2736     int nr_moved = 0;
  2737     int sd_idle = 0;
  2738     cpumask_t cpus = CPU_MASK_ALL;
  2739
  2740     /*
  2741      * When power savings policy is enabled for the parent domain, idle
  2742      * sibling can pick up load irrespective of busy siblings. In this case,
  2743      * let the state of idle sibling percolate up as IDLE, instead of
  2744      * portraying it as NOT_IDLE.
  2745      */
  2746     if (sd->flags & SD_SHARE_CPUPOWER &&
  2747         !test_sd_parent(sd, SD_POWERSAVINGS_BALANCE))
  2748         sd_idle = 1;
  2749
  2750     schedstat_inc(sd, lb_cnt[NEWLY_IDLE]);
  2751 redo:
  2752     group = find_busiest_group(sd, this_cpu, &imbalance, NEWLY_IDLE,
  2753                    &sd_idle, &cpus, NULL);
  2754     if (!group) {
  2755         schedstat_inc(sd, lb_nobusyg[NEWLY_IDLE]);
  2756         goto out_balanced;
  2757     }
  2758
  2759     busiest = find_busiest_queue(group, NEWLY_IDLE, imbalance,
  2760                 &cpus);
  2761     if (!busiest) {
  2762         schedstat_inc(sd, lb_nobusyq[NEWLY_IDLE]);
  2763         goto out_balanced;
  2764     }
  2765
  2766     BUG_ON(busiest == this_rq);
  2767
  2768     schedstat_add(sd, lb_imbalance[NEWLY_IDLE], imbalance);
  2769
  2770     nr_moved = 0;
  2771     if (busiest->nr_running > 1) {
  2772         /* Attempt to move tasks */
  2773         double_lock_balance(this_rq, busiest);
  2774         nr_moved = move_tasks(this_rq, this_cpu, busiest,
  2775                     minus_1_or_zero(busiest->nr_running),
  2776                     imbalance, sd, NEWLY_IDLE, NULL);

 
原来就是我们上面说的“笨办法”,针对当前CPU所属的每个domain(从子到父),找到该 sched_domain里最忙的sched_group(2752行),再从该group里找出最忙的运行队列(2759行),最后从该“最忙”运行队列里挑出几个进程到当前CPU的运行队列里。move_tasks函数到底挪多少进程到当前CPU是由第4和第5个参数决定的,第4个参数是指最多挪多少个进程,第5个参数是指最多挪多少“压力”。有了这两个参数限制,就不会挪过头了(即把太多进程挪到当前CPU,造成新的不均衡)。

举个例子,假如有一台8核的机器,两个CPU插槽,也就是两个chip,每个chip上4个核,再假设现在core 4最忙,core 0第二忙,如图:
 
按照 刘勃的文章里的提法,首先是core domain,即Processor 0属于domain 1,Processor 1属于domain 2,其中domain 1包含4个sched_group,每个group对应一个core,如下图(group未画出):
 
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wh8_2011/article/details/49699237

智能推荐

ubuntu12.04下安装QT_ubuntu 12.04 安装qt-程序员宅基地

文章浏览阅读3.3w次。下载QT creator :地址:http://qt-project.org/downloads下面方法小白的做法 有不对的地方希望大牛指出 刚入门Qt 不知如何安装学习 求指教一:输入以下命令:

GitHub快速入门_github 如何找仓库-程序员宅基地

文章浏览阅读1.6k次。1.账号注册Github快速入门提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加第一章 账号注册提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录Github快速入门前言一、账号注册二、使用查找仓库(repository)仓库管理Issue总结前言github 全球最大的开源代码网站。一、账号注册1.需要登录外网2.github.com3.sign up注:不需要真实的邮箱号码二、使用查找仓库(repository)github仓库_github 如何找仓库

更换JmGO G1投影仪调焦电机_坚果g1pro怎么换调焦-程序员宅基地

文章浏览阅读4.2k次。买的JmGO G1过保了,调焦不行。之前把调焦电机卸载了,某宝上找了好久,才找到个差不多的电机。抱着试试看的想法,淘了2个。如下图所示,和原来的相比,电机大小一样的,都是10*8mm,就是上面的齿轮形状不一样,还多了两个翅膀把上部分拆下来,发现和原有电机相比,少一个固定孔,本来想上面的减速部分直接用原来的电机头,发现螺丝孔距不一样,螺丝装不上去。只好用买来的新电机了,换上原来的齿轮头。这里..._坚果g1pro怎么换调焦

c++保留到小数点后n位_保留n位有效数字_c++保留小数点后几位怎么弄-程序员宅基地

文章浏览阅读2k次,点赞17次,收藏14次。结果自动的进行了四舍五入分别详细讲解上面的四种方法,包括它们的原理、异同以及推荐使用的情况。fixedfixedsetf()综上所述,为了确保输出的一致性和准确性,推荐使用第一种或第二种方法,因为它们明确指定了固定点表示法和精度,能够更好地控制输出的格式。_c++保留小数点后几位怎么弄

RK3568驱动指南|第七期-设备树-第57章 实例分析:中断_rk interrupts-程序员宅基地

文章浏览阅读496次。在gpio0的中断控制器为gic,在gic节点中#interrupt-cells属性被设置为3,这也就是为什么在gpio0节点中interrupts 属性有三个值,而ft5x06的中断控制器为gpio0,在gpio0节点中#interrupt-cells属性被设置为2,所以ft5x06节点的interrupts 属性只有两个值。中断信号源节点(例如设备节点或其他中断源节点)中的 interrupt-parent 属性用于指定中断信号源所属的中断控制器节点。中断信号源是产生中断的设备或其他中断源节点。_rk interrupts

Linux0.11 信号(十二)_linux0.11 do_signal-程序员宅基地

文章浏览阅读482次。信号机制是 Linux 0.11 为进程提供的一套"局部的类中断机制",即在进程执行的过程中,如果系统发现某个进程接收到了信号,就暂时打断进程的执行,转而去执行该进程的信号处理程序,处理完毕后,再从进程"被打断"之处继续执行。_linux0.11 do_signal

随便推点

EXCEL不求人实用技能大全汇总_excel技能大全-程序员宅基地

文章浏览阅读948次,点赞2次,收藏4次。excel实用技能汇总一.工作中常用的30个excel函数公式1.数字处理(1)取绝对值(2)取整(3)四舍五入(1)案例 =ABS()取整取整分为三种,分别是:(2.1)格式取整(也就是在单元格中通过格式控制显示为整数(四舍五入得到),复制其单元格到其他单元格里面的值依然包含小数点);(2.2)数值取整(非四舍五入):在单元格中通过公式取整 -..._excel技能大全

C++ for_each_c++ foreach (var item, pcfg->cloud_cfg)-程序员宅基地

文章浏览阅读57次。#include<vector>#include<string>#include<iostream>#include<algorithm>using namespace std;struct show{ int count; show (): count(0){} void operator()(const char& c){ cout << c; count ++; }};int main(){ vec_c++ foreach (var item, pcfg->cloud_cfg)

顺序表的创建;往顺序表的指定位置插入元素;从顺序表的指定位置删除元素_在顺序表的指定位置插入元素-程序员宅基地

文章浏览阅读5.8k次,点赞10次,收藏58次。顺序表的存储结构如下:typedef struct{ ElemType *elem; int length; int listsize;}SqList;顺序表的初始化如下:void InitList_Sq(SqList &L){ //构造一个空的线性表L L.elem = (ElemType *)malloc(LIST_..._在顺序表的指定位置插入元素

c# datetime._C#| DateTime.Year属性与示例-程序员宅基地

文章浏览阅读1k次。c# datetime. DateTime.Month属性 (DateTime.Month Property)DateTime.Month Property is used to get the year component of this object. It's a GET property of DateTime class. DateTime.Month属性用于获取此对象的年份组成部分..._datetime,.year()

matlab 求倾斜边缘,MTF的倾斜边缘法计算方法-程序员宅基地

文章浏览阅读1.2k次。MTF的倾斜边缘法计算方法简介光学系统性能的衡量方法有很多,常见的有点扩散函数法、瑞利判断法、点列图法、光学传递函数(MTF)法等,其中MTF法在光学系统和镜头加工制造中使用最为广泛。MTF曲线真实的反映了成像系统将物方信息传递到像方的能力。MTF曲线的横坐标一般是cycle/mm或者linepair/mm[1][11],纵坐标是反映对比度传递特性的像/物方调制度的比值。MTF的计算方法有很多,比..._matlab斜边超采样得到esf

RT-Smart ELF 应用程序加载运行过程分析-程序员宅基地

文章浏览阅读493次。在用户态应用程序处理的任务中,elf 加载运行是一个比较重要的步骤,下面就分析一下在 rt-smart 操作系统中,想要将一个应用程序运行起来要经过哪些步骤。ELF 格式介绍ELF 代表 Executable and Linkable Format。它是一种对可执行文件、目标文件和库使用的文件格式。它在 Linux 下成为标准格式已经很长时间,ELF 一个特别的优点在于,同一文件格式可以用于内核支..._rtt5.0 elf文件

推荐文章

热门文章

相关标签