0%

Linux进程管理(五)进程调度的调试

原图

概述

Linux内核为开发者提供了丰富的进程调度信息,这些调度信息都需要在内核配置时打开CONFIG_SCHED_DEBUG选项。

查看与进程相关的调度信息

有时候我们需要查看进程相关的调度信息,如进程的nice值、优先级、调度策略、vruntime及量化计算能力等信息。在Linux的proc目录中,为每个进程提供一个独立的目录,该目录包含了与进程相关的信息,执行如下命令:

1
vooxle@liushuai:~# cat /proc/1/sched

得到:

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
systemd (1, #threads: 1)
-------------------------------------------------------------------
se.exec_start : 5438471989.820758
se.vruntime : 3446.301246
se.sum_exec_runtime : 66272.041453
se.nr_migrations : 4998
nr_switches : 172620
nr_voluntary_switches : 169241
nr_involuntary_switches : 3379
se.load.weight : 1048576
se.runnable_weight : 1048576
se.avg.load_sum : 172
se.avg.runnable_load_sum : 172
se.avg.util_sum : 176128
se.avg.load_avg : 0
se.avg.runnable_load_avg : 0
se.avg.util_avg : 0
se.avg.last_update_time : 5438471989820416
se.avg.util_est.ewma : 10
se.avg.util_est.enqueued : 0
policy : 0
prio : 120
clock-delta : 103
mm->numa_scan_seq : 0
numa_pages_migrated : 0
numa_preferred_nid : 0
total_numa_faults : 806
current_node=0, numa_group_id=0
numa_faults node=0 task_private=734 task_shared=72 group_private=0 group_shared=0
numa_faults node=1 task_private=0 task_shared=0 group_private=0 group_shared=0

上面就是ID为1的进程的proc目录,在进程proc目录里,看到和进程调度相关的节点为sched,读者可以使用cat命令来读取这个节点的信息,我们可以得到很多有用的信息,这些信息是在proc_sched_show_task()函数中输出的,该函数实现在kerel/sched/debug.c文件中。这些信息如下:

  • 进程的名称为systemd,ID是1,线程有1个
  • 进程的优先级为120
  • 进程的调度策略是SCHED_NORMAL,使用的调度类是CFS调度类(fair_sched_class)
  • 当前进程的虚拟时间(vruntime)是3446.301246ms。总运行时间为66272.041453ms
  • 进程发生过4998次迁移,172620次进程上下文切换,其中主动调度有169241次,被抢占调度有3379 次
  • 进程的权重se.load.weight和se.runnable.weight相等,都是1048576。注意,这两个值均为原本的权重值乘以1024。进程优先级为120,nice值为0,它原本的权重值为1024
  • 当前时刻,进程的se.avg.load_sum值和se.avg.runnable_load_sum相等,都是172
  • 进程的se.ave.load_avg 和se.avg.runnable_load_avg是相等的都是0。对于该进和来说,它的量化负载的最大值就等于它的权重值,即1024,这是在100%占用CPU的情况下得到的
  • 进程的量化计算能力(se.avg.util_avg)为0

Linux内核还提供一个与调度信息相关的数据结构sched_statistics,其中包含了非常多和调度相关的统计信息。要查看这些统计信息,需要打开CONFIG_SCHEDSTATS配置选项。另外,还需要打开sched_schedstats节点。

1
echo 1 > /proc/sys/kernel/sched_schedstats

重新查看ID为1的进程调度信息,我们会发现里面多了很多统计信息

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
root@liushuai:/proc/1# cat sched
systemd (1, #threads: 1)
-------------------------------------------------------------------
se.exec_start : 5439456327.802850
se.vruntime : 3452.764216
se.sum_exec_runtime : 66278.504423
se.nr_migrations : 4998
se.statistics.sum_sleep_runtime : 0.000000
se.statistics.wait_start : 0.000000
se.statistics.sleep_start : 0.000000
se.statistics.block_start : 0.000000
se.statistics.sleep_max : 0.000000
se.statistics.block_max : 0.000000
se.statistics.exec_max : 0.000000
se.statistics.slice_max : 0.000000
se.statistics.wait_max : 0.000000
se.statistics.wait_sum : 0.000000
se.statistics.wait_count : 0
se.statistics.iowait_sum : 0.000000
se.statistics.iowait_count : 0
se.statistics.nr_migrations_cold : 0
se.statistics.nr_failed_migrations_affine : 0
se.statistics.nr_failed_migrations_running : 0
se.statistics.nr_failed_migrations_hot : 0
se.statistics.nr_forced_migrations : 0
se.statistics.nr_wakeups : 0
se.statistics.nr_wakeups_sync : 0
se.statistics.nr_wakeups_migrate : 0
se.statistics.nr_wakeups_local : 0
se.statistics.nr_wakeups_remote : 0
se.statistics.nr_wakeups_affine : 0
se.statistics.nr_wakeups_affine_attempts : 0
se.statistics.nr_wakeups_passive : 0
se.statistics.nr_wakeups_idle : 0
avg_atom : 0.383900
avg_per_cpu : 13.261005
nr_switches : 172645
nr_voluntary_switches : 169266
nr_involuntary_switches : 3379
se.load.weight : 1048576
se.runnable_weight : 1048576
se.avg.load_sum : 286
se.avg.runnable_load_sum : 286
se.avg.util_sum : 293807
se.avg.load_avg : 6
se.avg.runnable_load_avg : 6
se.avg.util_avg : 6
se.avg.last_update_time : 5439456327801856
se.avg.util_est.ewma : 10
se.avg.util_est.enqueued : 0
policy : 0
prio : 120
clock-delta : 56
mm->numa_scan_seq : 0
numa_pages_migrated : 0
numa_preferred_nid : 0
total_numa_faults : 806
current_node=0, numa_group_id=0
numa_faults node=0 task_private=734 task_shared=72 group_private=0 group_shared=0
numa_faults node=1 task_private=0 task_shared=0 group_private=0 group_shared=0

查看CFS的信息

Linux内核在调度方面实现了一些调试接口,为开发者提供窥探调度器内部信息的接口。 在proc目录下面有一个sched_debug节点,其信息如下所示。

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
root@liushuai:/proc# cat sched_debug
Sched Debug Version: v0.11, 5.4.0-81-generic #91-Ubuntu
ktime : 5439613876.757507
sched_clk : 5439566740.450647
cpu_clk : 5439566596.040965
jiffies : 5654795694
sched_clock_stable() : 1

sysctl_sched
.sysctl_sched_latency : 24.000000
.sysctl_sched_min_granularity : 3.000000
.sysctl_sched_wakeup_granularity : 4.000000
.sysctl_sched_child_runs_first : 0
.sysctl_sched_features : 2059067
.sysctl_sched_tunable_scaling : 1 (logarithmic)
...
rt_rq[0]:
.rt_nr_running : 0
.rt_nr_migratory : 0
.rt_throttled : 0
.rt_time : 0.000000
.rt_runtime : 950.000000

dl_rq[0]:
.dl_nr_running : 0
.dl_nr_migratory : 0
.dl_bw->bw : 996147
.dl_bw->total_bw : 0
  • Sched调试信息的版本号:v0.11
  • Linux内核版本号:5.4.0
  • 内核时间(ktime)的值5439613876.757507
  • sched_clock=5439566740.450647和cpu_clk=5439566596.040965的值
  • 当前jiffies的值5654795694
  • 和调度相关的sysctl_sched的值
    • 调度周期sysctl_sched_latency 为24.000000ms
    • 调度最小粒度为3.000000ms
    • 唤醒的最小粒度为4.000000ms
    • fork调用完成之后禁止子进程先运行
    • 调度支持的特性

cpu相关信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cpu#0, 2500.140 MHz
.nr_running : 0
.nr_switches : 57306500
.nr_load_updates : 0
.nr_uninterruptible : -2994
.next_balance : 5654.795676
.curr->pid : 0
.clock : 5439566591.813321
.clock_task : 5439566591.813321
.avg_idle : 1000000
.max_idle_balance_cost : 500000
.yld_count : 1
.sched_count : 8189
.sched_goidle : 4044
.ttwu_count : 1273
.ttwu_local : 321
  • nr_running:有0个进程在就绪队列里
  • nr_switches:就绪队列发生进程切换的次数57306500次
  • nr_load_updates:就绪队列的cpu_load[]平滑负载更新的次数0次
  • next_balance:下一次做负载均衡的时间5654.795676
  • curr->pid:正在运行状态的PID
  • clock和clock_task:当前系统采样的时刻

cfs就绪队列相关信息:

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
32
33
34
35
36
37
38
39
40
cfs_rq[1]:/user.slice
.exec_clock : 12.379262
.MIN_vruntime : 0.000001
.min_vruntime : 2306434.555699
.max_vruntime : 0.000001
.spread : 0.000000
.spread0 : -8049717.704115
.nr_spread_over : 0
.nr_running : 1
.load : 1048576
.runnable_weight : 1048576
.load_avg : 3
.runnable_load_avg : 3
.util_avg : 3
.util_est_enqueued : 0
.removed.load_avg : 0
.removed.util_avg : 0
.removed.runnable_sum : 0
.tg_load_avg_contrib : 3
.tg_load_avg : 1037
.throttled : 0
.throttle_count : 0
.se->exec_start : 5439566596.839807
.se->vruntime : 7544926.691548
.se->sum_exec_runtime : 2240863.841914
.se->statistics.wait_start : 0.000000
.se->statistics.sleep_start : 0.000000
.se->statistics.block_start : 0.000000
.se->statistics.sleep_max : 0.000000
.se->statistics.block_max : 0.000000
.se->statistics.exec_max : 0.303114
.se->statistics.slice_max : 0.000000
.se->statistics.wait_max : 0.000000
.se->statistics.wait_sum : 0.000000
.se->statistics.wait_count : 143
.se->load.weight : 521740
.se->runnable_weight : 521740
.se->avg.load_avg : 2
.se->avg.util_avg : 3
.se->avg.runnable_load_avg : 2
  • exec_clock:CFS就绪队列的总运行时间
  • MIN_vruntime:表示CFS就绪队列的红黑树中最左边节点的vruntime的值
  • min_vruntime:CFS就绪队列里min_vruntime的值
  • max_vruntime: CFS就绪队列的红黑树中最右边节点的vruntime的值
  • nr_running:CFS就绪队列中的进程数
  • load:CFS就绪队列的总权重
  • runnable_weight.CFS就绪队列中可运行状态的进程总权重
  • load_avg:调度队列中总的量化负载,这是CFS就绪队列中所有进程的量化负载之和
  • runnable_load_avg:CFS就绪队列里所有可运行状态下的进程总量化负载,可以看到它的值和load_avg相等
  • util_avg:CFS就绪队列当前的量化计算能力

所有进程相关信息:

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
runnable tasks:
S task PID tree-key switches prio wait-time sum-exec sum-sleep
-----------------------------------------------------------------------------------------------------------
I rcu_gp 3 13.968356 2 100 0.000000 0.003216 0.000000 0 0 /
I rcu_par_gp 4 15.968978 2 100 0.000000 0.002189 0.000000 0 0 /
I kworker/0:0H 6 4826.431210 4 100 0.000000 0.020363 0.000000 0 0 /
I mm_percpu_wq 10 24.040288 2 100 0.000000 0.002587 0.000000 0 0 /
S ksoftirqd/0 11 10356126.783742 181278 120 0.077741 3101.302842 18658.038816 0 0 /
S migration/0 13 0.000000 1360506 0 0.000000 14628.664708 0.000000 0 0 /
S idle_inject/0 14 0.000000 3 49 0.000000 0.005710 0.000000 0 0 /
S cpuhp/0 15 298446.415933 21 120 0.000000 1.152329 0.000000 0 0 /
S watchdogd 369 11.999994 2 0 0.000000 0.000000 0.000000 0 0 /
Secryptfs-kthrea 418 3181.658188 2 120 0.000000 0.007012 0.000000 0 0 /
I kworker/0:1H 771 10354055.277526 23104 100 0.000000 515.468619 0.000000 0 0 /
S rsyslogd 1230 100.894491 9129 120 0.000000 325.720995 0.000000 0 1230 /system.slice/rsyslog.service
S dockerd 598258 31439.037574 27303 120 0.000000 28928.757364 0.000000 0 597670 /system.slice/snap.docker.dockerd.service
S containerd 597865 31427.350714 113110 120 0.000000 13081.711696 0.030330 0 598167 /system.slice/snap.docker.dockerd.service
I kworker/0:2 646991 10279557.062806 122739 120 0.000000 3114.131510 0.000000 0 0 /
I kworker/0:0 649432 10356141.706616 369586 120 1.089386 9517.253921 103008.678000 0 0 /
S smbd 651779 1641474.345475 460050 120 11.539434 246732.864126 64471.275817 0 651779 /system.slice/smbd.service
S node 652105 3528667.050423 1354 120 0.000000 2331.451911 0.000000 0 652102 /user.slice
S cpptools 652144 3527016.422624 6 120 0.000000 0.203793 0.000000 0 0 /user.slice
S cpptools 652162 3527016.384935 6 120 0.000000 0.146937 0.000000 0 0 /user.slice
S cpptools 652182 3527016.373736 6 120 0.000000 0.115269 0.000000 0 0 /user.slice
S cpptools 652296 3527040.380268 1 120 0.000000 0.047959 0.000000 0 0 /user.slice
S cpptools-srv 652246 3527028.332316 1 120 0.000000 0.076723 0.000000 0 0 /user.slice
  • 第1列显示进程的状态。R表示可运行状态,也就是在CFS就绪队列中的进程;S表示睡眠状态的进程
  • 第2列显示进程的名称
  • 第3列显示PID
  • 第4列显示进程在CFS就绪队列的红黑树中的键值
  • 第5列显示进程切换的次数
  • 第6列显示进程的优先级
  • 第7列显示进程等待时间
  • 第8列显示进程运行的总时间
  • 第9列显示进程休眠的总时间

查看调度域信息

在理解SMP负载均衡机制的过程中,CPU的拓扑关系是一个难点。Linux内核新增了一个调试节点来帮助开发者理解(详见kernel/sched/topology.c文件)。在编译内核时,不仅需要打开CONFIG_SCHED_DEBUG选项,还需要在内核启动参数里传递sched_debug参数。 在内核启动之后,可以通过dmesg命令来得到CPU拓扑关系,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
[0.224258] CPUO attaching sched-domain(s)
[0.224422] domain-0: span=0-3 level=MC
[0.224780] groups: O:{ span=0 },1:{span=1},2:{span=2},3:{span=3}
[0.227045] CPU1 attaching sched-domain(s):
[0.227111] domain-0: span=0-3 level=MC
[0.227179] groups: 1:{span=1},2:{span=2 },3:{span=3 }, 0:{ span=0}
[0.227347] CPU2 attaching sched-domain(s):
[0.227407] domain-0: span=0-3 level=MC
[0.227471] groups: 2:{span=2},3:{span=3},0:{span=0},1:{span=1}
[0.228416] CPU3 attaching sched- domain(s):
[0.228485] domain-0: span=0-3 level=MC
[02285530] groups: 3:{span=3 }, 0:{span=0 }, 1:{span=1},2:{span=2}
[0.228757] root domain span: O-3 (max cpucapacity = 1024)

可以看出,该系统只有MC层级,从CPU0角度看,它对应的调度域是domain-0,管辖的范围是CPU[0~3],调度层级是MC。同理,可以得出CPU1、CPU2以及CPU3的CPU拓扑关系。

与调度相关的调试节点

和调度相关的调试节点在/proc/sys/kernel目录下,下面来简单介绍一下。

  • sched_cfs_bandwidth_slice_us:用于设置CFS的带宽限制,默认值为5ms
  • sched_child_runs_first:通过fork调用创建进程之后,sched_child_runs_first可以用于控制是父进程还是子进程先运行;若该值为1,表示子进程先运行;默认值为0
  • sched_domain:与调度域相关的目录
  • sched_latency_ns:设置CFS就绪队列调度的总时间片sysctl_sched_latency,默认值为6ms。 sched_latency_ns表示一个运行队列中所有进程运行一次的时间片,它与运行队列的进程数有关。如果进程数超过sched_nr_latency(默认是8),那么调度周期就是 sched_min_granularity_ns乘以运行队列里的进程数量;否则,就是sysctl_sched_latency
  • sched_migration_cost_ns:判断一个进程是否可以利用高速缓存的热度。如果进程的运行时间(now-p->se.exec_start)小于它,那么内核认为它的数据还在高速缓存里,所以该进程可以利用高速缓存的热度,在迁移的时候就不会考虑它
  • sched_min_granularity_ns:设置CPU密集型进程最小的调度时间片,也就是进程最短运行时间,用于防止频繁切换。默认值为0.75ms
  • sched_nr_migrate:设置在SMP负载均衡机制里每次最多可以从目标CPU迁移多少个进程到源CPU里。在迁移的过程中关闭了中断,包括软中断机制,因此增大该值会导致中断延迟,同时也增大了实时进程的延迟。该值默认设置为32
  • sched_schedstats:用于打开调度统计信息
  • sched_wakeup_granularity_ns:待唤醒进程会检查是否需要抢占当前进程,若待唤醒进程的睡眠时间短于sched_wakeup_granularity_ns,那么不会抢占当前进程。增加该值会减小待唤醒进程的抢占概率。若减小该值,那么发生抢占的概率就越大。默认值为1ms

另外,在/sys/kermel/debug/目录下也有几个和调度相关的节点。

  • sched_features:表示调度器支持的特性,如START_DEBIT(新进程尽量早调度),WAKEUP_PREEMPT(唤醒的进程是否可以抢占当前运行的进程)等。所有的特性详见kerel/sched/sech_features.h文件的定义
  • osched_debug:调度器的调试信息的开关。注意,该值仅仅控制sched_debug_enabled,并不会控制/proc/sched_debug节点 关于sched_debug_enabled的示例代码如下,
1
2
3
4
5
6
static int __init sched_debug_setup(char *str)
{
sched_debug_enabled = true;
return 0;
}
early_param("sched_debug", sched_debug_setup);

参考文献

《奔跑吧Linux内核》
《Linux内核设计与实现》