Linux内核static-keys

一、static-keys.txt 翻译

静态键
-----------

作者:Jason Baron <jbaron@redhat.com>

0) 摘要

静态键允许通过 GCC 功能和代码修补技术在对性能敏感的快速路径内核代码中包含很少使用的功能。 一个简单的例子:

struct static_key key = STATIC_KEY_INIT_FALSE;
...
if (static_key_false(&key))
    do unlikely code
else
    do likely code
...
static_key_slow_inc();
...
static_key_slow_inc();
...

static_key_false() 分支将生成到代码中,对可能的代码路径的影响尽可能小。


1) 动机

目前,跟踪点是使用条件分支实现的。 条件检查需要检查每个跟踪点的全局变量。 虽然这个检查的开销很小,但是当内存缓存受到压力时它会增加(这些全局变量的内存缓存行可能与其他内存访问共享)。
随着我们增加内核中跟踪点的数量,这种开销可能会成为一个更大的问题。 此外,跟踪点通常处于休眠状态(禁用)并且不提供直接的内核功能。 因此,非常希望尽可能减少它们的影响。 尽管跟踪点是这
项工作的最初动机,但其他内核代码路径应该能够利用静态键设施。


2) 解决方案

gcc (v4.5) 添加了一个新的“asm goto”语句,允许分支到标签:http://gcc.gnu.org/ml/gcc-patches/2009-07/msg01556.html

使用“asm goto”,我们可以创建默认情况下采用或不采用的分支,而无需检查内存。 然后,在运行时,我们可以修补分支站点以改变分支方向。

例如,如果我们有一个默认禁用的简单分支:

if (static_key_false(&key))
    printk("I am the true branch\n");

因此,默认情况下不会发出“printk”。 生成的代码将在直线代码路径中包含单个原子“无操作(no-op)”指令(x86 上为 5 个字节)。 当分支被“翻转(flipped)”时,我们将使用“跳转(jump)”指令将直线代码路径
中的“无操作(no-op)”修补到线外真实分支。 因此,改变分支方向是昂贵的,但分支选择基本上是“免费的”。 这是这种优化的基本权衡。

这种低级修补机制称为“跳转标签修补”,它为静态键设施提供了基础。


3) 静态键标签API、用法及示例:

为了利用这种优化,您必须首先定义一个键:

struct static_key key;

初始化为:

struct static_key key = STATIC_KEY_INIT_TRUE;

或者:

struct static_key key = STATIC_KEY_INIT_FALSE;

如果键未初始化,则默认为 false。 'struct static_key'必须是全局变量。 也就是说,它不能在堆栈上分配或在运行时动态分配。

然后在代码中使用键为:

if (static_key_false(&key))
    do unlikely code
else
    do likely code

或者:

if (static_key_true(&key))
    do likely code
else
    do unlikely code

通过“STATIC_KEY_INIT_FALSE”初始化的键必须在“static_key_false()”构造中使用。 同样,通过“STATIC_KEY_INIT_TRUE”初始化的键必须用在“static_key_true()”构造中使用。 一个键可以在许多分支中使用,但所有
分支必须与键的初始化方式相匹配。

然后可以通过以下方式切换分支:

static_key_slow_inc(&key);
...
static_key_slow_dec(&key);

因此,“static_key_slow_inc()”意味着“使分支为真”,并且'static_key_slow_dec()' 表示使用适当的引用计数'使分支为假'。 例如,如果键被初始化为真,static_key_slow_dec() 会将分支切换为假。 随后的 
static_key_slow_inc() 会将分支更改回 true。 同样,如果键被初始化为 false,则“static_key_slow_inc()”会将分支更改为 true。 然后'static_key_slow_dec()',将再次使分支为假。

内核中的示例用法是跟踪点的实现:

static inline void trace_##name(proto)                   \
{                                                        \
    if (static_key_false(&__tracepoint_##name.key))        \
            __DO_TRACE(&__tracepoint_##name,               \
                    TP_PROTO(data_proto),                  \
                    TP_ARGS(data_args),                 \
                    TP_CONDITION(cond));                   \
}

跟踪点默认是禁用的,可以放置在内核的性能关键部分。 因此,通过使用静态键,跟踪点在不使用时可以产生绝对最小的影响。


4) 架构级代码打补丁接口,‘跳转标签(jump labels)’


为了利用这种优化,架构必须实现一些功能和宏。 如果没有架构支持,我们就简单地退回到传统的加载、测试和跳转序列。

* 选择 HAVE_ARCH_JUMP_LABEL,参见:arch/x86/Kconfig

* #define JUMP_LABEL_NOP_SIZE,见:arch/x86/include/asm/jump_label.h

* __always_inline bool arch_static_branch(struct static_key *key),见:arch/x86/include/asm/jump_label.h

* void arch_jump_label_transform(struct jump_entry *entry, enum jump_label_type type),参见:arch/x86/kernel/jump_label.c

* __init_or_module void arch_jump_label_transform_static(struct jump_entry *entry, enum jump_label_type type), 参见:arch/x86/kernel/jump_label.c

* struct jump_entry,参见:arch/x86/include/asm/jump_label.h


5)静态键/跳转标签分析,结果(x86_64):

