LINUX内核CPU负载均衡机制【转】

转自:http://oenhan.com/cpu-load-balance

还是神奇的进程调度问题引发的,参看Linux进程组调度机制分析,组调度机制是看清楚了,发现在重启过程中,很多内核调用栈阻塞在了double_rq_lock函数上,而double_rq_lock则是load_balance触发的,怀疑当时的核间调度出现了问题,在某个负责场景下产生了多核互锁,后面看了一下CPU负载平衡下的代码实现,写一下总结。

内核代码版本:kernel-3.0.13-0.27。

内核代码函数起自load_balance函数,从load_balance函数看引用它的函数可以一直找到schedule函数这里,便从这里开始往下看,在__schedule中有下面一句话。

1

2

if (unlikely(!rq->nr_running))

idle_balance(cpu, rq);

从上面可以看出什么时候内核会尝试进行CPU负载平衡:即当前CPU运行队列为NULL的时候。
CPU负载平衡有两种方式:pull和push,即空闲CPU从其他忙的CPU队列中拉一个进程到当前CPU队列;或者忙的CPU队列将一个进程推送到空闲的CPU队列中。idle_balance干的则是pull的事情,具体push下面会提到。
在idle_balance里面,有一个proc阀门控制当前CPU是否pull:

1

2

if (this_rq->avg_idle < sysctl_sched_migration_cost)

return;

sysctl_sched_migration_cost对应proc控制文件是/proc/sys/kernel/sched_migration_cost,开关代表如果CPU队列空闲了500us(sysctl_sched_migration_cost默认值)以上,则进行pull,否则则返回。
for_each_domain(this_cpu, sd) 则是遍历当前CPU所在的调度域,可以直观的理解成一个CPU组,类似task_group,核间平衡指组内的平衡。负载平衡有一个矛盾就是:负载平衡的频度和CPU cache的命中率是矛盾的,CPU调度域就是将各个CPU分成层次不同的组,低层次搞定的平衡就绝不上升到高层次处理,避免影响cache的命中率。

图例如下;

最终通过load_balance进入正题。

首先通过find_busiest_group获取当前调度域中的最忙的调度组,首先update_sd_lb_stats更新sd的状态,也就是遍历对应的sd,将sds里面的结构体数据填满,如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

