[TOC]

static_calljump_label 的区别及异同点

static_calljump_label 都是 Linux 内核中的优化机制,旨在提高性能,减少运行时的分支判断和间接调用开销。尽管它们的目标相似,但它们的实现方式和应用场景有所不同。


相同点

  1. 动态修改代码路径

    • 两者都支持在运行时动态修改代码路径,从而实现功能的启用或禁用。
    • 通过修改指令(代码补丁),避免了传统的条件分支或间接调用的性能开销。
  2. 性能优化

    • 两者都旨在减少分支预测失败或间接调用的开销,适用于性能敏感的代码路径。
  3. 运行时灵活性

    • 两者都允许在运行时动态切换功能或行为,而无需重新编译或重启系统。
  4. 代码补丁机制

    • 两者都依赖于代码补丁(code patching)技术,通过修改内存中的指令来实现动态行为。

不同点

特性 static_call jump_label
主要用途 用于优化函数调用,将间接调用替换为直接调用。 用于动态启用或禁用代码块,优化条件分支判断。
优化的内容 函数调用路径(间接调用 → 直接调用)。 条件分支路径(条件判断 → 无条件跳转或 NOP)。
实现方式 使用静态调用点(static_call)和跳板(trampoline)。 使用静态键(static_key)和条件跳转指令。
代码修改的粒度 修改函数调用点的指令。 修改条件分支的跳转指令或替换为 NOP。
典型场景 动态切换函数实现,例如模块化设计中的函数替换。 动态启用或禁用调试、跟踪等功能。
依赖的内核配置 CONFIG_HAVE_STATIC_CALL_INLINE CONFIG_JUMP_LABEL
性能提升的方式 消除间接调用的开销,直接跳转到目标函数。 消除条件分支的开销,避免分支预测失败。
动态更新的接口 使用 static_call_update 更新目标函数。 使用 static_key_enablestatic_key_disable 启用或禁用功能。

工作原理对比

1. static_call 的工作原理

  • 静态调用点
    • 使用 DECLARE_STATIC_CALLDEFINE_STATIC_CALL 定义静态调用点。
    • 调用点初始绑定到默认函数。
  • 动态更新
    • 使用 static_call_update 在运行时更新调用点的目标函数。
    • 在支持内联补丁的架构上,直接修改调用点的指令,将其替换为目标函数的直接跳转指令。
  • 性能优化
    • 消除了函数指针调用的间接开销,避免了 retpoline 的性能损耗。

2. jump_label 的工作原理

  • 静态键
    • 使用 static_key 表示功能的启用或禁用状态。
  • 动态修改
    • 使用 static_key_enablestatic_key_disable 修改静态键的状态。
    • 在运行时修改条件分支的跳转指令,将其替换为无条件跳转或 NOP。
  • 性能优化
    • 消除了条件分支的判断开销,避免了分支预测失败。

使用场景对比

static_call 的典型使用场景

  1. 模块化设计
    • 在模块加载时动态切换函数实现,例如替换默认实现为模块提供的实现。
  2. 性能敏感的函数调用
    • 在频繁调用的代码路径中,减少函数指针调用的开销。

jump_label 的典型使用场景

  1. 调试和跟踪
    • 动态启用或禁用调试和跟踪功能,例如 tracepointskprobes
  2. 动态功能开关
    • 根据运行时配置动态启用或禁用某些功能,例如内核的调试选项。

总结

  • static_call

    • 主要用于优化函数调用路径,适合动态切换函数实现的场景。
    • 通过直接跳转到目标函数,消除了间接调用的开销。
  • jump_label

    • 主要用于优化条件分支判断,适合动态启用或禁用代码块的场景。
    • 通过修改跳转指令,消除了分支预测失败的开销。

两者在实现动态行为和性能优化方面各有侧重,但都依赖于代码补丁技术,是 Linux 内核中重要的动态优化机制。

jump label 跳转分支

什么是 Jump Label?

Jump Label 是 Linux 内核中的一种优化机制,用于动态地启用或禁用代码路径。它通过修改指令来避免不必要的分支判断,从而提高性能。Jump Label 的核心思想是将条件分支转换为直接跳转(或不跳转),从而减少分支预测失败的开销。


