原图
概述
Linux 内核为开发者提供了丰富的进程调度信息,这些调度信息都需要在内核配置时打开 CONFIG_SCHED_DEBUG 选项。
查看与进程相关的调度信息
有时候我们需要查看进程相关的调度信息,如进程的 nice 值、优先级、调度策略、vruntime 及量化计算能力等信息。在 Linux 的 proc 目录中,为每个进程提供一个独立的目录,该目录包含了与进程相关的信息,执行如下命令:
得到:
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, ------------------------------------------------------------------- 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 systemd (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 Sched Debug Version: v0.11, 5.4.0-81-generic 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 .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 内核设计与实现》