例如,让我们将以下分支添加到“getppid()”,这样系统调用现在看起来像:

SYSCALL_DEFINE0(getppid)
{
        int pid;

+       if (static_key_false(&key))
+               printk("I am the true branch\n");

        rcu_read_lock();
        pid = task_tgid_vnr(rcu_dereference(current->real_parent));
        rcu_read_unlock();

        return pid;
}

GCC生成的带有跳转标签的指令是:

ffffffff81044290 <sys_getppid>:
ffffffff81044290:       55                      push   %rbp
ffffffff81044291:       48 89 e5                mov    %rsp,%rbp
ffffffff81044294:       e9 00 00 00 00          jmpq   ffffffff81044299 <sys_getppid+0x9>
ffffffff81044299:       65 48 8b 04 25 c0 b6    mov    %gs:0xb6c0,%rax
ffffffff810442a0:       00 00
ffffffff810442a2:       48 8b 80 80 02 00 00    mov    0x280(%rax),%rax
ffffffff810442a9:       48 8b 80 b0 02 00 00    mov    0x2b0(%rax),%rax
ffffffff810442b0:       48 8b b8 e8 02 00 00    mov    0x2e8(%rax),%rdi
ffffffff810442b7:       e8 f4 d9 00 00          callq  ffffffff81051cb0 <pid_vnr>
ffffffff810442bc:       5d                      pop    %rbp
ffffffff810442bd:       48 98                   cltq
ffffffff810442bf:       c3                      retq
ffffffff810442c0:       48 c7 c7 e3 54 98 81    mov    $0xffffffff819854e3,%rdi
ffffffff810442c7:       31 c0                   xor    %eax,%eax
ffffffff810442c9:       e8 71 13 6d 00          callq  ffffffff8171563f <printk>
ffffffff810442ce:       eb c9                   jmp    ffffffff81044299 <sys_getppid+0x9>

如果没有跳转标签优化,它看起来像:

ffffffff810441f0 <sys_getppid>:
ffffffff810441f0:       8b 05 8a 52 d8 00       mov    0xd8528a(%rip),%eax        #
ffffffff81dc9480 <key>
ffffffff810441f6:       55                      push   %rbp
ffffffff810441f7:       48 89 e5                mov    %rsp,%rbp
ffffffff810441fa:       85 c0                   test   %eax,%eax
ffffffff810441fc:       75 27                   jne    ffffffff81044225 <sys_getppid+0x35>
ffffffff810441fe:       65 48 8b 04 25 c0 b6    mov    %gs:0xb6c0,%rax
ffffffff81044205:       00 00
ffffffff81044207:       48 8b 80 80 02 00 00    mov    0x280(%rax),%rax
ffffffff8104420e:       48 8b 80 b0 02 00 00    mov    0x2b0(%rax),%rax
ffffffff81044215:       48 8b b8 e8 02 00 00    mov    0x2e8(%rax),%rdi
ffffffff8104421c:       e8 2f da 00 00          callq  ffffffff81051c50 <pid_vnr>
ffffffff81044221:       5d                      pop    %rbp
ffffffff81044222:       48 98                   cltq
ffffffff81044224:       c3                      retq
ffffffff81044225:       48 c7 c7 13 53 98 81    mov    $0xffffffff81985313,%rdi
ffffffff8104422c:       31 c0                   xor    %eax,%eax
ffffffff8104422e:       e8 60 0f 6d 00          callq  ffffffff81715193 <printk>
ffffffff81044233:       eb c9                   jmp    ffffffff810441fe <sys_getppid+0xe>
ffffffff81044235:       66 66 2e 0f 1f 84 00    data32 nopw %cs:0x0(%rax,%rax,1)
ffffffff8104423c:       00 00 00 00