Jump Label 的使用场景

  1. 调试和跟踪

    • Jump Label 常用于内核的调试和跟踪功能(如 tracepointskprobes),这些功能在运行时可能被频繁启用或禁用。
    • 通过 Jump Label,可以在禁用这些功能时完全跳过相关代码,从而避免性能损耗。
  2. 动态功能开关

    • Jump Label 可用于实现动态功能开关,例如在运行时启用或禁用某些内核特性。
    • 例如,某些内核模块可能需要根据配置动态启用或禁用特定功能。
  3. 性能优化

    • 在性能敏感的代码路径中,Jump Label 可以减少分支判断的开销,从而提高代码执行效率。

Jump Label 的工作原理

  1. 静态键(Static Key)

    • Jump Label 的核心是静态键(static_key),它是一个内核数据结构,用于表示某个功能是否启用。
    • 静态键的值可以在运行时动态修改,从而控制代码路径的跳转行为。
  2. 代码生成

    • 在编译时,Jump Label 会将条件分支替换为一个直接跳转指令。
    • 例如:
      1
      2
      3
      if (static_key_enabled(&key)) {
      // 执行某些操作
      }
      在编译后会被转换为:
      1
      jmp <target>
  3. 动态修改指令

    • 在运行时,Jump Label 可以通过修改跳转指令来启用或禁用代码路径。
    • 如果功能被禁用,Jump Label 会将跳转指令替换为一个 NOP(No Operation)指令,从而跳过相关代码。
  4. 分支预测优化

    • 由于 Jump Label 消除了条件分支,CPU 不需要进行分支预测,从而避免了分支预测失败的开销。

Jump Label 的优点

  1. 性能提升

    • 通过消除不必要的分支判断,Jump Label 可以显著提高性能,特别是在频繁执行的代码路径中。
  2. 动态性

    • Jump Label 支持在运行时动态修改代码路径,适应不同的运行时需求。
  3. 透明性

    • 对开发者来说,Jump Label 的使用是透明的,开发者只需使用静态键 API,无需关心底层实现细节。

总结

Jump Label 是 Linux 内核中的一种动态优化机制,通过修改指令实现代码路径的动态启用或禁用。它广泛应用于调试、跟踪和动态功能开关等场景,能够显著减少分支预测失败的开销,从而提高性能。Jump Label 的核心是静态键(static_key),通过运行时修改指令实现高效的分支控制。这种机制在性能敏感的内核代码中非常重要,体现了内核对高效性和灵活性的追求。

include/linux/jump_label.h

jump_label_init 分支跳转初始化允许

1
2
3
4
5
6
7
8
9
10
/*
* 如果在调用 jump_label_init 之前使用 static_key作函数,则用于生成警告。
*/
bool static_key_initialized __read_mostly;
EXPORT_SYMBOL_GPL(static_key_initialized);

static __always_inline void jump_label_init(void)
{
static_key_initialized = true;
}

DEFINE_STATIC_KEY_MAYBE 静态键定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define STATIC_KEY_INIT_TRUE	{ .enabled = ATOMIC_INIT(1) }
#define STATIC_KEY_INIT_FALSE { .enabled = ATOMIC_INIT(0) }

#define STATIC_KEY_TRUE_INIT (struct static_key_true) { .key = STATIC_KEY_INIT_TRUE, }
#define STATIC_KEY_FALSE_INIT (struct static_key_false){ .key = STATIC_KEY_INIT_FALSE, }

#define DEFINE_STATIC_KEY_TRUE(name) \
struct static_key_true name = STATIC_KEY_TRUE_INIT

#define DEFINE_STATIC_KEY_FALSE(name) \
struct static_key_false name = STATIC_KEY_FALSE_INIT

#define _DEFINE_STATIC_KEY_1(name) DEFINE_STATIC_KEY_TRUE(name)
#define _DEFINE_STATIC_KEY_0(name) DEFINE_STATIC_KEY_FALSE(name)
#define DEFINE_STATIC_KEY_MAYBE(cfg, name) \
__PASTE(_DEFINE_STATIC_KEY_, IS_ENABLED(cfg))(name)

