Linux Jump Label/static-key机制详解

RToax 2021年3

关于Linux Jump Label(x86)已经进行过概述,下面就static-key进行详述。

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

1. static-key的使用方法

1.1. static-key定义

结构如下:

struct static_key {atomic_t enabled;struct jump_entry *entries;
};
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进行赋值。二者的定义分别为:

#define JUMP_LABEL_TYPE_FALSE_BRANCH 0UL
#define JUMP_LABEL_TYPE_TRUE_BRANCH 1UL
#define JUMP_LABEL_TYPE_MASK        1UL#define STATIC_KEY_INIT_TRUE         \{ .enabled = ATOMIC_INIT(1),              \.entries = (void *)JUMP_LABEL_TYPE_TRUE_BRANCH }
#define STATIC_KEY_INIT_FALSE       \{ .enabled = ATOMIC_INIT(0),              \.entries = (void *)JUMP_LABEL_TYPE_FALSE_BRANCH }

1.2. 判断语句

对于默认为false(STATIC_KEY_INIT_FALSE)的,使用:

if (static_key_false((&static_key)))do the unlikely work;
elsedo likely work

对于默认为true(STATIC_KEY_INIT_TRUE)的,使用:

if (static_key_true((&static_key)))do the unlikely work;
elsedo likely work
struct static_key key = STATIC_KEY_INIT_FALSE;int main() {jump_label_init();if (static_key_false(&key))printf("1.false\n");elseprintf("1.true\n");static_key_slow_inc(&key);if (static_key_false(&key))printf("2.false\n");elseprintf("2.true\n");
}

程序输出为:

1.true
2.false

1.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后,该分支就进入了。

2. static-key的内核实现

对X86场景其实现如下,其它架构下的实现类似。

