Skip to content

第四十三章:域与组

结丹中期

涉及内核源码:

林小源在研究负载均衡时,发现了一个更深层的结构。

他站在竞技场的高处,俯瞰着整个系统。四座石台不再是孤立的——它们之间有看不见的连线,形成了一个层次分明的拓扑结构。

"CPU 不是平坦的。"一个声音从他身后传来。

林小源转身,看到一个穿着白色长袍的老者。老者的手中握着一张图——图上画着 CPU 的拓扑结构,像一棵倒置的树。

"我是 ,"老者说,"调度域的构建者。我的工作是根据硬件拓扑,构建调度域的层次结构。"

他展开图:"看,最底层是 SMT 域——超线程级。同一物理核心的超线程共享大部分资源,所以它们之间的负载均衡最频繁。"

"往上是 DIE 域——芯片级。同一芯片上的 CPU 共享 LLC(最后级缓存)。"

"再往上是 NUMA 域——跨节点级。不同 NUMA 节点的 CPU 通过总线连接,访问远程内存比本地慢 2-3 倍。"

林小源看着那张图:"所以负载均衡是分层进行的?"

"对。"老者说,"先在最低层的域中尝试均衡。如果最低层解决不了问题,才向更高层的域求助。这样可以最小化跨域迁移的开销。"

老者带着林小源走到一座石台旁,指着石台表面的符文说:"每个调度域都有自己的均衡间隔。"

"SMT 域的均衡间隔是 1ms——因为超线程之间的迁移代价最低,所以检查最频繁。"

"DIE 域的均衡间隔是 4ms——同一芯片上的迁移代价中等。"

"NUMA 域的均衡间隔是 8ms——跨节点的迁移代价最高,所以检查最少。"

林小源看着那些数字:"这就是'就近原则'。"

"对。"老者说,"负载均衡的核心思想就是就近——先解决近距离的问题,再解决远距离的问题。就像你修炼时,先提升自己的实力,再考虑宗门之间的资源调配。"

林小源想起了自己在内核中看到的 NUMA 架构:"NUMA 系统中,每个 CPU 有自己的本地内存。访问本地内存快,访问远程内存慢。"

"对。"老者说,"所以调度域的设计考虑了 NUMA 的因素。在 NUMA 系统中,调度器会尽量让进程在本地 NUMA 节点上运行,避免跨节点的远程内存访问。"

林小源在老者的指导下,开始研究调度组。

"调度组是调度域中的 CPU 子集。"老者解释道,"负载均衡在同一域的组之间进行。"

他指着图上的一个节点:"比如在 DIE 域中,每个 SMT 域就是一个调度组。负载均衡器会在 DIE 域的调度组之间迁移进程,而不是在单个 CPU 之间迁移。"

"为什么用组而不是单个 CPU?"

"因为调度组内的 CPU 共享资源——比如超线程共享物理核心,同一芯片的 CPU 共享 LLC。在组内迁移进程没有意义,因为资源是共享的。只有在组之间迁移,才能真正平衡负载。"

林小源看着那张图,突然意识到调度域和调度组的设计不仅仅是为了负载均衡——它还反映了硬件的层次结构。内核不是在抽象地管理 CPU,而是在真实地映射硬件的拓扑。

"这就是内核的智慧。"林小源喃喃道,"不是强行统一,而是尊重差异。"

老者点头:"每个 CPU 都有自己的特点——有的快,有的慢,有的在本地,有的在远程。调度域和调度组的设计,正是为了让调度器能够理解和利用这些差异。"


c
/*
 * 调度域 (sched_domain) 的层次:
 *
 *   DIE 域(最底层)
 *   ├── SMT 域(超线程)
 *   │   ├── CPU 0
 *   │   └── CPU 1
 *   └── SMT 域(超线程)
 *       ├── CPU 2
 *       └── CPU 3
 *
 *   NUMA 域(跨节点)
 *   ├── DIE 域 (Node 0: CPU 0, 1)
 *   └── DIE 域 (Node 1: CPU 2, 3)
 *
 * 调度组 (sched_group):
 *   一个调度域中的 CPU 子集
 *   负载均衡在同一域的组之间进行
 */

struct sched_domain {
    char name[32];
    int span[8];        /* 域中包含的 CPU */
    int nr_cpus;
    int balance_interval;  /* 均衡间隔 (ms) */
};

struct sched_group {
    char name[32];
    int cpus[8];
    int nr_cpus;
    int load;
};

printf("=== 调度域与调度组 ===\n\n");

/* 4 个 CPU,2 个 NUMA 节点 */
printf("系统拓扑:\n");
printf("  NUMA Node 0: CPU 0, CPU 1 (同一物理核心,超线程)\n");
printf("  NUMA Node 1: CPU 2, CPU 3 (同一物理核心,超线程)\n\n");

/* SMT 域 */
struct sched_domain smt_0 = {
    "SMT-0", {0, 1}, 2, 1
};
struct sched_domain smt_1 = {
    "SMT-1", {2, 3}, 2, 1
};

/* DIE 域 */
struct sched_domain die_0 = {
    "DIE-0", {0, 1}, 2, 4
};
struct sched_domain die_1 = {
    "DIE-1", {2, 3}, 2, 4
};

/* NUMA 域 */
struct sched_domain numa = {
    "NUMA", {0, 1, 2, 3}, 4, 8
};

printf("调度域层次(从低到高):\n\n");

printf("SMT 域(超线程级):\n");
printf("  %s: CPU %d, %d (间隔 %dms)\n",
       smt_0.name, smt_0.span[0], smt_0.span[1], smt_0.balance_interval);
printf("  %s: CPU %d, %d (间隔 %dms)\n\n",
       smt_1.name, smt_1.span[0], smt_1.span[1], smt_1.balance_interval);

printf("DIE 域(物理核心级):\n");
printf("  %s: CPU %d, %d (间隔 %dms)\n",
       die_0.name, die_0.span[0], die_0.span[1], die_0.balance_interval);
printf("  %s: CPU %d, %d (间隔 %dms)\n\n",
       die_1.name, die_1.span[0], die_1.span[1], die_1.balance_interval);

printf("NUMA 域(跨节点级):\n");
printf("  %s: CPU %d, %d, %d, %d (间隔 %dms)\n\n",
       numa.name, numa.span[0], numa.span[1],
       numa.span[2], numa.span[3], numa.balance_interval);

printf("--- 负载均衡的策略 ---\n");
printf("先在最低层域中均衡:\n");
printf("  SMT 域: 间隔 1ms,频繁检查\n");
printf("  DIE 域: 间隔 4ms,中等频率\n");
printf("  NUMA 域: 间隔 8ms,最少检查\n\n");
printf("低层域无法均衡时,才向高层域求助\n");
printf("这样可以最小化跨域迁移的开销\n");

道藏笔记

内核启示

调度域和调度组定义了 CPU 的层次结构。

调度域的层次(从低到高):

  1. SMT 域 — 超线程级,共享物理核心
  2. MC 域 — 物理核心级,共享 LLC(最后级缓存)
  3. DIE 域 — 芯片级
  4. NUMA 域 — 跨节点级

负载均衡的层次化策略:

  • 先在最低层域中均衡
  • 低层域无法均衡时,才向高层域求助
  • 最小化跨域迁移的开销

调度域的配置:

  • — 均衡间隔(低层更频繁)
  • — 缓存友好性尝试次数
  • flags — 域的特性标志

调度域让负载均衡"由近及远"——先解决近距离的问题。


破关试炼

域与组之试

调度域不会每一刻都搬运任务,本章用哪个参数控制负载均衡检查间隔?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道