STATIC_KEY_CHECK_USE 检测静态键是否可以被使用

1
2
3
#define STATIC_KEY_CHECK_USE(key) WARN(!static_key_initialized,		      \
"%s(): static key '%pS' used before call to jump_label_init()", \
__func__, (key))

static_branch_disable static_branch_enable 静态分支禁用 静态分支启用

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
#define static_branch_enable(x)			static_key_enable(&(x)->key)
#define static_branch_disable(x) static_key_disable(&(x)->key)

static inline void static_key_enable(struct static_key *key)
{
STATIC_KEY_CHECK_USE(key);

if (atomic_read(&key->enabled) != 0) {
WARN_ON_ONCE(atomic_read(&key->enabled) != 1);
return;
}
atomic_set(&key->enabled, 1);
}

static inline void static_key_disable(struct static_key *key)
{
STATIC_KEY_CHECK_USE(key);

if (atomic_read(&key->enabled) != 1) {
//如果 enabled 的值既不是 1(启用状态),也不是 0(禁用状态),则触发警告
WARN_ON_ONCE(atomic_read(&key->enabled) != 0);
return;
}
atomic_set(&key->enabled, 0);
}

static_key_fast_inc_not_disabled 静态键快速增加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static inline bool static_key_fast_inc_not_disabled(struct static_key *key)
{
int v;

STATIC_KEY_CHECK_USE(key);
/*
* 防止启用键>的否定运算遵循与 CONFIG_JUMP_LABEL=y 相同的语义,请参阅 kernel/jump_label.c 注释。
*/
v = atomic_read(&key->enabled);
do {
if (v < 0 || (v + 1) < 0)
return false;
} while (!likely(atomic_try_cmpxchg(&key->enabled, &v, v + 1)));
return true;
}
#define static_key_slow_inc(key) static_key_fast_inc_not_disabled(key)

static_branch_inc 静态分支增加

1
2
3
4
5
6
7
8
/*
* Advanced usage; refcount, branch is enabled when: count != 0
*/

#define static_branch_inc(x) static_key_slow_inc(&(x)->key)
#define static_branch_dec(x) static_key_slow_dec(&(x)->key)
#define static_branch_inc_cpuslocked(x) static_key_slow_inc_cpuslocked(&(x)->key)
#define static_branch_dec_cpuslocked(x) static_key_slow_dec_cpuslocked(&(x)->key)

static_branch_maybe

  1. 分支类型和初始值的组合:
    • 分支类型(likely 或 unlikely)和静态键的初始值(true 或 false)共同决定生成的指令。
    • 例如,当分支类型为 likely 且初始值为 true 时,生成的指令是 NOP(无操作),表示直接执行分支路径,无需跳转。
  2. 逻辑表:
    • enabled 表示静态键是否启用。
    • type 表示静态键的初始值(true 或 false)。
    • branch 表示分支类型(likely 或 unlikely)。
    • 生成的指令可以是 NOP 或 JMP,分别表示直接执行或跳转。
  3. 动态和静态逻辑:
    • 动态逻辑:instruction = enabled ^ branch。
    • 静态逻辑:instruction = type ^ branch。
    • 这表明分支指令的生成是通过静态键的状态和分支类型的异或操作决定的。
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#ifdef CONFIG_JUMP_LABEL

