Linux static_key原理与应用

文章目录

  • 背景
  • 1. static-key的使用方法
    • 1.1. static-key定义
    • 1.2 初始化
    • 1.3 条件判断
    • 1.4 修改判断条件
  • 2、示例代码
    • 参考链接

背景

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

1. static-key的使用方法

1.1. static-key定义

static_key 结构体的定义如下:

#ifdef CONFIG_JUMP_LABELstruct static_key {atomic_t enabled;
/** Note:*   To make anonymous unions work with old compilers, the static*   initialization of them requires brackets. This creates a dependency*   on the order of the struct with the initializers. If any fields*   are added, STATIC_KEY_INIT_TRUE and STATIC_KEY_INIT_FALSE may need*   to be modified.** bit 0 => 1 if key is initially true*	    0 if initially false* bit 1 => 1 if points to struct static_key_mod*	    0 if points to struct jump_entry*/union {unsigned long type;struct jump_entry *entries;struct static_key_mod *next;};
};#else
struct static_key {atomic_t enabled;
};
#endif	/* CONFIG_JUMP_LABEL */

如果没有定义CONFIG_JUMP_LABEL,则static_key 退化成atomic变量。

1.2 初始化

#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 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 STATIC_KEY_INIT_TRUE                    \{ .enabled = { 1 },                    \.entries = (void *)JUMP_TYPE_TRUE }
#define STATIC_KEY_INIT_FALSE                    \{ .enabled = { 0 },                    \.entries = (void *)JUMP_TYPE_FALSE }

false和true的主要区别就是enabled 是否为1.

1.3 条件判断

#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;								\if (__builtin_types_compatible_p(typeof(*x), struct static_key_true))	\branch = !arch_static_branch(&(x)->key, true);			\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(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(branch);							\
})#else /* !CONFIG_JUMP_LABEL */#define static_branch_likely(x)		likely(static_key_enabled(&(x)->key))
#define static_branch_unlikely(x)	unlikely(static_key_enabled(&(x)->key))#endif /* CONFIG_JUMP_LABEL */

可见同样依赖HAVE_JUMP_LABEL。如果没有定义的话,直接退化成likely和unlikely
static_branch_unlikelystatic_branch_likely 只是填充指令的方式不同(可以参考上面的代码注释), 当static_key为false时,都会进入else逻辑语句中。

if (static_branch_unlikely((&static_key)))do likely work;
elsedo unlikely work

1.4 修改判断条件

使用static_branch_enablestatic_branch_disable可以改变static_key 状态

#define static_branch_enable(x)        static_key_enable(&(x)->key)
#define static_branch_disable(x)    static_key_disable(&(x)->key)

底层是调用static_key_slow_dec, static_key_slow_dec来改变key->enabled计数。

static inline void static_key_enable(struct static_key *key)
{int count = static_key_count(key);WARN_ON_ONCE(count < 0 || count > 1);if (!count)static_key_slow_inc(key);
}
static inline void static_key_disable(struct static_key *key)
{int count = static_key_count(key);WARN_ON_ONCE(count < 0 || count > 1);if (count)static_key_slow_dec(key);
}
static inline void static_key_slow_inc(struct static_key *key)
{STATIC_KEY_CHECK_USE(key);atomic_inc(&key->enabled);
}static inline void static_key_slow_dec(struct static_key *key)
{STATIC_KEY_CHECK_USE(key);atomic_dec(&key->enabled);
}

2、示例代码

