Linux Static Key原理与应用

news2024/11/25 4:28:48

文章目录

  • 背景
  • 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_LABEL

struct 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;
else
    do 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, sp
   8:   d503201f        nop
   c:   90000000        adrp    x0, 0 <func>
  10:   91000000        add     x0, x0, #0x0
  14:   94000000        bl      0 <printk>
  18:   a8c17bfd        ldp     x29, x30, [sp], #16
  1c:   d65f03c0        ret
  20:   90000000        adrp    x0, 0 <func>
  24:   91000000        add     x0, x0, #0x0
  28:   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, sp
   8:   340000a0        cbz     w0, 1c <func+0x1c>
   c:   90000000        adrp    x0, 0 <func>
  10:   91000000        add     x0, x0, #0x0
  14:   94000000        bl      0 <printk>
  18:   14000004        b       28 <func+0x28>
  1c:   90000000        adrp    x0, 0 <func>
  20:   91000000        add     x0, x0, #0x0
  24:   94000000        bl      0 <printk>
  28:   a8c17bfd        ldp     x29, x30, [sp], #16
  2c:   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.coloradmin.cn/o/1022631.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

msvcp71.dll丢失的解决方法分享,全面分析msvcp71.dll丢失原因

msvcp71.dll 丢失的问题可能困扰着许多使用 Windows 操作系统的用户。msvcp71.dll 是微软 C运行时库中的一个动态链接库文件&#xff0c;负责提供一些基本的函数和类&#xff0c;例如字符串处理、数学运算、文件操作等。如果这个文件丢失或损坏了&#xff0c;那么在使用依赖于它…

【深度学习 | LSTM】解开LSTM的秘密:门控机制如何控制信息流

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

计算机视觉与深度学习-全连接神经网络-训练过程-批归一化- [北邮鲁鹏]

文章目录 思想批归一化操作批归一化与梯度消失经过BN处理 算法实现 思想 直接对神经元的输出进行批归一化 批归一化&#xff1a;对输出值进行归一化&#xff0c;将归一化结果平移缩放作为输出。 批归一化操作 小批量梯度下降算法回顾&#xff1a;每次迭代时会读入一批数据&am…

工信部将制定虚拟宇宙标准

中国工业和信息化部(MIIT)周一表示&#xff0c;随着北京寻求成为新技术的全球标准制定者&#xff0c;中国将成立一个工作组来制定虚拟宇宙行业的标准。 周一&#xff0c;该部发布了一份提案草案&#xff0c;旨在组建一个虚拟宇宙工作组&#xff0c;该工作组可以通过互联网访问共…

CHATGPT中国免费网页版有哪些-CHATGPT中文版网页

CHATGPT中国免费网页版&#xff0c;一个强大的人工智能聊天机器人。如果你曾经感到困惑、寻求答案&#xff0c;或者需要一些灵感&#xff0c;那么CHATGPT国内网页版可能会成为你的好朋友。 CHATGPT国内免费网页版&#xff1a;你的多面“好朋友” 随着人工智能技术的不断发展&a…

Java学习day04:数组

声明&#xff1a;该专栏本人重新过一遍java知识点时候的笔记汇总&#xff0c;主要是每天的知识点题解&#xff0c;算是让自己巩固复习&#xff0c;也希望能给初学的朋友们一点帮助&#xff0c;大佬们不喜勿喷(抱拳了老铁&#xff01;) Java学习day04&#xff1a;数组 一、开发…

C++:new 和 delete

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》 文章目录 前言一、C内存管理1.内置类型2.自定义类型3.delete 与 new不匹配使用问题(VS平台下) 二、operator new 与 operator delete函数三、 new 和delete的实现原理内置类型自定义类型 四…

【前端知识】Three 学习日志(十)—— 常见几何体(长方体、球体、圆柱、矩形平面、圆形平面)

Three 学习日志&#xff08;十&#xff09;—— 常见几何体&#xff08;长方体、球体、圆柱、矩形平面、圆形平面&#xff09; 一、构建常用几何体 const geometry_list []// BoxGeometry&#xff1a;长方体 const geometry_box new THREE.BoxGeometry(100, 100, 100); geo…

CPU性能优化

在进行CPU性能优化的时候&#xff0c;我们经常先需要分析出来我们的应用程序中的CPU资源在哪些函数中使用的比较多&#xff0c;这样才能高效地优化。一个非常好的分析工具就是《性能之巅》作者 Brendan Gregg 发明的火焰图。 我们今天就来介绍下火焰图的使用方法&#xff0c;以…

