已经扩展好了。现在项目里有三个配套工具,都在 docs/kernel/cgroup/ 下:
┌────────────────────┬──────────────────────────────────────────────────────────────┐ │ 文件 │ 作用 │ ├────────────────────┼──────────────────────────────────────────────────────────────┤ │ dump-task-group.bt │ 遍历内核 task_groups 链表,dump 每个 task_group 的层级关系 │ ├────────────────────┼──────────────────────────────────────────────────────────────┤ │ dump-task-group.py │ 把上面的原始数据格式化成树状图 │ ├────────────────────┼──────────────────────────────────────────────────────────────┤ │ dump-cfs-rq.bt │ 接收一个 cfs_rq 地址,dump 它的基本信息 + rbtree 中的 entity │ └────────────────────┴──────────────────────────────────────────────────────────────┘
用法
- 看 task_group 树
echo a | sudo -S bpftrace dump-task-group.bt | python3 dump-task-group.py
输出示例(已经在你的机器上跑过):
0xffffffff84a10200 shares=1048576 cfs_rq[0]=0xffff889ffe833140 se[0]=0 (ROOT)
├── 0xffff88811180c200 shares=1048576 cfs_rq[0]=0xffff8881035bd400 se[0]=0xffff88810052c000
├── 0xffff888114e2ee00 shares=1048576 cfs_rq[0]=0xffff888114deb800 se[0]=0xffff888112db1000
│ ├── 0xffff888114e2f900 shares=1048576 cfs_rq[0]=0xffff888124b06c00 se[0]=0xffff888107ef8400
│ ├── 0xffff888114e2d280 shares=1048576 cfs_rq[0]=0xffff88810bf22600 se[0]=0xffff8881082c4400
│ └── ...
注意: • cfs_rq[0]:这个 task_group 在 CPU 0 上的 cfs_rq 地址 • se[0]:这个 task_group 在 CPU 0 上的 group sched_entity 地址(在父级 cfs_rq 的 rbtree 里排队时用到) • root 的 se[0]=0,因为 root 没有父级,不需要在父 cfs_rq 中表现为 group entity
- 深入看某个 cfs_rq 的 rbtree
从上一步的输出里挑一个 cfs_rq[0] 地址,比如 0xffff888124b06c00:
echo a | sudo -S bpftrace dump-cfs-rq.bt 0xffff888124b06c00
输出示例(当前系统空闲,rbtree 为空):
cfs_rq=0xffff888124b06c00 tg=0xffff888114e2f900 nr_queued=0 h_nr_queued=0
curr=0 next=0 rb_root=0 rb_leftmost=0
(empty rbtree)
如果系统有负载,你会看到类似这样的输出:
cfs_rq=0xffff... tg=0xffff... nr_queued=2 h_nr_queued=2
curr=0xffff... next=0 rb_root=0xffff... rb_leftmost=0xffff...
--- rbtree entities ---
[TASK ] se=0xffff... vruntime=12345678 parent=0
[GROUP] se=0xffff... vruntime=23456789 my_q=0xffff... parent=0
dumped 2 rbtree nodes
• [TASK]:普通进程 entity,my_q == 0 • [GROUP]:子 cgroup 的 group entity,my_q 指向该子 cgroup 自己的 cfs_rq • curr:当前正在运行、已被 dequeue 出 rbtree 的 entity(如果有的话)
────────────────────────────────────────────────────────────────────────────────
限制说明
- bpftrace 循环上限:dump-cfs-rq.bt 中 rbtree 遍历上限是 16 个节点。这是 BPF verifier 的限制,超过 16 次循环展开就会拒绝加载。对于理解层级结构通常够用了。
- 只 dump CPU 0:dump-task-group.bt 当前只打印 cfs_rq[0] 和 se[0]。如果你需要看其他 CPU,原理一样,解引用 cfs_rq[cpu] 和 se[cpu] 即可。
- 空闲系统可能为空:cfs_rq 的 rbtree 只在有 runnable 且排队等待的任务时才非空。当前运行中的任务会被 dequeue(curr 指针指向它),所以 rb_root 可能为 0,但 curr 非 0。
────────────────────────────────────────────────────────────────────────────────
如果你想看一个有负载的 cfs_rq 的完整输出,可以跑你的 cgroup-sched-lab.sh 制造几个 busy worker,然后挑它们所在 cgroup 的 cfs_rq[0] 用 dump-cfs-rq.bt 查看。
cgroup sched 在多核上的分布
这个测试终于完成了我一直奇怪的东西:
- 子 cgroup 其实是作为 se 放到 rbtree 的
- 每一个 CPU 都有 cfs_rq ,cfs_rq 中有 rbtree 的
我在 /sys/fs/cgroup/demo-sched-test/ 下建了一个层级:
demo-A (cpu.weight=100)
├── demo-B (cpu.weight=200) ← a(PID 2646310, CPU0) + b(PID 2646311, CPU1)
└── demo-C (cpu.weight=300) ← c(PID 2646312, CPU0) + d(PID 2646313, CPU1) + e(PID 2646314, CPU2)
然后通过 bpftrace dump 出了内核里的 task_group、cfs_rq、sched_entity 和 rbtree 的实拍数据。
────────────────────────────────────────────────────────────────────────────────
实测数据:task_group 层级
A tg=0xffff889bf06039c0 shares=1048576 parent=demo-sched-test
B tg=0xffff888ab75d6e00 shares=2097152 parent=A
C tg=0xffff889fe0b1d280 shares=3145728 parent=A
shares 和 cpu.weight 的关系:shares = cpu.weight * 1048576 / 100 • weight=100 → shares=1048576 • weight=200 → shares=2097152 • weight=300 → shares=3145728
────────────────────────────────────────────────────────────────────────────────
核心结论:多核下每个 CPU 都有一整套 cfs_rq
一个 cgroup(task_group)不是只有一个 cfs_rq,而是每个 CPU 都有一个独立的 cfs_rq。
B 和 C 的 cfs_rq 分布:
┌────────┬───────────────┬───────────────┬────────────────────┐ │ cgroup │ CPU 0 cfs_rq │ CPU 1 cfs_rq │ CPU 2 cfs_rq │ ├────────┼───────────────┼───────────────┼────────────────────┤ │ B │ 0xffff…a000 │ 0xffff…6c00 │ 0xffff…1600 (空) │ ├────────┼───────────────┼───────────────┼────────────────────┤ │ C │ 0xffff…7a00 │ 0xffff…b400 │ 0xffff…f600 │ └────────┴───────────────┴───────────────┴────────────────────┘
B 在 CPU 2 上没有进程,所以 cfs_rq[2] 是空的。C 在三个 CPU 上都有进程,所以三个 cfs_rq 都非空。
────────────────────────────────────────────────────────────────────────────────
A 的 cfs_rq:装的是 B 和 C 的 group entity
A 自己没有进程,它的 rbtree 里只有 group sched_entity(代表 B 和 C):
=== A cfs_rq[0] (CPU 0) ===
nr_queued=2 h_nr_queued=2
[CURR:GROUP] se=0xffff888c52ae2400 vruntime=135926731147 my_q=0xffff888558eea000
[GROUP] se=0xffff888a61e0a400 vruntime=135926984813 my_q=0xffff888132a07a00
=== A cfs_rq[1] (CPU 1) ===
nr_queued=2 h_nr_queued=2
[CURR:GROUP] se=0xffff88850fe3bc00 vruntime=136142682456 my_q=0xffff88945211b400
[GROUP] se=0xffff888b7234b800 vruntime=136144474778 my_q=0xffff88830c856c00
=== A cfs_rq[2] (CPU 2) ===
nr_queued=1 h_nr_queued=1
[CURR:GROUP] se=0xffff8896933ff400 vruntime=272582939947 my_q=0xffff8891290ff600
关键点: • A 的 rbtree 里没有 task,只有 group entity • my_q 指向的是 子 cgroup 的 cfs_rq。比如 A 中代表 C 的 group entity,它的 my_q=0xffff888132a07a00,这正是 C 的 cfs_rq[0] • CPU 0 上 A 的 curr 是 B 的 group entity(说明当前调度器正在运行 B 下面的任务 a) • CPU 1 上 A 的 curr 是 C 的 group entity(说明当前正在运行 C 下面的任务 d) • CPU 2 上只有 C 有进程(e),所以 A 的 cfs_rq[2] 里只有一个 C 的 group entity,没有 B
────────────────────────────────────────────────────────────────────────────────
B 和 C 的 cfs_rq:装的是真正的 task entity
=== B cfs_rq[0] (CPU 0, a 在跑) ===
curr=TASK se=0xffff8886c6538080 vruntime=108317230474
=== B cfs_rq[1] (CPU 1, b 在跑) ===
curr=TASK se=0xffff8888fcff2b40 vruntime=108532409529
=== C cfs_rq[0] (CPU 0, c 在跑) ===
curr=TASK se=0xffff888f3ebbab40 vruntime=108677868102
=== C cfs_rq[1] (CPU 1, d 在排队) ===
curr=0
rb_root=TASK se=0xffff8898b06e2b40 vruntime=108894974287 parent=0xffff88850fe3bc00
=== C cfs_rq[2] (CPU 2, e 在跑) ===
curr=TASK se=0xffff8899990f0080 vruntime=218100392810
注意 parent=0xffff88850fe3bc00,这正是 C 在 A 的 cfs_rq[1] 中的 group sched_entity 地址。验证了层级关系:
A cfs_rq[1]
└─ group se for C (parent=0xffff8893abbafc00, 这是 C 的 se[1])
└─ C cfs_rq[1]
└─ task se for d (parent=0xffff88850fe3bc00, 指回 C 的 group se)
────────────────────────────────────────────────────────────────────────────────
一句话总结
│ 每个 cgroup 在每个 CPU 上都有一个独立的 cfs_rq(内含 rbtree)。父 cgroup 的 rbtree 里排的是子 cgroup 的 group │ sched_entity,子 cgroup 的 rbtree 里排的是 task 的 sched_entity。调度器 pick_next_task 时,从 root cfs_rq 开始,遇到 group │ entity 就通过 my_q 进入子 cfs_rq,递归向下,直到选到一个真正的 task。
本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。