因此,禁用跳转标签案例添加了“mov”、“test”和“jne”指令,而跳转标签案例只有“无操作(no-op)”或“jmp 0”。 (jmp 0 在启动时被修补为 5 字节的原子无操作指令。)因此,禁用跳转标签的情况添加:

6 (mov) + 2 (test) + 2 (jne) = 10 - 5 (5 byte jump 0) = 5 个加法字节。

如果我们随后包含填充字节,则跳转标签代码将为这个小功能节省总共 16 个字节的指令存储器。在这种情况下,非跳转标签函数的长度为 80 个字节。因此,我们节省了 20% 的指令占用空间。事实上,我们可以
进一步改进这一点,因为 5 字节无操作实际上可以是 2 字节无操作,因为我们可以使用 2 字节 jmp 到达分支。但是,我们还没有实现最佳的无操作大小(它们目前是硬编码的)。

由于调度程序路径中使用了许多静态键 API,因此可以使用“pipe-test”(也称为“perf bench sched pipe”)来显示性能改进。在 3.3.0-rc2 上完成的测试:

跳转标签禁用:

'bash -c /tmp/pipe-test' 的性能计数器统计信息(50 次运行):

855.700314 task-clock # 0.534 CPUs utilized ( +- 0.11% )
200,003 context-switches # 0.234 M/sec ( +- 0.00% )
0 CPU-migrations # 0.000 M/sec ( +- 39.58% )
487 page-faults # 0.001 M/sec ( +- 0.02% )
1,474,374,262 cycles # 1.723 GHz ( +- 0.17% )
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
1,178,049,567 instructions # 0.80 insns per cycle ( +- 0.06% )
208,368,926 branches # 243.507 M/sec ( +- 0.06% )
5,569,188 branch-misses # 2.67% of all branches ( +- 0.54% )

1.601607384 seconds time elapsed ( +- 0.07% )


启用跳转标签:

'bash -c /tmp/pipe-test' 的性能计数器统计信息(50 次运行):

841.043185 task-clock # 0.533 CPUs utilized ( +- 0.12% )
200,004 context-switches # 0.238 M/sec ( +- 0.00% )
0 CPU-migrations # 0.000 M/sec ( +- 40.87% )
487 page-faults # 0.001 M/sec ( +- 0.05% )
1,432,559,428 cycles # 1.703 GHz ( +- 0.18% )
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
1,175,363,994 instructions # 0.82 insns per cycle ( +- 0.04% )
206,859,359 branches # 245.956 M/sec ( +- 0.04% )
4,884,119 branch-misses # 2.36% of all branches ( +- 0.85% )

1.579384366 seconds time elapsed

已保存分支的百分比为 0.7%,我们在“分支未命中”上节省了 12%。 这是我们期望获得最多节省的地方,因为这种优化是关于减少分支数量。 此外,我们还节省了 0.2% 的指令、2.8% 的周期和 1.4% 的运行时间。

 

二、实验

1. 测试demo

#define pr_fmt(fmt) "kfalse_test: " fmt

#include <linux/module.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/jump_label.h>

#if 0
struct static_key false_key = STATIC_KEY_INIT_TRUE;
struct static_key true_key = STATIC_KEY_INIT_FALSE;
#else
struct static_key false_key = STATIC_KEY_INIT_FALSE;
struct static_key true_key = STATIC_KEY_INIT_TRUE;
#endif

static int __init kernel_file_op_init(void)
{
    if (static_key_false(&false_key)) {
        pr_info("=========1==========");
    } else {
        pr_info("=========2==========");
    }
    static_key_slow_inc(&false_key);
    if (static_key_false(&false_key)) {
        pr_info("=========3==========");
    } else {
        pr_info("=========4==========");
    }


    if (static_key_true(&true_key)) {
        pr_info("=========5==========");
    } else {
        pr_info("=========6==========");
    }
    static_key_slow_dec(&true_key);
    if (static_key_true(&true_key)) {
        pr_info("=========7==========");
    } else {
        pr_info("=========8==========");
    }

    return 0;
}

static void __exit kernel_file_op_exit(void)
{
    pr_info("kernel file operation module exit.\n");
}
 
module_init(kernel_file_op_init);
module_exit(kernel_file_op_exit);
 
MODULE_LICENSE("GPL");