GeoServer地图服务器权限控制

目录 1下载相关软件 2部署软件 3配置鉴权环节 4Java工程 5测试鉴权 6测试鉴权结果分析 本文章应该会后面试验一个鉴权功能就会发布一系列测试过程&#xff08;GeoServer有很多鉴权方式&#xff09; 1Download - GeoServer 1下载相关软件 进入geoserver官网的下载页面 …

如何为你的Python程序配置HTTP/HTTPS爬虫IP

在编写Python程序时&#xff0c;有时候我们需要使用HTTP或HTTPS爬虫ip来实现网络请求和访问外部资源。本文将向您介绍如何快速入门&#xff0c;为您的Python程序配置HTTP/HTTPS爬虫ip&#xff0c;以便您能够轻松地处理爬虫ip设置并顺利运行您的程序。 一、了解HTTP/HTTPS爬虫ip…

Python Opencv实践 - ORB特征匹配

参考资料&#xff1a; ORB特征笔记_亦枫Leonlew的博客-CSDN博客 python opencv3 基于ORB的特征检测和 BF暴力匹配 knn匹配 flann匹配 - 知乎 Python OpenCV中的drawMatches()关键点匹配绘制方法详解_cv2.drawmatches_乔卿的博客-CSDN博客 import cv2 as cv import numpy as…

mysql 多个字段 like 同一个值怎么实现

1&#xff0c;需求&#xff1a;前端一个输入框 输入的内容要和数据库中多个字段进行匹配 前端输入内容需要和 username&#xff0c;realname&#xff0c;age&#xff0c;bh 这四个字段匹配 方法1&#xff08;可优化&#xff09;&#xff1b;select * from rzt_user where user…

爱惨了,这个听书神器APP

我喜欢听书的原因&#xff0c;第一个是比较省时间&#xff0c;而且很方便&#xff0c;看小说需要花费时间&#xff0c;看久了&#xff0c;眼睛又很疼。听书的话&#xff0c;刷牙听、走路听、开车听、睡前听等等都可以。 最近狂爱这个爱屁屁&#xff1a;听书神器 1、全网资源&…

nodejs项目实战(带源码)

nodejs项目实战 主要实现功能用户模块文章分类模块文章模块核心代码 数据库完整代码 主要实现功能 本项只适合新手&#xff0c;是一个接口类的项目&#xff0c;主要涉及一些增删改查功能以及三方包的使用&#xff0c;主要包括用node实现写用户登录注册&#xff0c;添加删除文章…

机器学习——聚类算法

0、前言&#xff1a; 机器学习聚类算法主要就是两类&#xff1a;K-means和DBSCAN聚类&#xff1a;一种无监督的学习&#xff0c;事先不知道类别&#xff08;相当于不用给数据提前进行标注&#xff09;&#xff0c;自动将相似的对象归到同一个簇中 1、K-means&#xff1a; 原理…

idea项目配置三大步

场景&#xff1a; 使用 idea 打开一个新项目的时候&#xff0c;想让项目迅速跑起来&#xff0c; 其实只需要下面简单三步&#xff1a; 1. 首先&#xff0c;配maven 2. 其次&#xff0c;配置 jdk 这里配置 project 就行了&#xff0c;不用管Modules中的配置。 3. 最后&#…

德纳 Dana EDI 项目案例

德纳 Dana是一家总部位于美国的公司&#xff0c;专门从事车辆传动和密封解决方案。它设计、制造和销售各种汽车零部件&#xff0c;如轴、传动系统、密封件等。该公司在汽车行业中具有悠久的历史&#xff0c;为各种不同类型的车辆提供关键的机械和工程解决方案。 项目背景与目标…

pythonSDK安装+Visual Studio Code

安装PythonSDK 点击去下载python的SDK&#xff1a;https://www.python.org/ 去下载 双击 下载好的安装包 等待安装可能会很慢… 如何验证是否成功安装了python的SDK Windows电脑 打开 CMD 窗口 如何打开 CMD 窗口 键盘 按 wind R python安装编辑器 Visual Studio Code…

在华为云服务器上CentOS 7安装单机版Redis

https://redis.io/是官网地址。 点击右上角的Download。 可以进入https://redis.io/download/——Redis官网下载最新版的网址。 然后在https://redis.io/download/页面往下拉&#xff0c;点击下图超链接这里。 进入https://download.redis.io/releases/下载自己需要的安装…