Linux Jump Label/static-key机制详解
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:: entries
(entries
的最低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机制详解相关推荐
- Linux下进程间通信的六种机制详解
linux下进程间通信的几种主要手段: 1.管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具 ...
- linux内核rcu,linux内核rcu机制详解
RCU(Read-Copy Update)是数据同步的一种方式,在当前的Linux内核中发挥着重要的作用.RCU主要针对的数据对象是链表,目的是提高遍历读取数据的效率,为了达到目的使用RCU机制读取数 ...
- php 反射原理,PHP反射机制详解
本文主要和大家分享PHP反射机制详解,内容包括1.自动生成文档2.实现 MVC 架构3.实现单元测试4.配合 DI 容器解决依赖,希望能帮助到大家. 1.自动生成文档 根据反射的分析类,接口,函数和方 ...
- php的 静态变量,PHP之static静态变量详解
在看别人项目过程中,看到函数里面很多static修饰的变量,关于static修饰的变量,作用域,用法越看越困惑,所以查了下资料. static用法如下: 1.static 放在函数内部修饰变量 2.s ...
- android系统(63)---Jobscheduler运行机制详解
android之Jobscheduler运行机制详解 如果想在将来达到一定条件下执行某项任务时,可以在一个实现了JobService的子类的onStartJob方法中执行这项任务,使用JobInfo的 ...
- 【java】SPI机制详解
1.概述 以前的文章:[SPI]java基础之SPI框架实现 转载:Java常用机制 - SPI机制详解 PI(Service Provider Interface),是JDK内置的一种 服务提供发现 ...
- 反射 数据类型_Java基础:反射机制详解
一.什么是反射: (1)Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法.本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取t对 ...
- Android JNI使用方法,JNI机制详解
Android JNI使用方法,JNI机制详解 JNI的出现使得开发者既可以利用Java语言跨平台.类库丰 富.开发便捷等特点,又可以利用Native语言的高效. JNI是JVM实现中的一部分,因此N ...
- Linux 内核中RAID5源码详解之守护进程raid5d
Linux 内核中RAID5源码详解之守护进程raid5d 对于一个人,大脑支配着他的一举一动:对于一支部队,指挥中心控制着它的所有活动:同样,对于内核中的RAID5,也需要一个像大脑一样的东西来支配 ...
最新文章
- 关于npm邮箱验证问题
- seaborn pairplot ax_强者致胜 AX电竞叛客RTX3070显卡发布_
- java中 银行存款取款_java银行存款取款
- OSChina 周六乱弹 ——用大脑直接写代码
- codevs 1422 河城荷取
- 人工智能数学基础之概率论
- NoteBurner Spotify教程:在Mac上将 Spotify 音乐转换为 MP3 格式
- 163 Blog试用有感
- Go语言编程从入门到精通,数据类型
- 给新生的软件网站工具推荐
- AdGuard Home 使用设置以及DNS测速软件
- 制药企业计算机管理软件,制药企业erp管理系统
- 【数据分析】销售案例分析——分解目标
- Ubuntu使用Flux调节色温保护眼睛
- 《Celeste》 开发者是如何精心制作“冲刺”的
- swarm-XDai主网免bzz质押和rpc全套搭建教程-windows
- Linux网络驱动架构
- 分析ctr模型效果的一些思路总结
- 如何恢复 Linux 上删除的文件:ext2
- vue中input禁止输入中文_input禁止键盘及中文输入,但可以点击
热门文章
- 设计模式是什么鬼(状态)
- springboot使用JdbcTemplate完成对数据库的增删改查
- Java多线程学习二十五:阻塞和非阻塞队列的并发安全原理||如何选择适合自己的阻塞队列?
- 国防科技大学计算机非军籍研究生就业情况,国防科技大学无军籍,将来就业前景怎样?涨知识了...
- MySQL高级-MySQL锁
- SQL Server2005探索之---正确使用索引
- Windows 10 设置 Java 环境变量
- HeroKu PaaS模式
- ES2005 js =
- Android(java)学习笔记133:Eclipse中的控制台不停报错Can't bind to local 8700 for debugger...