/*
这样初始化:
struct static_key false_key = STATIC_KEY_INIT_TRUE;
struct static_key true_key = STATIC_KEY_INIT_FALSE;
执行打印:
kfalse_test: =========2==========
kfalse_test: =========4==========
kfalse_test: =========5==========
kfalse_test: =========7==========
总结:static_key_false()的功能比较正常,static_key_true()的功能不正常!

这样初始化:
struct static_key false_key = STATIC_KEY_INIT_FALSE;
struct static_key true_key = STATIC_KEY_INIT_TRUE;
执行打印:
kfalse_test: =========2==========
kfalse_test: =========3==========
kfalse_test: =========5==========
kfalse_test: =========8==========

相信下面的这个,符合上面 static_key_false 只能初始化为 STATIC_KEY_INIT_FALSE 的规范*/

由实验得出结论,if语句中判别真假时,可理解为 static_key 为真即为真,static_key 为假即为假

结论:

用 static_key_{false/true}(key) 判断真假时,可以忽略这个 _false/_true 下标,判断的值 key 初始化为 STATIC_KEY_INIT_TRUE 判断结果就是 true, 判断的值 key 初始化为 STATIC_KEY_INIT_FALSE 判断结果就是 false。

但是要遵守一个规则: 初始化为 TRUE 的要使用 static_key_true() 进行判断,初始化为 FALSE 的要使用 static_key_false() 进行判断.


三、CONFIG_JUMP_LABEL 配置宏的作用

CONFIG_JUMP_LABEL 是 Linux 内核的一个性能优化配置项,核心思想是把"条件判断"从运行时 branch 变成可动态修改的 NOP/JMP 指令。

1. 基本原理

传统写法(无 jump label):

if (static_key_enabled(&my_key)) {
    do_something();
}

每次执行都要读一个全局变量、做条件跳转,即使"几乎永远不走这条路"也一样有开销。启用 CONFIG_JUMP_LABEL 后,编译器把这个 if 变成:

nop //默认不开启时,这里是一条 NOP(无操作)

当需要开启时,内核在运行时直接修改指令流,把那条 NOP 替换成 JMP(或反过来),之后该路径执行时完全没有条件判断开销。

2. 依赖条件

需要编译器和架构支持 asm goto(GCC 4.5+ / Clang); 支持的架构:x86、ARM64、ARM、MIPS、PowerPC、s390 等主流架构;
若不满足条件,自动退化为普通 if 判断,功能不变,只是没有性能优化.

3. 主要用途

内核里大量使用 static_key / static_branch 系列 API,这些都依赖 jump label

----------------------------------------------------------
使用场景                 举例
----------------------------------------------------------
tracepoint             默认关闭时零开销
perf/ftrace            未激活时不影响热路径
网络协议特性开关          TCP 扩展选项按需激活
调试/统计功能            生产环境关闭后完全无代价
cgroup/namespace 特性   未使用时不走额外判断
----------------------------------------------------------

4. 核心数据结构

//include/linux/jump_label.h
struct static_key {
    atomic_t enabled;
};

//典型用法
DEFINE_STATIC_KEY_FALSE(my_feature_key); //默认关闭

if (static_branch_unlikely(&my_feature_key)) {
    //默认路径生成 NOP,开启后变为 JMP
}

//两个常用变体:

static_branch_likely()   //默认开启,关闭才少见
static_branch_unlikely() //默认关闭,开启才少见

编译器据此做更好的分支预测提示。

5. 开关代价

切换状态(static_branch_enable/static_branch_disable)本身有开销:
(1) 需要暂停相关 CPU(stop_machine 或架构级同步).
(2) 直接写指令流,涉及 icache 同步.
(3) 适合低频切换,不适合高频切换的场景.

6. 与不开启相比

------------------------------------------------------------------------
              CONFIG_JUMP_LABEL=y     CONFIG_JUMP_LABEL=n
------------------------------------------------------------------------
热路径开销     一条 NOP(≈0)             一次内存读 + 条件分支
icache 占用   稍低(NOP vs 分支)         稍高
状态切换代价   高(改指令流)              低(改内存变量)
适用场景      状态稳定、热路径敏感        状态频繁变化或不支持的架构
------------------------------------------------------------------------

7. 一句话总结

CONFIG_JUMP_LABEL 让内核里大量 "默认不走" 或 "默认必走" 的条件分支在未激活时变成一条 NOP,彻底消除热路径上的判断开销,是内核 tracepoint、perf、网络协议扩展等大量子系统保持 "零成本抽象" 的基础机制。

注: 这些 static key 特性要生效,需要配置使能 CONFIG_JUMP_LABEL 才行!此feature适合判断频繁开关不频繁的场景使用。

 

四、补充

1. 使用案例

