linux内核死锁检测机制 | oenhan,Linux内核CPU负载均衡机制 | OenHan
还是神奇的进程调度问题引发的,参看Linux进程组调度机制分析,组调度机制是看清楚了,发现在重启过程中,很多内核调用栈阻塞在了double_rq_lock函数上,而double_rq_lock则是load_balance触发的,怀疑当时的核间调度出现了问题,在某个负责场景下产生了多核互锁,后面看了一下CPU负载平衡下的代码实现,写一下总结。
内核代码版本:kernel-3.0.13-0.27。
内核代码函数起自load_balance函数,从load_balance函数看引用它的函数可以一直找到schedule函数这里,便从这里开始往下看,在__schedule中有下面一句话。
1
2if (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
2if (this_rq->avg_idle < sysctl_sched_migration_cost)
return;
sysctl_sched_migration_cost对应proc控制文件是/proc/sys/kernel/sched_migration_cost,开关代表如果CPU队列空闲了500ms(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
22struct sd_lb_stats {
struct sched_group *busiest;/* Busiest group in this sd */
struct sched_group *this;/* Local group in this sd */
unsignedlong total_load;/* Total load of all groups in sd */
unsignedlong total_pwr;/* Total power of all groups in sd */
unsignedlong avg_load;/* Average load across all groups in sd */
/** Statistics of this group */
unsignedlong this_load;//当前调度组的负载
unsignedlong this_load_per_task;//当前调度组的平均负载
unsignedlong this_nr_running;//当前调度组内运行队列中进程的总数
unsignedlong this_has_capacity;
unsignedint this_idle_cpus;
/* Statistics of the busiest group */
unsignedint busiest_idle_cpus;
unsignedlong max_load;//最忙的组的负载量
unsignedlong busiest_load_per_task;//最忙的组中平均每个任务的负载量
unsignedlong busiest_nr_running;//最忙的组中所有运行队列中进程的个数
unsignedlong busiest_group_capacity;
unsignedlong busiest_has_capacity;
unsignedint 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
26do
{
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
3sds.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
31for_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)更新这个值.*/
unsignedlong power = power_of(i);
unsignedlong capacity = DIV_ROUND_CLOSEST(power,SCHED_POWER_SCALE);
unsignedlong 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
4double_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
7static 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
5if (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
6void stop_one_cpu_nowait(unsignedint 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
4void __init sched_init(void)
{
open_softirq(SCHED_SOFTIRQ, run_rebalance_domains);
}
linux内核死锁检测机制 | oenhan,Linux内核CPU负载均衡机制 | OenHan相关推荐
- 软考高级系统架构设计师:DNS负载均衡机制和反向代理的负载均衡机制
软考高级系统架构设计师:DNS负载均衡机制和反向代理的负载均衡机制 一.DNS基本原理 二.反向代理负载均衡 三.DNS和反向代理区别 1.系统执行效率 2.安全性方面 一.DNS基本原理 基于DNS ...
- linux 内核 死锁 检查,一种linux内核自旋锁死锁检测报告系统和方法与流程
本发明涉及内核死锁检测领域,具体的说是一种linux内核自旋锁死锁检测报告系统和方法. 背景技术: linux内核死锁是长期困扰内核开发人员的问题之一,但自内核引入lockdep调试模块之后,内核死锁 ...
- 查看linux cpu负载均衡,关于linux内核cpu进程的负载均衡
2.6内核中进程调度模块的负载均衡行为分为"拉"和"推",推这里不考虑,关于拉均衡有一篇文章特别好,具体出处就不记得了,我当时用的百度快照,那篇文章我认为最精彩 ...
- 在linux下面实现检测按键(Linux中kbhit()函数的实现)
//在linux下面实现检测按键(Linux中kbhit()函数的实现) #include <stdio.h> #include <termios.h> #include &l ...
- linux下面实现检测按键(Linux中kbhit()函数的实现)
在linux下面实现检测按键(Linux中kbhit()函数的实现) #include <stdio.h> #include <termios.h> #include &l ...
- 在Linux上使用Nginx为Solr集群做负载均衡
在Linux上使用Nginx为Solr集群做负载均衡 在Linux上搭建solr集群时需要用到负载均衡,但测试环境下没有F5 Big-IP负载均衡交换机可以用,于是先后试了weblogic的proxy ...
- Nginx——事件驱动机制(雷霆追风问题,负载均衡)
事件处理框架 所有的worker进程都在ngx_worker_process_cycle方法中循环处理事件,处理分发事件则在ngx_worker_process_cycle方法中调用ngx_proce ...
- 论文阅读四:基于流分类的数据中心网络负载均衡机制
名词解释: Utilization-aware Load-balancing based on Flow Classification, ULFC:基于流分类的数据中心网络负载均衡机制 Equal-C ...
- 2021年大数据Kafka(十一):❤️Kafka的消费者负载均衡机制和数据积压问题❤️
全网最详细的大数据Kafka文章系列,强烈建议收藏加关注! 新文章都已经列出历史文章目录,帮助大家回顾前面的知识重点. 目录 系列历史文章 Kafka的消费者负载均衡机制和数据积压问题 一.kafka ...
最新文章
- 每日一皮:临近截止日期, 产品经理就这样看着我...
- busybox 安装mysql_安装busybox
- 《Linus Torvalds自传》摘录
- 参数名称 java_java – 具有重要名称的WSDL中的参数名称
- 春眠不觉晓,读书醒醒脑|世界读书日送书征集
- 六维图见过么?Python 画出来了!
- 在命令行到处MYSQL数据到EXCEL表
- 计算机机房的荷载,​计算机信息中心机房建设标准
- 苹果6s微信网络未连接服务器,微信网络连接不可用怎么解决?苹果手机微信网络连接不可用?...
- 原生JS音乐歌词播放页面
- 31个让你大呼惊艳的数据可视化作品!
- Android开发笔记之视频录制
- 一个SparkSQL作业的一生
- 函数进阶 call apply bind 的区别
- 把一个字符串13579先变成Array——[1, 3, 5, 7, 9],再利用reduce(),就可以写出一个把字符串转换为Number的函数。
- 微信H5支付坑一--手续费未结算
- selenium学习——问卷星(可控比例)
- 汽车常识全面介绍 - 引擎详论
- Unity优化材质,清除空引用贴图
- 花十分钟顺手拿个阿里的Apsara Clouder专项技能认证,不比手里的王者香?
热门文章
- Java Long类的valueOf()方法及示例
- mcq 队列_MCQ | 基础知识 免费和开源软件| 套装3
- 在数组中查找第k个最大元素_查找数组中每个元素的最近最大邻居
- 解放双手!推荐一款阿里开源的低代码工具,YYDS!
- MySQL数据库工具类之——DataTable批量加入MySQL数据库(Net版)
- Linux新安装后设置root密码
- maven仓库理解、下载及设置
- Java创建对象的方式——反序列化创建对象
- 服务器采购框架合同协议书范本,手写一个满足WSGI协议的Server
- 监控mysql数据库里的数据_有谁知道哪种软件可以监控mysql数据库上执行的数据脚本吗?...