下面我们用一段代码来分析static-key对程序分支跳转硬编码的影响。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/static_key.h>DEFINE_STATIC_KEY_FALSE(key);void func(int a){if (static_branch_unlikely(&key)) {  printk("my_module: Feature is enabled\n");} else {printk("my_module: Feature is disabled\n");}
}static int __init my_module_init(void) {pr_info("my_module: Module loaded\n");int a = 1;func(a);static_branch_enable(&key);func(a);return 0;
}static void __exit my_module_exit(void) {pr_info("my_module: Module unloaded\n");
}module_init(my_module_init);
module_exit(my_module_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample Kernel Module with Static Key");

func汇编代码如下:

0000000000000000 <func>:0:   a9bf7bfd        stp     x29, x30, [sp, #-16]!4:   910003fd        mov     x29, sp8:   d503201f        nopc:   90000000        adrp    x0, 0 <func>10:   91000000        add     x0, x0, #0x014:   94000000        bl      0 <printk>18:   a8c17bfd        ldp     x29, x30, [sp], #161c:   d65f03c0        ret20:   90000000        adrp    x0, 0 <func>24:   91000000        add     x0, x0, #0x028:   94000000        bl      0 <printk>2c:   17fffffb        b       18 <func+0x18>

func中不适用static-key时,汇编代码如下:

void func(int a){if (a) {  printk("my_module: Feature is enabled\n");} else {printk("my_module: Feature is disabled\n");}
}0000000000000000 <func>:0:   a9bf7bfd        stp     x29, x30, [sp, #-16]!4:   910003fd        mov     x29, sp8:   340000a0        cbz     w0, 1c <func+0x1c>c:   90000000        adrp    x0, 0 <func>10:   91000000        add     x0, x0, #0x014:   94000000        bl      0 <printk>18:   14000004        b       28 <func+0x28>1c:   90000000        adrp    x0, 0 <func>20:   91000000        add     x0, x0, #0x024:   94000000        bl      0 <printk>28:   a8c17bfd        ldp     x29, x30, [sp], #162c:   d65f03c0        ret

对比可以发现,在0x8地址处,使用static-key编码在编译时将cbz指令替换为了nop指令,减少了程序运行时对比次数。

参考链接

  1. Linux内核中的static-key机制
  2. Linux内核jump label与static key的原理与示例
  3. static-keys.html | 静态键
  4. Linux Jump Label/static-key机制详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/112442.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ipad录屏在哪?学会这些方法,轻松找到录屏功能

随着科技的不断发展&#xff0c;ipad已经成为人们日常生活中不可或缺的工具之一&#xff0c;它不仅能够帮助我们处理各种任务&#xff0c;还可以用来记录和分享有趣的瞬间。在本篇文章中&#xff0c;我们将详细介绍ipad录屏在哪里&#xff0c;为您提供更多录制和编辑视频的可能…

【LeetCode热题100】--15.三数之和

15.三数之和 注意&#xff1a;最后答案中不能包含重复的三元组 使用排序双指针 可以使用三重循环枚举三元组&#xff0c;但是需要哈希表进行去重操作&#xff0c;得到不包含重复三元组的最终答案&#xff0c;消耗量大量的时间和空间 对于不重复的本质&#xff0c;保持三重循环…

sudo+vim+g++/gcc+makefile+进度条

目录 一、信任表中加入指定的普通用户&#xff08;使其能使用sudo&#xff09; 二、vim的使用 &#xff08;一&#xff09;基本概念 1. 正常/普通/命令模式(Normal mode) 2. 插入模式(Insert mode) 3. 末行模式(last line mode) &#xff08;二&#xff09;vim正常模式…

怎么防止360安全卫士修改默认浏览器?

默认的浏览器 原先选项是360极速浏览器&#xff08;如果有安装的话&#xff09;&#xff0c;我这里改成了Chrome。 先解锁 才能修改。

[SQL Server]在应使用条件的上下文(在 ‘)‘ 附近)中指定了非布尔类型的表达式,查询时间大于某个数值时

这种条件查询条件里面不要有空格&#xff0c;一个也不要有 这种条件查询条件里面不要有空格&#xff0c;一个也不要有 $giftsDb::table(drawgot)->where(disabtime,<,"2030-01-03")->select();

【WFA】【Enhanced open】CT_OWE_DHgroup_STA_NoAssociation-AllGroupsRejected_10338_1

测试报告如下: Fail的关键log: 当连接到ap失败时,驱动程序将尝试连接到ap。如果ap仅支持Group 20,并且sta支持Group 19、20。sta将首先尝试Group 19,ap将通过状态代码77拒绝它。然后驱动程序将尝试连接Group 19的ap,仍然达到最大重试次数。那么sta将尝试第Group 20 。 …

RT-Thread I/O设备模型框架

I/O 设备模型框架 RT-Thread提供了一套简单的I/O设备模型框架&#xff0c;如图所示&#xff0c;它位于硬件和应用程序之间&#xff0c;共分成三层&#xff0c;从上到下分别是I/O设备管理层、设备驱动框架层、设备驱动层。 应用程序通过I/O设备管理接口获得正确的设备驱动&…

git工具下载和安装

(1)从git官网下载安装包 然后安装 https://git-scm.com/downloads (2)git 学习参考官方的资料 https://git-scm.com/book/en/v2

从前序与中序遍历序列构造二叉树

给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,9,20,null,null,15,7] …

混淆矩阵和数据不平衡 (1/3)

一、说明 如果数据集数据不平恒&#xff0c;如何评估分类器的效果&#xff1f;如果分类器不好&#xff0c;如何改进分类器&#xff1f;本篇将讲述不平衡数据下&#xff0c;混淆矩阵的应用。 二、混淆矩阵的基本概念 2.1 连续数据分布 LET将数据视为连续的&#xff0c;分类的或有…

http概念

概念&#xff1a;HTTP&#xff0c;hyper text transfer protocol&#xff0c;超文本传输协议&#xff0c;规定了浏览器和服务器之间数据传输的规则。 特点&#xff1a; 1.基于TCP协议&#xff1a;面向连接&#xff0c;安全。 2.基于请求-响应模型的&#xff1a;一次请求对应一…

哈希及哈希表的实现

目录 一、哈希的引入 二、概念 三、哈希冲突 四、哈希函数 常见的哈希函数 1、直接定址法 2、除留余数法 五、哈希冲突的解决 1、闭散列 2、开散列 一、哈希的引入 顺序结构以及平衡树中&#xff0c;元素关键码与其存储位置之间没有对应的关系&#xff0c;因此在查找…