核心结论:Xkernel 不自动发现哪些 perf-const 值得调优,也不自动选择最优值—— 它把"机制(mechanism)与策略(policy)彻底分离":SIE 提供安全的运行时更新机制,策略完全交给用户/Agent 编写的 eBPF 程序。 但正是这种分离让运行时环境信号(PID、cgroup、设备、RTT、负载历史)能任意参与决策。

1. 机制与策略的彻底分离

论文 §3.2 明确提出五条设计原则,第一条就是"将值更新与调优策略分离"—— SIE 只做"如何安全地把值更新到状态","何时更新、更新为多少"完全交给 Xk-tune(eBPF 策略)§3.2。

论文原话:"The focus of this work is the mechanism and interface to realize principled OS tunability, not the policies on deciding the optimal values of each perf-const." — §5.3

1.1 三层职责切分

决策层 · 人类/Agent选择 perf-const基于经验/分析/类比编写 Xk-tune 策略条件 + xk_set设计粒度与值域per-flow/per-device/...离线分析层 · Xkernel (一次性)二进制 diff识别种子指令符号执行推导 IV↔V 映射生成作用域表CS/SS/挂载点在线机制层 · Xk-runtime (毫秒级)eBPF 加载 + Kprobe 挂载策略生效SIE 间接执行符号化更新状态自收敛转换保证副作用安全作用域表Xk-tune + 安全门

图 1:Xkernel 把任务切成三个清晰层级。人类/Agent 负责"决定调什么、怎么调", 离线分析负责"找到位置、构建作用域",运行时机制负责"安全执行更新"。 三者通过作用域表(offline→runtime)和Xk-tune 桩(human→runtime)解耦。

1.2 两个最小 API 构成的契约

Xk-tune 看到的接口极简:

typedef const struct xk_ctx * xk_handle_t;

#define XK_TUNE(unique_name, perfconst_id, args...)

long  xk_set(xk_handle_t xk_ctx, u64 val);          // 写入新值
bool xk_transition_done(xk_handle_t xk_ctx);   // 转换是否完成(强制安全门)xk.h · Fig 6

用户拥有完整的 eBPF 能力:读任意内核状态、与 eBPF map 通信、读取硬件计数器—— 唯一受限制的是写内核状态必须经 sie_write_kernel kfunc§3.6。

2. 运行时环境信号的全谱系

既然策略交给用户,"怎么获取运行时信息"就成了关键。Xkernel 与 eBPF 的能力等价—— 任何 eBPF 能读取的信号都能用于调优决策。论文中展示了五个具有代表性的信号类型。

可用信号eBPF 全谱网络流属性 (RTT/拥塞)进程/线程身份 (PID/cgroup)硬件可观测性 (计数器/dev)历史行为 (task storage)调用栈/调用方应用主动 hint

图 2:Xk-tune 可见的运行时信号来源。所有 eBPF helper(PID、cgroup、task storage、kfunc) 与 BPF_CORE_READ 读取的内核结构共同构成"运行时上下文"。

2.1 信号一:网络流的 RTT(Case-5)

粒度:per-flow 信号:struct sock 中的 curr_rtt
XK_TUNE(tcp_hystart, "net/ipv4/tcp_cubic.c:L349:3:0") {
    if (!xk_transition_done(xk_ctx)) return 0;
    struct sock *sk  = (struct sock *)PT_REGS_PARM1(ctx);
    struct bictcp *ca = inet_csk_ca(sk);
    u32 cur_rtt = BPF_CORE_READ(ca, curr_rtt);
    // 长 RTT(≥80ms)流:缩放因子 = 1(更激进退出慢启动)
    if (cur_rtt >= 80000) xk_set(xk_ctx, 1);
    return 0;
}Case-5

该策略让同一个 perf-const HYSTART_DELAY 的 SF 因子在不同流上取不同值—— 短 RTT 流用 SF=3(保守),长 RTT 流用 SF=1(激进),结果 P99.99 FCT 下降 81%§5.1。

2.2 信号二:应用通过 eBPF map 传递的语义提示

粒度:per-thread 信号:应用主动写入的 hint map
XK_TUNE(blk_max_request, "include/linux/blkdev.h:L123:32:0") {
    u64 pid = bpf_get_current_pid_tgid() & 0xFFFFFFFF;
    int *hint = bpf_map_lookup_elem(&hint_map, &pid);
    // RocksDB 标记的"压缩线程":禁掉攒批
    if (hint && *hint == COMPRESSION_THREAD) {
        xk_set(xk_ctx, 1);
    }
}Fig 13

应用可主动告诉内核"我这条线程是后台压缩线程,请用更小的攒批"—— 这是传统 sysctl 完全无法表达的应用感知调优§5.2。

2.3 信号三:内核内部可观测性的即席启发式