struct sd_lb_stats {

struct sched_group *busiest; /* Busiest group in this sd */

struct sched_group *this;  /* Local group in this sd */

unsigned long total_load;  /* Total load of all groups in sd */

unsigned long total_pwr;   /*    Total power of all groups in sd */

unsigned long avg_load;       /* Average load across all groups in sd */

/** Statistics of this group */

unsigned long this_load; //当前调度组的负载

unsigned long this_load_per_task; //当前调度组的平均负载

unsigned long this_nr_running; //当前调度组内运行队列中进程的总数

unsigned long this_has_capacity;

unsigned int  this_idle_cpus;

/* Statistics of the busiest group */

unsigned int  busiest_idle_cpus;

unsigned long max_load; //最忙的组的负载量

unsigned long busiest_load_per_task; //最忙的组中平均每个任务的负载量

unsigned long busiest_nr_running; //最忙的组中所有运行队列中进程的个数

unsigned long busiest_group_capacity;

unsigned long busiest_has_capacity;

unsigned int  busiest_group_weight;

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

do

{

local_group = cpumask_test_cpu(this_cpu, sched_group_cpus(sg));

if (local_group) {

//如果是当前CPU上的group,则进行赋值

sds->this_load = sgs.avg_load;

sds->this = sg;

sds->this_nr_running = sgs.sum_nr_running;

sds->this_load_per_task = sgs.sum_weighted_load;

sds->this_has_capacity = sgs.group_has_capacity;

sds->this_idle_cpus = sgs.idle_cpus;

} else if (update_sd_pick_busiest(sd, sds, sg, &sgs, this_cpu)) {

//在update_sd_pick_busiest判断当前sgs的是否超过了之前的最大值,如果是

//则将sgs值赋给sds

sds->max_load = sgs.avg_load;

sds->busiest = sg;

sds->busiest_nr_running = sgs.sum_nr_running;

sds->busiest_idle_cpus = sgs.idle_cpus;

sds->busiest_group_capacity = sgs.group_capacity;

sds->busiest_load_per_task = sgs.sum_weighted_load;

sds->busiest_has_capacity = sgs.group_has_capacity;

sds->busiest_group_weight = sgs.group_weight;

sds->group_imb = sgs.group_imb;

}

sg = sg->next;

} while (sg != sd->groups);

决定选择调度域中最忙的组的参照标准是该组内所有 CPU上负载(load) 的和, 找到组中找到忙的运行队列的参照标准是该CPU运行队列的长度, 即负载,并且 load 值越大就表示越忙。在平衡的过程中,通过比较当前队列与以前记录的busiest 的负载情况,及时更新这些变量,让 busiest 始终指向域内最忙的一组,以便于查找。
调度域的平均负载计算

1

2

3

sds.avg_load = (SCHED_POWER_SCALE * sds.total_load) / sds.total_pwr;

if (sds.this_load >= sds.avg_load)

goto out_balanced;

在比较负载大小的过程中, 当发现当前运行的CPU所在的组中busiest为空时,或者当前正在运行的 CPU队列就是最忙的时, 或者当前 CPU队列的负载不小于本组内的平均负载时,或者不平衡的额度不大时,都会返回 NULL 值,即组组之间不需要进行平衡;当最忙的组的负载小于该调度域的平均负载时,只需要进行小范围的负载平衡;当要转移的任务量小于每个进程的平均负载时,如此便拿到了最忙的调度组。
然后find_busiest_queue中找到最忙的调度队列,遍历该组中的所有 CPU 队列,经过依次比较各个队列的负载,找到最忙的那个队列。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

for_each_cpu(i, sched_group_cpus(group)) {

/*rq->cpu_power表示所在处理器的计算能力,在函式sched_init初始化时,会把这值设定为SCHED_LOAD_SCALE (=Nice 0的Load Weight=1024).并可透过函式update_cpu_power (in kernel/sched_fair.c)更新这个值.*/

unsigned long power = power_of(i);

unsigned long capacity = DIV_ROUND_CLOSEST(power,SCHED_POWER_SCALE);

unsigned long wl;

if (!cpumask_test_cpu(i, cpus))

continue;

rq = cpu_rq(i);

/*获取队列负载cpu_rq(cpu)->load.weight;*/

wl = weighted_cpuload(i);

/*

* When comparing with imbalance, use weighted_cpuload()

* which is not scaled with the cpu power.

*/

if (capacity && rq->nr_running == 1 && wl > imbalance)

continue;

/*

* For the load comparisons with the other cpu‘s, consider

* the weighted_cpuload() scaled with the cpu power, so that

* the load can be moved away from the cpu that is potentially

* running at a lower capacity.

*/

wl = (wl * SCHED_POWER_SCALE) / power;

if (wl > max_load) {

max_load = wl;

busiest = rq;

}

通过上面的计算,便拿到了最忙队列。
当busiest->nr_running运行数大于1的时候,进行pull操作,pull前对move_tasks,先进行double_rq_lock加锁处理。

1

2

3

4

double_rq_lock(this_rq, busiest);

ld_moved = move_tasks(this_rq, this_cpu, busiest,

imbalance, sd, idle, &all_pinned);

double_rq_unlock(this_rq, busiest);

move_tasks进程pull task是允许失败的,即move_tasks->balance_tasks,在此处,有sysctl_sched_nr_migrate开关控制进程迁移个数,对应proc的是/proc/sys/kernel/sched_nr_migrate。
下面有can_migrate_task函数检查选定的进程是否可以进行迁移,迁移失败的原因有3个,1.迁移的进程处于运行状态;2.进程被绑核了,不能迁移到目标CPU上;3.进程的cache仍然是hot,此处也是为了保证cache命中率。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

/*关于cache cold的情况下,如果迁移失败的个数太多,仍然进行迁移

* Aggressive migration if:

* 1) task is cache cold, or

* 2) too many balance attempts have failed.

*/

tsk_cache_hot = task_hot(p, rq->clock_task, sd);

if (!tsk_cache_hot ||

sd->nr_balance_failed > sd->cache_nice_tries) {

#ifdef CONFIG_SCHEDSTATS

if (tsk_cache_hot) {

schedstat_inc(sd, lb_hot_gained[idle]);

schedstat_inc(p, se.statistics.nr_forced_migrations);

}

#endif

return 1;

}

判断进程cache是否有效,判断条件,进程的运行的时间大于proc控制开关sysctl_sched_migration_cost,对应目录/proc/sys/kernel/sched_migration_cost_ns

1

2

3

4

5

6

7

static int

task_hot(struct task_struct *p, u64 now, struct sched_domain *sd)

{

s64 delta;

delta = now - p->se.exec_start;

return delta < (s64)sysctl_sched_migration_cost;

}

在load_balance中,move_tasks返回失败也就是ld_moved==0,其中sd->nr_balance_failed++对应can_migrate_task中的"too many balance attempts have failed",然后busiest->active_balance = 1设置,active_balance = 1。

1

2

3

4

5

if (active_balance)

//如果pull失败了,开始触发push操作

stop_one_cpu_nowait(cpu_of(busiest),

active_load_balance_cpu_stop, busiest,

&busiest->active_balance_work);

push整个触发操作代码机制比较绕,stop_one_cpu_nowait把active_load_balance_cpu_stop添加到cpu_stopper每CPU变量的任务队列里面,如下:

1

2

3

4

5

6

void stop_one_cpu_nowait(unsigned int cpu, cpu_stop_fn_t fn, void *arg,

struct cpu_stop_work *work_buf)

{

*work_buf = (struct cpu_stop_work){ .fn = fn, .arg = arg, };

cpu_stop_queue_work(&per_cpu(cpu_stopper, cpu), work_buf);

}

而cpu_stopper则是cpu_stop_init函数通过cpu_stop_cpu_callback创建的migration内核线程,触发任务队列调度。因为migration内核线程是绑定每个核心上的,进程迁移失败的1和3问题就可以通过push解决。active_load_balance_cpu_stop则调用move_one_task函数迁移指定的进程。

上面描述的则是整个pull和push的过程,需要补充的pull触发除了schedule后触发,还有scheduler_tick通过触发中断,调用run_rebalance_domains再调用rebalance_domains触发,不再细数。

1

2

3

4

void __init sched_init(void)

{

open_softirq(SCHED_SOFTIRQ, run_rebalance_domains);

}

Linux内核CPU负载均衡机制来自于OenHan

链接为:http://oenhan.com/cpu-load-balance

原文地址:https://www.cnblogs.com/sky-heaven/p/11117653.html

时间: 2024-08-06 09:43:28

LINUX内核CPU负载均衡机制【转】的相关文章

(转帖)linux内核SMP负载均衡浅析

需求在<linux进程调度浅析>一文中提到,在SMP(对称多处理器)环境下,每个CPU对应一个run_queue(可执行队列).如果一个进程处于TASK_RUNNING状态(可执行状态),则它会被加入到其中一个run_queue(且同一时刻仅会被加入到一个run_queue),以便让调度程序安排它在这个run_queue对应的CPU上面运行.一个CPU对应一个run_queue这样的设计,其好处是:1.一个持续处于TASK_RUNNING状态的进程总是趋于在同一个CPU上面运行(其间,这个进程

LVS (Linux Virtual Server) 负载均衡

[大型网站技术实践]初级篇:借助LVS+Keepalived实现负载均衡 一.负载均衡:必不可少的基础手段 1.1 找更多的牛来拉车吧 当前大多数的互联网系统都使用了服务器集群技术,集群即将相同服务部署在多台服务器上构成一个集群整体对外提供服务,这些集群可以是Web应用服务器集群,也可以是数据库服务器集群,还可以是分布式缓存服务器集群等等. 古人有云:当一头牛拉不动车的时候,不要去寻找一头更强壮的牛,而是用两头牛来拉车. 在实际应用中,在Web服务器集群之前总会有一台负载均衡服务器,负载均衡设备

Linux多网卡负载均衡 : bond

在这介绍的Linux双网卡绑定实现就是使用两块网卡虚拟成为一块网卡,这个聚合起来的设备看起来是一个单独的以太网接口设备,通俗点讲就是两块网卡具有相同的IP地址而并行链接聚合成一个逻辑链路工作.其实这项技术在Sun和Cisco中早已存在,被称为Trunking和Etherchannel技术,在Linux的2.4.x的内核中也采用这这种技术,被称为bonding.bonding技术的最早应用是在集群——beowulf上,为了提高集群节点间的数据传输而设计的.下面我们讨论一下bonding 的原理,什

Linux 之nginx 负载均衡集群

Linux 之nginx 负载均衡集群: 实验环境:10.72.4.37 (dr,是lnmp 的环境),10.72.4.48 (rs1),10.72.4.39 (rs2,是lamp 的环境),三台机上均安装nginx.[rpm  -qal nginx 查看nginx  的黙认安装目录] [如果是没有安装nginx的,按以下步骤操作: Centos 6下如果安装过epel的yum源可以直接yum安装nginx  yum install -y nginx 或者源码包编译nginx, 官网地址http

Linux内核中的信号机制--一个简单的例子【转】

本文转载自:http://blog.csdn.net/ce123_zhouwei/article/details/8562958 Linux内核中的信号机制--一个简单的例子 Author:ce123(http://blog.csdn.NET/ce123) 信号机制是类UNIX系统中的一种重要的进程间通信手段之一.我们经常使用信号来向一个进程发送一个简短的消息.例如:假设我们启动一个进程通过socket读取远程主机发送过来的网络数据包,此时由于网络因素当前主机还没有收到相应的数据,当前进程被设置

linux+nginx+tomcat负载均衡,实现session同步

linux+nginx+tomcat负载均衡,实现session同步 花了一个上午的时间研究nginx+tomcat的负载均衡测试,集群环境搭建比较顺利,但是session同步的问题折腾了几个小时才搞定,现把我的过程贴上来,以备用.软件及环境是:虚拟机上装centos 5.5IP为:192.168.0.51 装上nginx和tomcat  6.0.32 命名为 Tomcat1一台win7上装tomcat  6.0.32  IP为:192.168.0.50  命名为 Tomcat2 首先装ngin

nginx 健康检查和负载均衡机制分析

nginx 是优秀的反向代理服务器,这里主要讲它的健康检查和负载均衡机制,以及这种机制带来的问题.所谓健康检查,就是当后端出现问题(具体什么叫出现问题,依赖 于具体实现,各个实现定义不一样),不再往这个后端分发请求,并且做后续的检查,直到这个后端恢复正常.所谓负载均衡,就是选择后端的方式,如何(根据后 端的能力)将请求均衡的分发到后端.此外,当请求某个后端失败时,要将该请求分发到其它后端(redispatch).这里以 ngx_http_upstream_round_robin(简称RR)做为负

【转贴】Linux系统NGINX负载均衡404错误处理方法

NGINX负载均衡404错误处理方法 使用NGINX 实现负载均衡,但一组服务器的数据不是实施同步,主服务器有了数据要过段时间才同步到其他服务器 upstream   image.stream.com   { server 192.168.1.25:8088; server 192.168.1.24:8088; server 192.168.1.23:8088; } 用户访问图片的时候,就有60% 的几率显示为找不到文件. 问题: 怎么配置成以下功能: 1.连接图片服务器时,如果说浏览的机器在2

Linux的CPU负载

Linux下的CPU负载: 指的是一段时间内任务队列的长度,通俗的讲,就是一段时间内一共有多少任务在使用或等待使用CPU. 通常通过有3个数值,分别代表1,5,15钟内的平均CPU负载,越小越好. CPU负载与CPU利用率不是同一个概念.虽然CPU负载表示当前系统还有多少个任务等待处理,而CPU利用则表明的是在一个任务上,CPU的使用率. Linux的CPU负载