内核的static-key机制

来源:互联网 发布:行知幼儿园 武侯 编辑:程序博客网 时间:2024/05/07 05:30

内核中有很多判断条件在正常情况下的结果都是固定的,除非极其罕见的场景才会改变,通常单个的这种判断的代价很低可以忽略,但是如果这种判断数量巨大且被频繁执行,那就会带来性能损失了。内核的static-key机制就是为了优化这种场景,其优化的结果是:对于大多数情况,对应的判断被优化为一个NOP指令,在非常有场景的时候就变成jump XXX一类的指令,使得对应的代码段得到执行。

static-key的使用方法

1、 定义一个static-key
struct static_key key = STATIC_KEY_INIT_FALSE;
注意:这个key及其初始值必须是静态存在的,不能定义为局部变量或者使用动态分配的内存。通常为全局变量或者静态变量。其中的STATIC_KEY_INIT_FALSE表示这个key的默认值为false,对应的分支默认不进入,如果是需要默认进入的,用STATIC_KEY_INIT_TRUE,这里如果不赋值,系统默认为STATIC_KEY_INIT_FALSE,在代码运行中不能再用STATIC_KEY_INIT_FALSE/STATIC_KEY_INIT_TRUE进行赋值。
2、 判断语句
对于默认为false(STATIC_KEY_INIT_FALSE)的,使用:
if (static_key_false((&static_key)))
do the unlikely work;
else
do likely work
对于默认为true(STATIC_KEY_INIT_TRUE)的,使用:
if (static_key_true((&static_key)))
do the unlikely work;
else
do likely work
3、 修改判断条件
使用static_key_slow_inc让分支条件变成true,使用static_key_slow_dec让分支条件变成false,与其初始的默认值无关。该接口是带计数的,
也就是:
初始值为STATIC_KEY_INIT_FALSE的,那么:
static_key_slow_inc; static_key_slow_inc; static_key_slow_dec 那么
if (static_key_false((&static_key)))对应的分支会进入,而再次static_key_slow_dec后,该分支就不再进入了。
初始值为STATIC_KEY_INIT_TRUE的,那么:
static_key_slow_dec; static_key_slow_dec; static_key_slow_inc 那么
if (static_key_true((&static_key)))对应的分支不会进入,而再次static_key_slow_inc后,该分支就进入了。

static-key的内核实现

static_key_false

对X86场景其实现如下,其它架构下的实现类似。
static __always_inline bool arch_static_branch(struct static_key *key)
{
asm_volatile_goto(“1:”
STATIC_KEY_INITIAL_NOP
“.pushsection __jump_table, \”aw\” \n\t”
_ASM_ALIGN “\n\t”
_ASM_PTR “1b, %l[l_yes], %c0 \n\t”
“.popsection \n\t”
: : “i” (key) : : l_yes);
return false;
l_yes:
return true;
}
1、 其中的asm_volatile_goto宏 使用了asm goto,是gcc的特性,其允许在嵌入式汇编中jump到一个C语言的label,详见gcc的manual。但是本处其作用只是将C语言的label “l_yes”传递到嵌入式汇编中。
2、 STATIC_KEY_INITIAL_NOP其实就是NOP指令
3、 .pushsection __jump_table是通知编译器,以下的内容写入到段“__jump_table”
4、 _ASM_PTR “1b, %l[l_yes], %c0,是往段“__jump_table”中写入label “1b”、C label “l_yes”和输入参数struct static_key *key的地址,这些信息对应于struct jump_entry 中的code、target、key成员,在后续的处理中非常重要。
5、 .popsection表示以下的内容回到之前的段,其实多半就是.text段。
可见,以上代码的作用就是:执行NOP指令后返回false,同时把NOP指令的地址、代码”return true”对应地址、struct static_key *key的地址写入到段“__jump_table”。由于固定返回为false且为always online,编译器会把
if (static_key_false((&static_key)))
do the unlikely work;
else
do likely work
优化为:
nop
do likely work
retq
l_yes:
do the unlikely work;
正常场景,就没有判断了。

相关初始化

在系统初始化过程中,会通过jump_label_init先把”__jump_table”段中的内容排序,并根据其中记录的struct static_key的地址,把该struct static_key对应的一系列jump_entry的首地址记录到struct static_key:: entries(entries的最低bit位是JUMP_LABEL_TRUE_BRANCH或者JUMP_LABEL_FALSE_BRANCH,其余的bit位才是地址)。对于在modules中使用的static key,模块的加载处理sys_init_module会做类似的事情。

判断条件修改

static_key_slow_dec/ static_key_slow_inc,这两个函数的做法原理类似,其关键在于当计数(key->enabled)达到需要修改代码段中的代码的时候,通过jump_label_update来完成代码的修改。把struct static_key::target指向的位置(就是使用该static_key的arch_static_branch中的那些”1b标号指向的nop指令”),替换为jump指令,从而jump到不常用的段;或者从jump指令改回nop指令。

0 0
原创粉丝点击