粒度:per-task 信号:bpf_task_storage 累积的失败计数
// 跟踪器:连续失败合并次数
SEC("kretprobe/blk_attempt_plug_merge")
int BPF_KRETPROBE(blk_attempt_plug_merge_ret, long ret) {
    int *fail_cnt = bpf_task_storage_get(&fail_cnt_map, ...);
    if (ret == 0 && fail_cnt) (*fail_cnt)++;
    else *fail_cnt = 0;
}

// 调优:连续失败 ≥16 → 推断为随机负载 → 攒批=1
XK_TUNE(blk_add_rq_to_plug, "block/blk.h:L312:32:0") {
    int *fail_cnt = bpf_task_storage_get(&fail_cnt_map, ...);
    if (fail_cnt && *fail_cnt >= 16) xk_set(xk_ctx, 1);
}Fig 14

这是论文中最有启发的模式之一:用运行时观察推断负载类型。 顺序负载的合并失败率低,随机负载高——策略观察一段时间后才"学会"该用哪个值。 这种启发式不可能被硬编码到内核源码§5.2。

2.4 信号四:调用方身份(多 shrinker 共享常量)

Linux 有 43 个 shrinker 共享同一个 SHRINK_BATCH 批量大小。sysctl 时代只能设一个全局值—— 但 zswap-shrinker(数据中心)与 inode-shrinker(文件密集)的最优批大小可能差 5 倍。 Xkernel 允许在 Xk-tune 中检查调用方,为不同 shrinker 设不同值§5.2。

2.5 信号五:硬件计数器(NUMA 调优)

Case-4 中 NR_MAX_BATCHED_MIGRATION 的最优值需要运行时观察 TLB shootdown 次数—— 这些信息通过 BPF_CORE_READ 读 perf 计数器或内核统计获得。 这正是论文强调的"用内核和硬件可观测性进行调优和推理"§5.1。

3. 灵活粒度的本质:条件就是粒度

Xkernel 的粒度灵活性不是"系统提供多种粒度选项",而是"粒度由用户条件任意定义"—— 同一 Xk-tune 内部的不同 xk_set 调用条件就是天然的粒度切分。

3.1 粒度维度的对照

粒度维度 传统 sysctl/sysfs Xkernel
全局 支持 默认 支持 条件不分支
per-PID / per-thread 不支持 需改源码 支持 bpf_get_current_pid_tgid
per-cgroup 不支持 需 memcg/Blki cgroup 子系统支持 支持 bpf_get_current_cgroup_id
per-device 不支持 需 per-device sysfs 支持 检查 struct request_queue
per-flow(网络) 不支持 需 tc/qdisc 支持 PT_REGS_PARM1(ctx) → sock
per-callsite(同一代码路径) 不支持 不可能 支持 检查调用栈/调用方
运行时动态切换 不支持 值固定,运行时可改但全局改 支持 eBPF map 通知+任意条件

3.2 同一 Xk-tune 表达多粒度的案例

Case-1 中,同一段 Xk-tune 代码同时为以下场景设置不同值§5.1:

这就是"粒度即条件"——代码中每个 xk_set 的 if 条件都是一次粒度切分。

4. 选择哪些 perf-const:人工 vs 自动的现状

这是您问题的关键。论文给出的答案是明确的人工驱动,但留下了一个非常清晰的接口供未来 AI 代理接入。

4.1 论文明确不做什么

"We expect the new opportunities and flexibility enabled by Xkernel to inspire many new tuning techniques (e.g., with AI)."
— §5.3

具体来说,Xkernel 不做的事:

4.2 Xkernel 自动做的事

4.3 论文评估的 140 个 perf-const 是怎么选的

完全人工:研究人员从四个 Linux 子系统(CPU 调度、内存、存储、网络)手工挑选, 数量与 sysctl 已暴露的 145 个性能旋钮相当,作为概念验证§6。 没有自动化筛选过程的描述——这是未来工作。

4.4 与 AI Agent 结合的接口(论文已设想)

论文 §5.2 与微软研究院的官方解读都强调了 Xkernel 作为 AI Agent 操作系统接口的潜力:

"当调优接口是可编程、有作用域、经过验证、毫秒级生效的,那它的价值便不再局限于人工操作, 而可以成为 AI agent 安全操作的接口。Agent 可以在线观测 workload(Xkernel 兼容 eBPF, 可与 eBPF 观测体系结合)、在线生成并部署调优代码,为每个 workload 和每套硬件持续搜索特化的最优配置, 安全机制则确保整个过程不会破坏系统的正确性和稳定性。" — 微软研究院 OSDI'26

这条路径是清晰的:

  1. 观测:Agent 通过 eBPF 收集运行时信号(与 Xk-tune 用同一套机制)
  2. 决策:LLM/RL Agent 根据观测决定要调哪些 perf-const、调成什么值
  3. 执行:Agent 生成 Xk-tune 代码,由 xk-load 部署
  4. 验证:Xkernel 自带的副作用安全保证 Agent 不会破坏系统