DECLARE_STATIC_KEY_TRUE(slub_debug_enabled);
DECLARE_STATIC_KEY_FALSE(_debug_pagealloc_enabled);

DECLARE_STATIC_KEY_MAYBE(CONFIG_INIT_ON_ALLOC_DEFAULT_ON, init_on_alloc); //默认开启
static inline bool want_init_on_alloc(gfp_t flags)
{
    if (static_branch_maybe(CONFIG_INIT_ON_ALLOC_DEFAULT_ON, &init_on_alloc)) //运行时判断
        return true;
    return flags & __GFP_ZERO;
}

DECLARE_STATIC_KEY_MAYBE(CONFIG_INIT_ON_FREE_DEFAULT_ON, init_on_free); //默认关闭
static inline bool want_init_on_free(void)
{
  return static_branch_maybe(CONFIG_INIT_ON_FREE_DEFAULT_ON, &init_on_free); //运行时判断
}

(1) DECLARE_STATIC_KEY_MAYBE

是 Linux 内核中用于声明静态键(Static Key)的一个宏,它允许开发者定义一个默认关闭的静态键,并且该静态键的初始状态可以在编译时通过配置选项来决定。 它允许内核开发者根据不同的内核配置(Kconfig)选项,在编译阶段就决定某个静态键的默认状态(开启或关闭),而无需在运行时进行判断。

 

2. 配置选项可以在编译期赋值给变量

static bool __page_table_check_enabled __initdata = IS_ENABLED(CONFIG_PAGE_TABLE_CHECK_ENFORCED);

它是一个编译器常量。


3. Linux-6.1 实现

struct static_key_true {
    struct static_key key;
};

struct static_key_false {
    struct static_key key;
};

#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_TRUE_RO(name)    \
    struct static_key_true name __ro_after_init = STATIC_KEY_TRUE_INIT

#define DECLARE_STATIC_KEY_TRUE(name)    \
    extern struct static_key_true name

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

#define DEFINE_STATIC_KEY_FALSE_RO(name)    \
    struct static_key_false name __ro_after_init = STATIC_KEY_FALSE_INIT

#define DECLARE_STATIC_KEY_FALSE(name)    \
    extern struct static_key_false name

#define DEFINE_STATIC_KEY_ARRAY_TRUE(name, count)        \
    struct static_key_true name[count] = {            \
        [0 ... (count) - 1] = STATIC_KEY_TRUE_INIT,    \
    }

#define DEFINE_STATIC_KEY_ARRAY_FALSE(name, count)        \
    struct static_key_false name[count] = {            \
        [0 ... (count) - 1] = STATIC_KEY_FALSE_INIT,    \
    }

还有一个 maybe:

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

 


4. DEFINE_STATIC_KEY_TRUE(name) 含义

这是 Linux 内核的 static key(静态键) 机制,用于定义一个初始值为 true(启用)的静态分支开关。

DEFINE_STATIC_KEY_TRUE(name);
// 大致等价于:
struct static_key_true name = STATIC_KEY_TRUE_INIT;
// 初始值 = enabled(开启)

//使用时配合 static_branch_likely / static_branch_unlikely:

if (static_branch_likely(&name)) {
    // 默认走这里(初始 true = 启用), 编译器生成的是一条 NOP 指令(无跳转,零开销)
} else {
    // 被禁用后走这里
}

(1) 工作原理(Static Key / Jump Label)

初始状态(TRUE / 启用):if 分支处的机器码 = NOP(空操作,直接 fall through 到 true 分支), 零条件判断开销(不读内存、不做比较、不跳转)

禁用后(调用 static_branch_disable(&name)):内核运行时直接修改代码段:将 NOP 替换为 JMP 指令, if 分支变成无条件跳转到 else 分支。仍然零条件判断开销(只是跳转方向变了)

(2) 实际使用示例

// mm/vmscan.c
DEFINE_STATIC_KEY_ARRAY_TRUE(lru_gen_caps, NR_LRU_GEN_CAPS);

// 使用处:
#define get_cap(cap) static_branch_likely(&lru_gen_caps[cap])

if (get_cap(LRU_GEN_CORE)) {
    // MGLRU 核心功能启用(默认路径,编译为 NOP,零开销)
    ...
}

//通过 /sys/kernel/mm/lru_gen/enabled 写入 0 时:
static_branch_disable(&lru_gen_caps[LRU_GEN_CORE]); //代码段中的 NOP 被热补丁为 JMP,跳过 MGLRU 路径,走普通路径

(3) 与普通变量判断的对比