/*
* Combine the right initial value (type) with the right branch order
* to generate the desired result.
*
*
* type\branch| likely (1) | unlikely (0)
* -----------+-----------------------+------------------
* | |
* true (1) | ... | ...
* | NOP | JMP L
* | <br-stmts> | 1: ...
* | L: ... |
* | |
* | | L: <br-stmts>
* | | jmp 1b
* | |
* -----------+-----------------------+------------------
* | |
* false (0) | ... | ...
* | JMP L | NOP
* | <br-stmts> | 1: ...
* | L: ... |
* | |
* | | L: <br-stmts>
* | | jmp 1b
* | |
* -----------+-----------------------+------------------
*
* The initial value is encoded in the LSB of static_key::entries,
* type: 0 = false, 1 = true.
*
* The branch type is encoded in the LSB of jump_entry::key,
* branch: 0 = unlikely, 1 = likely.
*
* This gives the following logic table:
*
* enabled type branch instuction
* -----------------------------+-----------
* 0 0 0 | NOP
* 0 0 1 | JMP
* 0 1 0 | NOP
* 0 1 1 | JMP
*
* 1 0 0 | JMP
* 1 0 1 | NOP
* 1 1 0 | JMP
* 1 1 1 | NOP
*
* Which gives the following functions:
*
* dynamic: instruction = enabled ^ branch
* static: instruction = type ^ branch
*
* See jump_label_type() / jump_label_init_type().
*/

#define static_branch_likely(x) \
({ \
bool branch; \
//检查 x 的类型是否为 struct static_key_true
if (__builtin_types_compatible_p(typeof(*x), struct static_key_true)) \
branch = !arch_static_branch(&(x)->key, true); \
//检查 x 的类型是否为 struct static_key_false
else if (__builtin_types_compatible_p(typeof(*x), struct static_key_false)) \
branch = !arch_static_branch_jump(&(x)->key, true); \
else \
//则触发错误
branch = ____wrong_branch_error(); \
likely_notrace(branch); \
})

#define static_branch_unlikely(x) \
({ \
bool branch; \
if (__builtin_types_compatible_p(typeof(*x), struct static_key_true)) \
branch = arch_static_branch_jump(&(x)->key, false); \
else if (__builtin_types_compatible_p(typeof(*x), struct static_key_false)) \
branch = arch_static_branch(&(x)->key, false); \
else \
branch = ____wrong_branch_error(); \
unlikely_notrace(branch); \
})

#else /* !CONFIG_JUMP_LABEL */

#define static_branch_likely(x) likely_notrace(static_key_enabled(&(x)->key))
#define static_branch_unlikely(x) unlikely_notrace(static_key_enabled(&(x)->key))

#endif /* CONFIG_JUMP_LABEL */

#define static_branch_maybe(config, x) \
(IS_ENABLED(config) ? static_branch_likely(x) \
: static_branch_unlikely(x))

arch/arm/include/asm/jump_label.h

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
#define JUMP_LABEL_NOP_SIZE 4

/*这个宏也在 Rust 方面进行了扩展。 */
//生成一个静态分支的汇编代码模板
#define ARCH_STATIC_BRANCH_ASM(key, label) \
//定义一个局部标签 1,表示当前指令的位置
"1:\n\t" \
//插入一个 nop(无操作)指令,表示默认情况下不跳转
WASM(nop) "\n\t" \
//将跳转信息写入 __jump_table 段
".pushsection __jump_table, \"aw\"\n\t" \
//将标签 1 的地址、目标标签 label 和静态键 key 的地址写入跳转表
".word 1b, " label ", " key "\n\t" \
".popsection\n\t" \

static __always_inline bool arch_static_branch(struct static_key *key, bool branch)
{
//使用 GCC 的 asm goto 扩展,允许在汇编代码中使用条件跳转
asm goto(ARCH_STATIC_BRANCH_ASM("%c0", "%l[l_yes]")
//将静态键 key 和分支类型 branch 的地址作为输入
: : "i" (&((char *)key)[branch]) : :
l_yes); //如果条件满足,则跳转到标签 l_yes,返回 true

return false;
l_yes:
return true;
}
//类似于 arch_static_branch,但默认情况下插入一个跳转指令(b),而不是 nop
static __always_inline bool arch_static_branch_jump(struct static_key *key, bool branch)
{
asm goto("1:\n\t"
WASM(b) " %l[l_yes]\n\t" //插入一个跳转指令 b,表示默认情况下跳转到目标标签 l_yes
".pushsection __jump_table, \"aw\"\n\t"
".word 1b, %l[l_yes], %c0\n\t"
".popsection\n\t"
: : "i" (&((char *)key)[branch]) : : l_yes);

return false;
l_yes:
return true;
}

static_call 静态调用