但这一切是未来工作——论文本身只提供了基础设施层。

5. 离线分析与在线策略的协作

理解"调优变量怎么选"还需要看到 Xkernel 的双层架构: 离线一次性投入 + 在线高频决策,两者通过作用域表连接。

5.1 离线阶段(一次性,18 分钟/perf-const)

xk-build 输出作用域表(scope table)
  • Critical Span 地址:perf-const 进入运行时状态的精确指令序列
  • Safe Span 边界:所有受副作用影响的代码范围
  • 符号状态表达式:R/M ← f(R/M, IV) 形式化描述
  • IV↔V 线性映射:IV = a·V + b,让策略可用源码值表达
  • Kprobe 挂载点:函数+偏移,SIE 间接的入口

※ 作用域表与精确的内核源、编译器版本、构建配置绑定,可跨部署机器复用。

5.2 在线阶段(毫秒级)

xk-load + Xk-tune 运行时
  1. Xk-runtime 用作用域表定位 SIE 间接代码
  2. 包装为嵌入函数指针存入 xk_ctx.set_fn
  3. 在生成的 kprobe handler 中调用用户策略
  4. 策略通过 xk_set(xk_ctx, val) 触发 SIE 间接更新
  5. SIE 检查 xk_transition_done,必要时等待安全点

5.3 一张表总结职责切分

问题谁负责何时
哪些 perf-const 值得调? 人类/Agent 部署前或运行时
常量在二进制中的位置? Xkernel 离线分析(自动) 每次内核升级后一次
调成什么值? Xk-tune 策略(用户/Agent 编写) 每次 perf-const 触发时
何时切换值? Xk-tune 条件逻辑(用户/Agent 编写) 每次触发时决策
切换是否安全? Xkernel 运行时(自动) 每次切换时验证
切换消耗多少资源? Xkernel 运行时(自动) 每次切换时计量

6. 实操示例:用户从零构建一个调优策略

让我们把"怎么调"具体化——一个工程师如何用 Xkernel 调一个常量。

6.1 工作流四步

1. xk-build→ 2. xk-gen→ 3. 编写 Xk-tune→ 4. xk-load

6.2 步骤详解

# 步骤 1:离线分析(一次性,~18 分钟)
xk-build --target net/ipv4/tcp_cubic.c:349:3:0 \
         --kernel-src /usr/src/linux-6.8.0 \
         --out scope_table.json

# 步骤 2:生成 Xk-tune 桩(自动)
xk-gen --scope scope_table.json --out tcp_hystart_stub.c

# 步骤 3:工程师在桩中编写策略
# (在生成的桩内填充 if 条件和 xk_set 调用)
vim tcp_hystart_stub.c

# 步骤 4:编译并加载到运行中的内核
clang -target bpf -O2 -c tcp_hystart_stub.c -o tcp_hystart.o
xk-load --bpf tcp_hystart.o

# 卸载(有序回滚)
xk-unload --bpf tcp_hystart.oworkflow

6.3 关键点:什么时候需要重新做离线分析

不需要重新分析的:策略值变化、新增 if 条件、不同应用接入。 作用域表与策略代码正交——这是机制与策略分离的工程红利。

7. 直答您的两个问题

7.1 "要调哪些变量是怎么选择的?"

答案:当前由人(开发者或运维)基于领域知识选择,AI Agent 接入是论文明确设想的未来工作。 具体路径有四种:
  1. 经验:开发者根据系统知识挑出可疑常量(论文 140 个如此选出)
  2. 性能瓶颈分析:通过 perf / bpftool 定位热点,再追溯到 perf-const
  3. 类比迁移:从已暴露的 sysctl 出发,推断同类未暴露的常量
  4. Agent 自动发现:论文 §5.3 留白,未来由 AI 工具承担

7.2 "要有环境进行运行时调整和调优吗?"

答案:Xkernel 的整个设计就是为运行时调整而生——不仅"是",而且是其相对于 sysctl/KLP 的核心差异。
  • 运行时调整是 Xkernel 的本职:毫秒级更新、毫秒级安全转换、无需重启
  • 运行时环境信号是策略的燃料:任何 eBPF 可读取的信号(PID/cgroup/RTT/硬件计数器/历史状态)都可参与决策
  • 粒度由运行时条件定义:同一段代码可对不同进程/设备/流取不同值
  • 唯一不在运行时做的:分析 perf-const 在二进制中的位置(必须离线,且一次性)

7.3 一句话总结

Xkernel 的精妙之处在于:它把"调什么"和"调成什么"彻底从"如何安全地改一个二进制常量"中分离出来。 前者是策略研究(论文留白,等 AI 接入),后者是机制工程(论文完成)。 两者通过作用域表 + Xk-tune 这两个最小接口松耦合协作,让运行时环境信号能以前所未有的丰富度参与决策。