-------------------------------------------------------------------------------------------------------
普通全局变量       if (flag)                   Static Key + static_branch_likely(&key)
-------------------------------------------------------------------------------------------------------
热路径开销         读内存 + 比较 + 条件跳转       零(NOP 或无条件 JMP)
切换开销           零(改变量值)                高(修改代码段 + IPI 同步所有 CPU)
适用场景           频繁切换                     极少切换、极频繁检查的开关
Cache 影响        读取变量可能 cache miss       无内存访问
-------------------------------------------------------------------------------------------------------

(4) 与 DEFINE_STATIC_KEY_FALSE 的区别

-------------------------------------------------------------------------------------------------------
宏                               初始值     常搭配                            默认走
-------------------------------------------------------------------------------------------------------
DEFINE_STATIC_KEY_TRUE(name)     启用      static_branch_likely(&name)      true 分支(NOP fall through)
DEFINE_STATIC_KEY_FALSE(name)    禁用      static_branch_unlikely(&name)    false 分支(NOP fall through)
-------------------------------------------------------------------------------------------------------

(5) 小结

DEFINE_STATIC_KEY_TRUE(name) = 定义一个默认启用的零开销运行时开关,适用于"99.9% 时间都是某个状态,极偶尔才切换"的场景(如功能总开关、调试开关、硬件特性检测等)。


5. static_key_false(key) 的含义

这是 Static Key 机制的旧版 API(Linux 3.x~4.x 时代),作用是:测试一个初始为 false(禁用)的 static key 当前是否被激活。

(1) 等价关系(旧 API → 新 API)

// 旧 API(已废弃但仍在旧代码中可见):
if (static_key_false(&key))    // 初始为 false,预期大多数时候不走这个分支
// 等价于新 API:
if (static_branch_unlikely(&key))  // 新写法,语义更清晰

(2) 语义解读

static struct static_key my_feature = STATIC_KEY_INIT_FALSE;  //初始关闭

if (static_key_false(&my_feature)) {
    //默认不走这里(编译为 JMP 跳过,或 NOP fall-through 到 else)
    //只有 static_key_slow_inc(&my_feature) 激活后才会走这里
    do_feature_stuff();
}

"false" 的含义: 告诉编译器和 jump label, 这个 key 初始值是 false(禁用的), 大多数时候分支不成立(unlikely),因此默认生成的机器码应该优化"不进入分支"的路径。

(3) 机器码行为

初始状态(key = false / 禁用):代码位置的指令 = NOP(或 JMP 到 else), if 分支不执行,零开销

激活后(static_key_slow_inc(&key) 使 key 变为 true):代码位置的指令被热补丁为 JMP 到 if 体(或 NOP fall-through 到 if 体), if 分支开始执行

(4) 新旧 API 对照表

---------------------------------------------------------------------------------------------
旧 API(deprecated)            新 API(推荐)                       含义
---------------------------------------------------------------------------------------------
static_key_false(&key)        static_branch_unlikely(&key)       测试初始为 false 的 key
static_key_true(&key)         static_branch_likely(&key)         测试初始为 true 的 key
STATIC_KEY_INIT_FALSE         DEFINE_STATIC_KEY_FALSE(name)      定义初始为 false 的 key
STATIC_KEY_INIT_TRUE          DEFINE_STATIC_KEY_TRUE(name)       定义初始为 true 的 key
static_key_slow_inc(&key)     static_branch_enable(&key)         激活(falsetrue)
static_key_slow_dec(&key)     static_branch_disable(&key)        禁用(truefalse)
---------------------------------------------------------------------------------------------

(5) 典型使用场景

// 定义(默认禁用某个功能/调试开关)
static struct static_key paravirt_steal_enabled = STATIC_KEY_INIT_FALSE;
// 热路径中检测
if (static_key_false(&paravirt_steal_enabled)) {
    // 只有在虚拟化环境显式启用 steal time 后才走这里, 非虚拟化环境下此处是 NOP,完全零开销
    account_steal_time();
}
// 初始化时启用
void enable_steal_time(void) {
    static_key_slow_inc(&paravirt_steal_enabled); //热补丁代码段,使上面的 if 分支开始执行
}

(5) 小结

static_key_false(&key) = "测试这个默认为禁用的开关是否被激活了",是旧版 API,新代码应使用 static_branch_unlikely(&key)。核心价值:在"关闭"状态下的热路径中实现零开销(连一次内存读取都没有)。

 

posted on 2021-07-18 14:46  Hello-World3  阅读(326)  评论(0)    收藏  举报

导航