static_call 是 Linux 内核中的一种优化机制,用于通过代码动态修改实现高效的函数调用。它通过在运行时对函数调用点进行代码补丁(code patching),将间接函数调用(通过函数指针)替换为直接函数调用(直接跳转到目标函数)。这种机制可以显著提高性能,尤其是在需要频繁调用的代码路径中。


static_call 的使用场景

  1. 性能敏感的代码路径

    • 在内核中,某些代码路径可能需要频繁调用函数。如果使用传统的函数指针调用,会带来额外的间接调用开销。static_call 可以将这些调用优化为直接调用,从而提高性能。
  2. 动态功能切换

    • static_call 支持在运行时动态更新函数调用目标。例如,可以在模块加载或配置更改时切换到不同的实现函数,而无需重新编译或重启系统。
  3. 替代 retpoline

    • 在缓解 Spectre v2 漏洞时,内核引入了 retpoline 技术,但这会显著降低性能。static_call 可以避免使用 retpoline,从而减少性能损耗。
  4. 模块化设计

    • 在模块化的内核设计中,static_call 可以用于实现模块之间的高效函数调用,同时保持灵活性。

static_call 的工作原理

  1. 声明和定义静态调用

    • 使用 DECLARE_STATIC_CALLDEFINE_STATIC_CALL 宏声明和定义一个静态调用点。例如:
      1
      2
      DECLARE_STATIC_CALL(my_static_call, default_func);
      DEFINE_STATIC_CALL(my_static_call, default_func);
    • 这会创建一个静态调用点,并将其初始值设置为 default_func
  2. 调用静态函数

    • 使用 static_call(name)(args...) 调用静态函数。例如:
      1
      static_call(my_static_call)(arg1, arg2);
    • 在运行时,这会直接跳转到当前绑定的目标函数。
  3. 动态更新调用目标

    • 使用 static_call_update 更新静态调用点的目标函数。例如:
      1
      static_call_update(my_static_call, new_func);
    • 这会在运行时修改调用点的代码,将目标函数更新为 new_func
  4. 代码补丁(Code Patching)

    • 在支持 CONFIG_HAVE_STATIC_CALL_INLINE 的架构上,static_call 会直接修改调用点的机器指令,将其替换为目标函数的直接跳转指令。
    • 如果架构不支持内联补丁,则通过一个中间的跳板(trampoline)实现动态调用。
  5. 查询当前绑定的函数

    • 使用 static_call_query 查询当前绑定的目标函数。例如:
      1
      func = static_call_query(my_static_call);

static_call 的优点

  1. 性能提升

    • 通过将间接调用优化为直接调用,static_call 可以显著减少函数调用的开销。
  2. 动态性

    • 支持在运行时动态更新函数调用目标,适应不同的运行时需求。
  3. 安全性

    • 避免了传统函数指针调用可能带来的安全问题,例如函数指针被错误修改或滥用。
  4. 灵活性

    • 支持动态功能切换,适用于模块化设计和动态配置场景。

static_call 的实现细节

  1. 静态调用点的定义

    • 每个静态调用点由一个 static_call_key 结构体表示,包含目标函数的指针和其他元数据。
  2. 跳板(Trampoline)

    • 每个静态调用点都有一个跳板函数,初始时跳板指向默认函数。
    • 在运行时,跳板的目标地址可以被动态修改。
  3. 内联补丁

    • 在支持 CONFIG_HAVE_STATIC_CALL_INLINE 的架构上,static_call 会直接修改调用点的机器指令,将其替换为目标函数的直接跳转指令。
    • 这需要编译器或工具链的支持(如 objtool),以标记所有静态调用点。
  4. 回退机制

    • 如果架构不支持内联补丁,static_call 会通过跳板实现动态调用。

static_call 的关键函数和宏

  1. 声明和定义

    • DECLARE_STATIC_CALL(name, func):声明一个静态调用点。
    • DEFINE_STATIC_CALL(name, func):定义一个静态调用点,并设置默认函数。
  2. 调用和更新

    • static_call(name)(args...):调用静态函数。
    • static_call_update(name, func):更新静态调用点的目标函数。
  3. 查询

include/linux/static_call.h