日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux内核死锁检测机制 | oenhan,Linux内核CPU负载均衡机制 | OenHan

發(fā)布時間:2025/3/12 linux 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux内核死锁检测机制 | oenhan,Linux内核CPU负载均衡机制 | OenHan 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

還是神奇的進程調度問題引發(fā)的,參看Linux進程組調度機制分析,組調度機制是看清楚了,發(fā)現(xiàn)在重啟過程中,很多內核調用棧阻塞在了double_rq_lock函數(shù)上,而double_rq_lock則是load_balance觸發(fā)的,懷疑當時的核間調度出現(xiàn)了問題,在某個負責場景下產生了多核互鎖,后面看了一下CPU負載平衡下的代碼實現(xiàn),寫一下總結。

內核代碼版本:kernel-3.0.13-0.27。

內核代碼函數(shù)起自load_balance函數(shù),從load_balance函數(shù)看引用它的函數(shù)可以一直找到schedule函數(shù)這里,便從這里開始往下看,在__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的狀態(tài),也就是遍歷對應的sd,將sds里面的結構體數(shù)據(jù)填滿,如下:

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;//當前調度組內運行隊列中進程的總數(shù)

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;//最忙的組中所有運行隊列中進程的個數(shù)

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;

在比較負載大小的過程中, 當發(fā)現(xiàn)當前運行的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運行數(shù)大于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開關控制進程遷移個數(shù),對應proc的是/proc/sys/kernel/sched_nr_migrate。

下面有can_migrate_task函數(shù)檢查選定的進程是否可以進行遷移,遷移失敗的原因有3個,1.遷移的進程處于運行狀態(tài);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的情況下,如果遷移失敗的個數(shù)太多,仍然進行遷移

* 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失敗了,開始觸發(fā)push操作

stop_one_cpu_nowait(cpu_of(busiest),

active_load_balance_cpu_stop, busiest,

&busiest->active_balance_work);

push整個觸發(fā)操作代碼機制比較繞,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函數(shù)通過cpu_stop_cpu_callback創(chuàng)建的migration內核線程,觸發(fā)任務隊列調度。因為migration內核線程是綁定每個核心上的,進程遷移失敗的1和3問題就可以通過push解決。active_load_balance_cpu_stop則調用move_one_task函數(shù)遷移指定的進程。

上面描述的則是整個pull和push的過程,需要補充的pull觸發(fā)除了schedule后觸發(fā),還有scheduler_tick通過觸發(fā)中斷,調用run_rebalance_domains再調用rebalance_domains觸發(fā),不再細數(shù)。

1

2

3

4void __init sched_init(void)

{

open_softirq(SCHED_SOFTIRQ, run_rebalance_domains);

}

總結

以上是生活随笔為你收集整理的linux内核死锁检测机制 | oenhan,Linux内核CPU负载均衡机制 | OenHan的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。