static bool __always_inline arch_static_branch(struct static_key *key)
{asm_volatile_goto ("1:"".byte " stringify(STATIC_KEY_INIT_NOP) "\n\t"".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;
}
  • 其中的asm_volatile_goto宏 使用了asm goto,是gcc的特性,其允许在嵌入式汇编中jump到一个C语言的label,详见gcc的manual。但是本处其作用只是将C语言的label “l_yes”传递到嵌入式汇编中。
#define asm_volatile_goto(x) do { __asm__ goto (x); __asm__ (""); } while (0)
  • STATIC_KEY_INITIAL_NOP其实就是NOP指令
#define P6_NOP1  GENERIC_NOP1
#define P6_NOP2 0x66,0x90
#define P6_NOP3 0x0f,0x1f,0x00
#define P6_NOP4 0x0f,0x1f,0x40,0
#define P6_NOP5 0x0f,0x1f,0x44,0x00,0
#define P6_NOP6 0x66,0x0f,0x1f,0x44,0x00,0
#define P6_NOP7 0x0f,0x1f,0x80,0,0,0,0
#define P6_NOP8 0x0f,0x1f,0x84,0x00,0,0,0,0
#define P6_NOP5_ATOMIC P6_NOP5#define STATIC_KEY_INIT_NOP P6_NOP5_ATOMIC
  • .pushsection __jump_table是通知编译器,以下的内容写入到段“__jump_table
  • _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成员,在后续的处理中非常重要。
//_ASM_PTR经过一系列的展开后为:
#ifndef __LP64__
#define _ASM_PTR " .long "
#else
#define _ASM_PTR " .quad "
#endif
  • .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;
elsedo likely work

优化为:

    nopdo likely workretq
l_yes:do the unlikely work;

正常场景,就没有判断了。

2.1. 相关初始化

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

2.2. 判断条件修改

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指令。

3. 链接

Linux Jump Label(x86)
Jump Label
jump label v3
Jump label reworked
jump label: introduce static_branch() interface
Special sections in Linux binaries
https://www.kernel.org/doc/Documentation/static-keys.txt
Linux tracing - kprobe, uprobe and tracepoint
perf Static Tracepoints
The Linux Kernel Tracepoint API
Using the Linux Kernel Tracepoints
内核的static-key机制

Linux Jump Label/static-key机制详解相关推荐

  1. Linux下进程间通信的六种机制详解

    linux下进程间通信的几种主要手段:        1.管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具 ...

  2. linux内核rcu,linux内核rcu机制详解

    RCU(Read-Copy Update)是数据同步的一种方式,在当前的Linux内核中发挥着重要的作用.RCU主要针对的数据对象是链表,目的是提高遍历读取数据的效率,为了达到目的使用RCU机制读取数 ...

  3. php 反射原理,PHP反射机制详解

    本文主要和大家分享PHP反射机制详解,内容包括1.自动生成文档2.实现 MVC 架构3.实现单元测试4.配合 DI 容器解决依赖,希望能帮助到大家. 1.自动生成文档 根据反射的分析类,接口,函数和方 ...

  4. php的 静态变量,PHP之static静态变量详解

    在看别人项目过程中,看到函数里面很多static修饰的变量,关于static修饰的变量,作用域,用法越看越困惑,所以查了下资料. static用法如下: 1.static 放在函数内部修饰变量 2.s ...

  5. android系统(63)---Jobscheduler运行机制详解

    android之Jobscheduler运行机制详解 如果想在将来达到一定条件下执行某项任务时,可以在一个实现了JobService的子类的onStartJob方法中执行这项任务,使用JobInfo的 ...

  6. 【java】SPI机制详解

    1.概述 以前的文章:[SPI]java基础之SPI框架实现 转载:Java常用机制 - SPI机制详解 PI(Service Provider Interface),是JDK内置的一种 服务提供发现 ...

  7. 反射 数据类型_Java基础:反射机制详解

    一.什么是反射: (1)Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法.本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取t对 ...

  8. Android JNI使用方法,JNI机制详解

    Android JNI使用方法,JNI机制详解 JNI的出现使得开发者既可以利用Java语言跨平台.类库丰 富.开发便捷等特点,又可以利用Native语言的高效. JNI是JVM实现中的一部分,因此N ...

  9. Linux 内核中RAID5源码详解之守护进程raid5d

    Linux 内核中RAID5源码详解之守护进程raid5d 对于一个人,大脑支配着他的一举一动:对于一支部队,指挥中心控制着它的所有活动:同样,对于内核中的RAID5,也需要一个像大脑一样的东西来支配 ...

最新文章

  1. 关于npm邮箱验证问题
  2. seaborn pairplot ax_强者致胜 AX电竞叛客RTX3070显卡发布_
  3. java中 银行存款取款_java银行存款取款
  4. OSChina 周六乱弹 ——用大脑直接写代码
  5. codevs 1422 河城荷取
  6. 人工智能数学基础之概率论
  7. NoteBurner Spotify教程:在Mac上将 Spotify 音乐转换为 MP3 格式
  8. 163 Blog试用有感
  9. Go语言编程从入门到精通,数据类型
  10. 给新生的软件网站工具推荐
  11. AdGuard Home 使用设置以及DNS测速软件
  12. 制药企业计算机管理软件,制药企业erp管理系统
  13. 【数据分析】销售案例分析——分解目标
  14. Ubuntu使用Flux调节色温保护眼睛
  15. 《Celeste》 开发者是如何精心制作“冲刺”的
  16. swarm-XDai主网免bzz质押和rpc全套搭建教程-windows
  17. Linux网络驱动架构
  18. 分析ctr模型效果的一些思路总结
  19. 如何恢复 Linux 上删除的文件:ext2
  20. vue中input禁止输入中文_input禁止键盘及中文输入,但可以点击

热门文章

  1. 设计模式是什么鬼(状态)
  2. springboot使用JdbcTemplate完成对数据库的增删改查
  3. Java多线程学习二十五:阻塞和非阻塞队列的并发安全原理||如何选择适合自己的阻塞队列?
  4. 国防科技大学计算机非军籍研究生就业情况,国防科技大学无军籍,将来就业前景怎样?涨知识了...
  5. MySQL高级-MySQL锁
  6. SQL Server2005探索之---正确使用索引
  7. Windows 10 设置 Java 环境变量
  8. HeroKu PaaS模式
  9. ES2005 js =
  10. Android(java)学习笔记133:Eclipse中的控制台不停报错Can't bind to local 8700 for debugger...