linux 内核 static-key机制分析
1、static key是什么
Linux内核的 Static Keys机制是一种高效的条件分支优化技术,主要用于在运行时动态启用或禁用特定代码路径,同时避免常规条件判断(如 if 语句)的性能开销。它通过结合编译时优化和运行时代码修补(如 Jump Label 技术)实现近乎零成本的开关切换,广泛应用于性能敏感的代码逻辑(如调试代码、跟踪点、性能监控等)。
1.1、核心原理
- 无分支执行:Static Keys将条件判断转换为对内存中某个标志位的直接跳转,绕过传统if语句的分支预测和流水线冲刷。
- 代码修补:通过修改内存中的指令(如将 nop 替换为 jmp),在运行时动态启用或禁用代码路径(依赖CPU架构支持,如x86的jmp指令替换)。
- 原子性操作:启用或禁用Static Key是原子操作,无需锁机制,保证线程安全。
1.2、核心数据结构
1.2.1、struct static_key
表示一个静态键的状态,关键字段如下:
struct static_key {atomic_t enabled;/* Jump Label 相关字段(如跳转地址) */};
enabled:键的启用状态(0 禁用,1 启用)。
1.2.2、键的初始状态
DEFINE_STATIC_KEY_TRUE(key): 定义初始为true的键。
DEFINE_STATIC_KEY_FALSE(key): 定义初始为false的键。
1.2.3、使用方式
(1)、定义静态键
#include <linux/jump_label.h>
/*定义并初始化一个静态键(默认禁用)*/
DEFINE_STATIC_KEY_FALSE(my_key);
(2)、在代码中使用静态键
if (static_branch_unlikely(&my_key)) {
/*仅当 my_key 启用时执行此代码块*/
pr_info("Feature is enabled!\n");
}
- 分支预测提示:
static_branch_unlikely():暗示代码块大概率不执行(优化跳转指令位置)。
static_branch_likely():暗示代码块大概率执行。
(3)、动态启用/禁用键
/*启用键*/
static_branch_enable(&my_key);
/*禁用键*/
static_branch_disable(&my_key);
2、数据结构
typedef u32 jump_label_t;
struct jump_entry {jump_label_t code; /*被内核动态修改的nop或跳转指令地址 */jump_label_t target; /*l_yes 标号地址,arch_static_branch()函数中会描述 l_yes 标号*/
/*关联的static_key的地址,static_key的地址4字节对齐,地址值最低2bit位总是0。内核实现利用最低1位用来标识 branch 类型: 0 -> false (static_key_false()构建的 jump_entry )1 -> true (static_key_true()构建的 jump_entry )*/jump_label_t key;
};struct static_key_mod {struct static_key_mod *next;struct jump_entry *entries;struct module *mod;
};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* 因为地址是4字节对齐,所以可以将低2bit作为标记使用*/union {unsigned long type;struct jump_entry *entries;struct static_key_mod *next;};
};/** Two type wrappers around static_key, such that we can use compile time type differentiation to emit the right code.* All the below code is macros in order to play type games.*/
struct static_key_true {struct static_key key;
};struct static_key_false {struct static_key key;
};enum jump_label_type {JUMP_LABEL_NOP = 0,JUMP_LABEL_JMP,
};#define JUMP_TYPE_FALSE 0UL
#define JUMP_TYPE_TRUE 1UL
#define JUMP_TYPE_LINKED 2UL
#define JUMP_TYPE_MASK 3UL下面是static key声明和定义
/** We should be using ATOMIC_INIT() for initializing .enabled, but* the inclusion of atomic.h is problematic for inclusion of jump_label.h* in 'low-level' headers. Thus, we are initializing .enabled with a* raw value, but have added a BUILD_BUG_ON() to catch any issues in* jump_label_init() see: kernel/jump_label.c.*/
/*enable为1,entries为1*/
#define STATIC_KEY_INIT_TRUE \{ .enabled = { 1 }, \{ .entries = (void *)JUMP_TYPE_TRUE } }
/*enable为0,entries为0*/
#define STATIC_KEY_INIT_FALSE \{ .enabled = { 0 }, \{ .entries = (void *)JUMP_TYPE_FALSE } }/*根据类型(true/false)来初始化.key成员变量*/
#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 DEFINE_STATIC_KEY_TRUE(name) \struct static_key_true name = STATIC_KEY_TRUE_INIT#define DECLARE_STATIC_KEY_TRUE(name) \extern struct static_key_true name#define DEFINE_STATIC_KEY_FALSE(name) \struct static_key_false name = STATIC_KEY_FALSE_INIT#define DECLARE_STATIC_KEY_FALSE(name) \extern struct static_key_false name#define DEFINE_STATIC_KEY_ARRAY_TRUE(name, count) \struct static_key_true name[count] = { \[0 ... (count) - 1] = STATIC_KEY_TRUE_INIT, \}#define DEFINE_STATIC_KEY_ARRAY_FALSE(name, count) \struct static_key_false name[count] = { \[0 ... (count) - 1] = STATIC_KEY_FALSE_INIT, \}enum jump_label_type {JUMP_LABEL_NOP = 0,JUMP_LABEL_JMP,
};
3、接口分析
static_branch_likely()
static_branch_unlikely()
static_branch_enable()
static_branch_disable()
static_branch_inc()
static_branch_dec()
3.1、static_branch_likely/static_branch_unlikely()分析
/** Combine the right initial value (type) with the right branch order to generate the desired result.* 注意 likely(...)优化后会将成功放在前面,失败放在后面;unlikely(...)优化后会将失败放在前面,成功放在后面;* type为true表示struct static_key_true,type为false表示struct static_key_false* 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 (动态时取决于enable和branch的值)* static: instruction = type ^ branch (静态时取决于type和branch的值)* 上面这个规则很重要* See jump_label_type() / jump_label_init_type().*/
在Jump Label机制中,type和branch是两个关键参数,用于控制动态分支的行为和优化方向。
branch是一个布尔值(true/false 或 1/0),表示当前分支的默认状态(是否启用),它直接影响生成的指令类型(如nop或jmp),以及static_key的地址偏移计算。
决定指令初始值:
branch=true,初始指令为jmp(分支启用)。
branch=false,初始指令为nop(分支禁用)。
影响static_key的地址偏移:static_key结构体中通过偏移量区分不同的分支状态。
#define static_branch_likely(key) \
arch_static_branch(&(key)->enabled, true)
#define static_branch_unlikely(key) \
arch_static_branch(&(key)->enabled, false)
type是一个枚举值,表示static_key的类型,用于区分不同用途的键,它影响内核对键的初始化和运行时处理。
type和branch协作
初始化阶段
branch决定初始指令:若branch=true,初始指令为 jmp,对应 JUMP_LABEL_JMP。
type 决定处理方式:若type=JUMP_LABEL_TYPE_BRANCH,内核在初始化时会跳过该键,直到显式启用。
运行时修改
启用分支:调用 static_key_enable()将type设为JUMP_LABEL_JMP,并修改指令为jmp。
禁用分支:调用 static_key_disable()将type设为 JUMP_LABEL_NOP,并修改指令为nop。
地址偏移计算
branch 影响 key 的偏移:&((char *)key)[branch] 根据branch的值选择key的地址偏移,确保内核通过static_key的地址快速定位状态。
branch 决定指令的初始状态,type 决定键的生命周期和用途,两者共同实现灵活的动态分支管理。
static key的类型必须为struct static_key_true/struct static_key_false,否则会产生错误。
#define static_branch_likely(x) \
({/*__builtin_types_compatible_p GNU扩展,用来判断两个类型是否相同,sizeof(int) sizeof(char*)结果一样但是类型不同*/ \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(); \branch; \
})
注意,static_branch_likely内部调用的arch_static_branch或者arch_static_branch_jump传递的branch都为true;#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(); \branch; \
})
注意,static_branch_unlikely内部调用的arch_static_branch或者arch_static_branch_jump传递的branch都为false;#define static_key_enabled(x) \
({ \if (!__builtin_types_compatible_p(typeof(*x), struct static_key) && \!__builtin_types_compatible_p(typeof(*x), struct static_key_true) &&\!__builtin_types_compatible_p(typeof(*x), struct static_key_false)) \____wrong_branch_error(); \static_key_count((struct static_key *)x) > 0; \
})int static_key_count(struct static_key *key)
{/** -1 means the first static_key_slow_inc() is in progress.* static_key_enabled() must return true, so return 1 here.*/int n = atomic_read(&key->enabled);return n >= 0 ? n : 1;
}
3.2、arch_static_branch/arch_static_branch_jump 分析
arch_static_branch/arch_static_branch_jump是和cpu体系架构相关的代码,使用asm goto内嵌汇编实现运行时的准确分支功能;
you can reference labels using the actual C label name enclosed in brackets.
For example, to reference a label named carry, you can use ‘%l[carry]’.
The label must still be listed in the GotoLabels section when using this approach.
#define JUMP_LABEL_NOP_SIZE 4
static __always_inline bool arch_static_branch(struct static_key *key, bool branch)
{asm_volatile_goto("1:\n\t"/*运行时被动态修改的指令或修改为nop,或修改为跳转到 l_yes 标号的指令*//*此时为nop空操作指令*/ WASM(nop) "\n\t"/*在 __jump_table的section内,构建 jump_entry 变量:
(struct jump_entry){
.code = 上面标号1的地址,即运行是被动态修改指令的地址;
.target = 标号 l_yes 地址;
.key = key 地址,用最低1位标记branch类型: false(0), true(1),(char *)key将key地址转换为char*可以进行字节偏移&((char *)key)[branch])通过key &~1可以恢复static_key的地址,同时key地址最低bit位为branch的值(likely为true,unlikely为false);因为static key的地址是4字节对齐的,所以可以使用上面的方法,非常巧妙的方法。
}
*/".pushsection __jump_table, \"aw\"\n\t"".word 1b, %l[l_yes], %c0\n\t"".popsection\n\t": : "i" (&((char *)key)[branch]) : : l_yes);return false;
l_yes:return true;
}static __always_inline bool arch_static_branch_jump(struct static_key *key, bool branch)
{asm_volatile_goto("1:\n\t"/*运行时被动态修改的指令或修改为nop,或修改为跳转到 l_yes 标号的指令*//*此时为跳转指令 b l_yes*/WASM(b) " %l[l_yes]\n\t"/*
在__jump_table的section内,构建 jump_entry 变量:
(struct jump_entry){
.code = 上面标号1的地址,即运行是被动态修改指令的地址;
.target = 标号 l_yes 地址;
.key = key 地址,用最低1位标记branch类型: false(0), true(1),(char *)key将key地址转换为char*可以进行字节偏移&((char *)key)[branch])通过key &~1可以恢复static_key的地址,同时key地址最低bit位为branch的值(likely为true,unlikely为false);因为static key的地址是4字节对齐的,所以可以使用上面的方法,非常巧妙的方法。
}
*/".pushsection __jump_table, \"aw\"\n\t" /*切换到__jump_table段*/".word 1b, %l[l_yes], %c0\n\t" /*往段中写入jump_entry 数据*/ ".popsection\n\t": : "i" (&((char *)key)[branch]) : : l_yes); /*切回.text段*/return false;
l_yes:return true;
}
上面代码中的".word 1b, %l[l_yes], %c0\n\t"
.word 定义字空间,会依次将1b,%l[l_yes],%c0 数据放到从当前位置开始的连续地址空间上,即__jump_table段中。
跳转表与代码修补:__jump_table 段记录所有可动态修改的跳转点。
内核运行时(如调用 static_branch_enable())遍历此表,修改代码指令。
在内联汇编中,%c0是GCC的操作数修饰符,用于将输入操作数强制作为立即数(Immediate Value)嵌入到汇编指令中。
上面的Jump Label的代码中用于将static_key的地址偏移直接写入跳转表条目。
语法含义
%c0:
%0:表示第一个输入操作数(索引从 0 开始)。
c修饰符,强制将操作数视为立即数(即直接嵌入指令中的常量值)。
约束条件 "i":在输入操作数约束中,"i"表示操作数必须是整数类型的编译时常量(如 #define 定义的常量或地址偏移)。
注意:
当arch_static_branch/arch_static_branch_jump()函数被编译时,内嵌汇编中1:标签处的内容为nop指令或者b指令在内核的代码段中,内核加载时,其被加载到内存中的某个地址上。
而pushsection和popsection指令在编译时已经将定义的struct jump_entry数据(".word 1b, %l[l_yes], %c0\n\t")存入__jump_table段中,所以在后面要介绍的
jump_label_init/jump_label_update()函数将标签1:(jump_entry->code)地址中的指令修改为nop或者b指令,从而达到运行时动态启用或禁用特定代码路径的功能。
当arch_static_branch/arch_static_branch_jump()被调用时,会根据key的状态,决定是否跳转到l_yes标签。
如果跳转,返回true,否则返回false。和if判断条件相比,不用每次调用都去进行一次条件判断,提高执行效率。
这里的关键在于内联汇编如何与跳转表配合,让内核在运行时通过修改代码(如将nop替换为jmp)来改变执行路径。
static __always_inline bool static_key_false(struct static_key *key)
{return arch_static_branch(key, false);
}static __always_inline bool static_key_true(struct static_key *key)
{return !arch_static_branch(key, true);
}
对于函数对 static_key_*() 前的 __always_inline 修饰符,还得额外说明一下, __always_inline 是告诉编译器,函数代码必须就地展开。
在当前的场景下,不加__always_inline 修饰行不行?答案是不行。因为函数 arch_static_branch() 里面使用了标号 1 和 标号 l_yes 的地址,
为了保证每个 struct jump_entry 记录的标号地址唯一,必须要就地展开函数代码。
3.3、jump_label_init 分析
经过前面的分析可知,所有的struct jump_entry都放在名为__jump_table section内,在链接阶段,内核链接脚本vmlinux.lds片段
...
. = ALIGN(8); __start___jump_table = .; *(__jump_table) __stop___jump_table = .; . = ALIGN(8);
...
将所有的 __jump_table输入section,放置到__start___jump_table到 __stop___jump_table区间内,且保证每个 struct jump_entry 的地址都位于4字节边界;
ALIGN(8) 保证了第一个struct jump_entry的地址位于8字节边界,同时每个struct jump_entry 变量为 4 * 3 = 12 字节。
地址4字节对齐,意味着地址的低2位总是为0,正是利用这一点,struct static_key::type用低位的2位来存储相关struct jump_entry(即static_key::entries指向的struct jump_entry)的类型。
arch/arm/kernel/jump_label.c
start_kernel->jump_label_init
void __init jump_label_init(void)
{struct jump_entry *iter_start = __start___jump_table;struct jump_entry *iter_stop = __stop___jump_table;struct static_key *key = NULL;struct jump_entry *iter;/** Since we are initializing the static_key.enabled field with* with the 'raw' int values (to avoid pulling in atomic.h) in* jump_label.h, let's make sure that is safe. There are only two* cases to check since we initialize to 0 or 1.*/BUILD_BUG_ON((int)ATOMIC_INIT(0) != 0);BUILD_BUG_ON((int)ATOMIC_INIT(1) != 1);if (static_key_initialized)return;cpus_read_lock();jump_label_lock();/*通过jump_entry key值来排序*/jump_label_sort_entries(iter_start, iter_stop);/*遍历所有的jump_entry*/for (iter = iter_start; iter < iter_stop; iter++) {struct static_key *iterk;/* rewrite NOPs */if (jump_label_type(iter) == JUMP_LABEL_NOP) /*nop类型,将jump_entry->code位置替换为nop指令*/arch_jump_label_transform_static(iter, JUMP_LABEL_NOP);/*获取jump_entry对应的static_key*/iterk = jump_entry_key(iter);if (iterk == key)continue;key = iterk;/*设置static_key的entries和type值,将static_key和jump_entry建立联系*/static_key_set_entries(key, iter);}/*static_key初始化完成*/static_key_initialized = true;jump_label_unlock();cpus_read_unlock();
}/*获取jump_entry对应的static_key地址*/
static inline struct static_key *jump_entry_key(struct jump_entry *entry)
{/*在arch_static_branch/arch_static_branch_jump中设置的key值为struct static_key地址偏移一个地址(即(&((char *)key)[branch]),这里获取struct jump_entry 对应的struct static_key结构。*/return (struct static_key *)((unsigned long)entry->key & ~1UL);
}static bool jump_entry_branch(struct jump_entry *entry)
{/*通过地址判断brach为0或为1,key的值设置为(&((char *)key)[branch]。*/return (unsigned long)entry->key & 1UL;
}static enum jump_label_type jump_label_type(struct jump_entry *entry)
{/*获取jump_entry对应的static_key地址*/struct static_key *key = jump_entry_key(entry);/*static_key的enabled是否>0,即是否使能当前static key*/bool enabled = static_key_enabled(key);/*通过地址判断brach为0或为1*/bool branch = jump_entry_branch(entry);/* See the comment in linux/jump_label.h *//*计算出当前放置的是not指令还是jump指令*/return enabled ^ branch;
}
/**** A 'struct static_key' uses a union such that it either points directly* to a table of 'struct jump_entry' or to a linked list of modules which in* turn point to 'struct jump_entry' tables.** The two lower bits of the pointer are used to keep track of which pointer* type is in use and to store the initial branch direction, we use an access* function which preserves these bits.*/
static void static_key_set_entries(struct static_key *key,struct jump_entry *entries)
{unsigned long type;WARN_ON_ONCE((unsigned long)entries & JUMP_TYPE_MASK);/*在对entries赋值之前将key->type值记录,记录其初始值,这里使用了地址对齐的小技巧来存放JUMP_TYPE_FALSE/JUMP_TYPE_TRUE*/type = key->type & JUMP_TYPE_MASK;/*将static_key和jump_entry建立联系*/key->entries = entries;/*将type值写回*/key->type |= type;
}void arch_jump_label_transform_static(struct jump_entry *entry,enum jump_label_type type)
{__arch_jump_label_transform(entry, type, true);
}static void __arch_jump_label_transform(struct jump_entry *entry,enum jump_label_type type,bool is_static)
{void *addr = (void *)entry->code;unsigned int insn;if (type == JUMP_LABEL_JMP) /*产生跳转指令*/insn = arm_gen_branch(entry->code, entry->target);elseinsn = arm_gen_nop(); /*产生nop指令*//*修改addr处的指令为insn*/if (is_static)__patch_text_early(addr, insn);elsepatch_text(addr, insn);
}static inline unsigned long arm_gen_nop(void)
{
#ifdef CONFIG_THUMB2_KERNELreturn 0xf3af8000; /* nop.w */
#elsereturn 0xe1a00000; /* mov r0, r0 */
#endif
}static inline unsigned long arm_gen_branch(unsigned long pc, unsigned long addr)
{return __arm_gen_branch(pc, addr, false);
}static inline unsigned long arm_gen_branch_link(unsigned long pc, unsigned long addr)
{return __arm_gen_branch(pc, addr, true);
}unsigned long __arm_gen_branch(unsigned long pc, unsigned long addr, bool link)
{if (IS_ENABLED(CONFIG_THUMB2_KERNEL))return __arm_gen_branch_thumb2(pc, addr, link);elsereturn __arm_gen_branch_arm(pc, addr, link);
}static unsigned long __arm_gen_branch_arm(unsigned long pc, unsigned long addr, bool link)
{unsigned long opcode = 0xea000000;long offset;if (link) /*link寄存器*/opcode |= 1 << 24;offset = (long)addr - (long)(pc + 8);if (unlikely(offset < -33554432 || offset > 33554428)) {WARN_ON_ONCE(1);return 0;}offset = (offset >> 2) & 0x00ffffff;return opcode | offset;
}/*静态修改内存地址对应的指令*/
static inline void __patch_text_early(void *addr, unsigned int insn)
{__patch_text_real(addr, insn, false);
}/*动态修改内存地址对应的指令*/
static inline void __patch_text(void *addr, unsigned int insn)
{__patch_text_real(addr, insn, true);
}void __kprobes __patch_text_real(void *addr, unsigned int insn, bool remap)
{bool thumb2 = IS_ENABLED(CONFIG_THUMB2_KERNEL);unsigned int uintaddr = (uintptr_t) addr;bool twopage = false;unsigned long flags;void *waddr = addr;int size;if (remap)waddr = patch_map(addr, FIX_TEXT_POKE0, &flags);else__acquire(&patch_lock);if (thumb2 && __opcode_is_thumb16(insn)) {*(u16 *)waddr = __opcode_to_mem_thumb16(insn);size = sizeof(u16);} else if (thumb2 && (uintaddr & 2)) {u16 first = __opcode_thumb32_first(insn);u16 second = __opcode_thumb32_second(insn);u16 *addrh0 = waddr;u16 *addrh1 = waddr + 2;twopage = (uintaddr & ~PAGE_MASK) == PAGE_SIZE - 2;if (twopage && remap)addrh1 = patch_map(addr + 2, FIX_TEXT_POKE1, NULL);*addrh0 = __opcode_to_mem_thumb16(first);*addrh1 = __opcode_to_mem_thumb16(second);if (twopage && addrh1 != addr + 2) {flush_kernel_vmap_range(addrh1, 2);patch_unmap(FIX_TEXT_POKE1, NULL);}size = sizeof(u32);} else {if (thumb2)insn = __opcode_to_mem_thumb32(insn);elseinsn = __opcode_to_mem_arm(insn);*(u32 *)waddr = insn;size = sizeof(u32);}if (waddr != addr) {flush_kernel_vmap_range(waddr, twopage ? size / 2 : size);patch_unmap(FIX_TEXT_POKE0, &flags);} else__release(&patch_lock);/*冲刷指令cache,避免cache不一致*/flush_icache_range((uintptr_t)(addr),(uintptr_t)(addr) + size);
}实时动态更新jump_label_update
/*更新对应的static_key*/
static void jump_label_update(struct static_key *key)
{struct jump_entry *stop = __stop___jump_table;struct jump_entry *entry;
#ifdef CONFIG_MODULESstruct module *mod;if (static_key_linked(key)) {__jump_label_mod_update(key);return;}preempt_disable();mod = __module_address((unsigned long)key);if (mod)stop = mod->jump_entries + mod->num_jump_entries;preempt_enable();
#endif/*static_key对应的jump_entry*/entry = static_key_entries(key);/* if there are no users, entry can be NULL */if (entry)__jump_label_update(key, entry, stop);
}static void __jump_label_update(struct static_key *key,struct jump_entry *entry,struct jump_entry *stop)
{for (; (entry < stop) && (jump_entry_key(entry) == key); entry++) {/** entry->code set to 0 invalidates module init text sections* kernel_text_address() verifies we are not in core kernel* init code, see jump_label_invalidate_module_init().*/if (entry->code && kernel_text_address(entry->code)) /*是否为kernel代码段地址*/arch_jump_label_transform(entry, jump_label_type(entry));}
}
3.4、动态使能/失能static key
/*
* Advanced usage; refcount, branch is enabled when: count != 0
*/
#define static_branch_inc(x) static_key_slow_inc(&(x)->key)
#define static_branch_dec(x) static_key_slow_dec(&(x)->key)
#define static_branch_inc_cpuslocked(x) static_key_slow_inc_cpuslocked(&(x)->key)
#define static_branch_dec_cpuslocked(x) static_key_slow_dec_cpuslocked(&(x)->key)
/*
* Normal usage; boolean enable/disable.
*/
#define static_branch_enable(x) static_key_enable(&(x)->key)
#define static_branch_disable(x) static_key_disable(&(x)->key)
#define static_branch_enable_cpuslocked(x) static_key_enable_cpuslocked(&(x)->key)
#define static_branch_disable_cpuslocked(x) static_key_disable_cpuslocked(&(x)->key)
static_key_enable流程分析如下:
void static_key_enable(struct static_key *key)
{cpus_read_lock();static_key_enable_cpuslocked(key);cpus_read_unlock();
}void static_key_enable_cpuslocked(struct static_key *key)
{STATIC_KEY_CHECK_USE();/*获取key的enable值,>0则warn,理论上其值应为0*/if (atomic_read(&key->enabled) > 0) {WARN_ON_ONCE(atomic_read(&key->enabled) != 1);return;}jump_label_lock();if (atomic_read(&key->enabled) == 0) {/*将enabled设置为-1,update过程中其值为-1*/atomic_set(&key->enabled, -1);/*更新jump_label*/jump_label_update(key);/** See static_key_slow_inc().*//*设置为1*/atomic_set_release(&key->enabled, 1);}jump_label_unlock();
}/*更新对应的static_key*/
static void jump_label_update(struct static_key *key)
{struct jump_entry *stop = __stop___jump_table;struct jump_entry *entry;
#ifdef CONFIG_MODULESstruct module *mod;if (static_key_linked(key)) {__jump_label_mod_update(key);return;}preempt_disable();mod = __module_address((unsigned long)key);if (mod)stop = mod->jump_entries + mod->num_jump_entries;preempt_enable();
#endif/*static_key对应的jump_entry*/entry = static_key_entries(key);/* if there are no users, entry can be NULL */if (entry)__jump_label_update(key, entry, stop);
}static void __jump_label_update(struct static_key *key,struct jump_entry *entry,struct jump_entry *stop)
{for (; (entry < stop) && (jump_entry_key(entry) == key); entry++) {/** entry->code set to 0 invalidates module init text sections* kernel_text_address() verifies we are not in core kernel* init code, see jump_label_invalidate_module_init().*/if (entry->code && kernel_text_address(entry->code)) /*是否为kernel代码段地址*/arch_jump_label_transform(entry, jump_label_type(entry)); /*由于enabled的值发生变化jump_label_type值也会变换,根据type类型决定是nop还是b跳转指令*/}
}static_key_disable实现
void static_key_disable(struct static_key *key)
{cpus_read_lock();static_key_disable_cpuslocked(key);cpus_read_unlock();
}void static_key_disable_cpuslocked(struct static_key *key)
{STATIC_KEY_CHECK_USE();/*获取key的enable值,!=1则warn,理论上其值应为1*/if (atomic_read(&key->enabled) != 1) {WARN_ON_ONCE(atomic_read(&key->enabled) != 0);return;}jump_label_lock();if (atomic_cmpxchg(&key->enabled, 1, 0)) /*enabled值为1时,原子地修改为0,返回旧值1,否则不做任何操作*/jump_label_update(key);jump_label_unlock();
}static_key_slow_inc实现
void static_key_slow_inc(struct static_key *key)
{cpus_read_lock();static_key_slow_inc_cpuslocked(key);cpus_read_unlock();
}void static_key_slow_inc_cpuslocked(struct static_key *key)
{int v, v1;STATIC_KEY_CHECK_USE();/** Careful if we get concurrent static_key_slow_inc() calls;* later calls must wait for the first one to _finish_ the* jump_label_update() process. At the same time, however,* the jump_label_update() call below wants to see* static_key_enabled(&key) for jumps to be updated properly.** So give a special meaning to negative key->enabled: it sends* static_key_slow_inc() down the slow path, and it is non-zero* so it counts as "enabled" in jump_label_update(). Note that* atomic_inc_unless_negative() checks >= 0, so roll our own.*/for (v = atomic_read(&key->enabled); v > 0; v = v1) { /*enabled>0的情况下只做+1处理*/v1 = atomic_cmpxchg(&key->enabled, v, v + 1); /*原子加1,v1记录旧值*/if (likely(v1 == v))return;}jump_label_lock();if (atomic_read(&key->enabled) == 0) { /*enabled由0->1,需要update*/atomic_set(&key->enabled, -1);jump_label_update(key);/** Ensure that if the above cmpxchg loop observes our positive* value, it must also observe all the text changes.*/atomic_set_release(&key->enabled, 1);} else {atomic_inc(&key->enabled);}jump_label_unlock();
}void static_key_slow_dec(struct static_key *key)
{STATIC_KEY_CHECK_USE();__static_key_slow_dec(key, 0, NULL);
}static void __static_key_slow_dec(struct static_key *key,unsigned long rate_limit,struct delayed_work *work)
{cpus_read_lock();__static_key_slow_dec_cpuslocked(key, rate_limit, work);cpus_read_unlock();
}static void __static_key_slow_dec_cpuslocked(struct static_key *key,unsigned long rate_limit,struct delayed_work *work)
{/** The negative count check is valid even when a negative* key->enabled is in use by static_key_slow_inc(); a* __static_key_slow_dec() before the first static_key_slow_inc()* returns is unbalanced, because all other static_key_slow_inc()* instances block while the update is in progress.*//*enabled原子地-1后值为0,持有jump_label_mutex返回true,否则不持有jump_label_mutex,返回false*/if (!atomic_dec_and_mutex_lock(&key->enabled, &jump_label_mutex)) {WARN(atomic_read(&key->enabled) < 0,"jump label: negative count!\n");return;}if (rate_limit) {atomic_inc(&key->enabled);schedule_delayed_work(work, rate_limit);} else {jump_label_update(key);}jump_label_unlock();
}/*** atomic_add_unless - add unless the number is already a given value* @v: pointer of type atomic_t* @a: the amount to add to v...* @u: ...unless v is equal to u.** Atomically adds @a to @v, so long as @v was not already @u.* Returns non-zero if @v was not @u, and zero otherwise.*/
static inline int atomic_add_unless(atomic_t *v, int a, int u)
{return __atomic_add_unless(v, a, u) != u;
}/*** atomic_dec_and_mutex_lock - return holding mutex if we dec to 0* @cnt: the atomic which we are to dec* @lock: the mutex to return holding if we dec to 0** return true and hold lock if we dec to 0, return false otherwise*/
int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock)
{/* dec if we can't possibly hit 0 *//*除非cnt为1,否则cnt值原子地-1,即cnt不能为0*/if (atomic_add_unless(cnt, -1, 1)) /*cnt不为1,则原子地-1,返回非0值*/return 0;/*下面处理cnt为1的情况*//* we might hit 0, so take the lock */mutex_lock(lock);/*cnt原子地-1,测试cnt值,如果cnt为0返回true,否则返回false*/if (!atomic_dec_and_test(cnt)) {/*cnt原子地-1后其值非0,则释放mutex*//* when we actually did the dec, we didn't hit 0 */mutex_unlock(lock);return 0;}/* we hit 0, and we hold the lock */return 1;
}
相关文章:
linux 内核 static-key机制分析
1、static key是什么 Linux内核的 Static Keys机制是一种高效的条件分支优化技术,主要用于在运行时动态启用或禁用特定代码路径,同时避免常规条件判断(如 if 语句)的性能开销。它通过结合编译时优化和运行时代码修补(如 Jump Label 技术)实现近乎零成本的开关切换,广泛应用…...
【Java学习】Knife4j使用流程
手动添加依赖,并刷新Maven <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi2-spring-boot-starter</artifactId><version>4.3.0</version> </dependency>在配置文件application.…...
Java 基本操作快速入门:理解与实践
在软件开发的世界里,Java 作为一种广泛使用的编程语言,已经成为构建企业级应用、移动应用甚至大型系统的主力军。对于任何一位初学者来说,理解 Java 的基本操作是学习编程的第一步。从变量声明到控制流的结构,每一个基础知识点都是…...
jetson orin nano 开发板conda 的 base 环境在 shell 启动时自动激活
使用MobaXterm_Personal_23.0.exe 连接jetson开发板时默认是不进入base环境的 1.输入此命令nano ~/.bashrc看到图1后把conda activate 你的环境名 放到图中标记位置 然后保存退出: Ctrl O 回车保存 Ctrl X 退出编辑器 输入此命令后,source ~/.bas…...
【高中数学/指数/对数】同构六君子之 x/e^x/lnx组合曲线
yx*e^x ye^x/x yx/e^x yx*lnx ylnx/x yx/lnx END...
Golang|在线排查协程泄漏
根据我们的代码,前5毫秒内,每隔1毫秒就会来一个请求,5毫秒之后由于前面的协程执行完,后面又会来新的协程,所以协程数目会保持稳定但是代码一运行,协程数量一直增长,发生了协程泄漏 我们可以list…...
健康养生指南
在快节奏的现代生活中,健康养生愈发重要,它是我们享受美好生活的基石。 饮食是养生的关键一环。秉持均衡原则,每日保证谷类、蔬果、优质蛋白等各类食物合理摄入。多吃富含膳食纤维的粗粮,像燕麦、糙米,可促进肠道蠕…...
实验二 两个多位十进制数相加实验
一、实验目的 1.掌握汇编子程序的编写方法。 2.掌握循环程序的设计方法。 二、实验内容 将键盘输入的两个5位十进制数相加,在屏幕上显示相加的结果。 三、实验要求 1.显示格式:被加数加数相加的结…...
多模态大模型MLLM基础训练范式 Pre-train + Instruction FineTuning
多模态大模型Pre-train 为了在图文嵌入空间中更好地对齐视觉和文本信息。为此,使用图像-文本对(image-caption style data),表示为 ( X , Y a ) (\mathbf{X}, Y_a) (X,Ya),其中: X \mathbf{X} X&#x…...
2025.4.15六年之约day11
六年之约已经断更好几个月了,当初六年之约是当做日记来写的,然后被同事刷到了,被谈及的时候挺尴尬的,毕竟里面记录的是我的所思所想。在互联网下,是不适合发布日记的,但我又爱记录所思所想所做。 不知道距…...
Rust学习之实现命令行小工具minigrep(二)
Rust学习之实现命令行小工具minigrep(二) Rust学习之实现命令行小工具minigrep(一) 前言 继续记录一下Rust 语言学习过程,上次写了一个命令行查找字符串的小项目minigrep。学习完了闭包(Closures&#x…...
Access Token 和 Refresh Token 的双令牌机制,维持登陆状态
目录 1. 双令牌机制2. 工作流程3. 客户端实现4. 服务器端实现5. 注意事项拓展:Token在客户端安全存储的几种方式 为了实现客户端在 JWT Token 过期后自动更新 Token,通常会采用 Access Token 和 Refresh Token 的双令牌机制。以下是实现自动更新 Token 的…...
前端 -- uni-app 的 splitChunks 分包详解与实战!
全文目录: 开篇语📝 前言📖 目录🌟 什么是 splitChunks?🛠 splitChunks 的核心原理📂 文件拆分的机制⚙️ 配置选项✨ splitChunks 实战案例1️⃣ 项目初始化2️⃣ 编写页面逻辑3️⃣ 配置 splitChunks4️⃣ 查看效果🧩 splitChunks 的高级用法与优化🔍 优化一…...
【教程】检查RDMA网卡状态和测试带宽 | 附测试脚本
转载请注明出处:小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你,欢迎[点赞、收藏、关注]哦~ 目录 检查硬件和驱动状态 测试RDMA通信 报错修复 对于交换机的配置,可以看这篇: 【教程】详解配置多台主机通过交换机实现互…...
OSPF的拓展配置
OSPF的拓展配置 1,ospf的手工认证 1,接口认证 r1: display ospf peer brief (查看邻居关系) int g 0/0/0 ospf authentication-mode md5 1 cipher 123456 display this r2: ospf authentication-mode md5 1 plain 12345…...
http、https、TLS、证书原理理解,对称加密到非对称加密问题,以及对应的大致流程
http 超文本传输协议 存在问题: 安全性、隐私性、数据完整性 易被中间人(黑客之类的)对数据进行劫持、篡改、隐私泄露 引出了 https (source) http 在网络模型中的应用层 Application > transport > inter…...
vscode格式化为什么失效?自动保存和格式化(Prettier - Code formatter,vue-format)
vscode自动格式化保存最终配置 博主找了好多的插件,也跟着教程配置了很多,结果还是没有办法格式化,最终发现了一个隐藏的小齿轮,配置完后就生效了 关键步骤 关键配置 一定要点小齿轮!!! 这个小…...
两类中断控制器处理流程_链式和层级
今天呢,我们来用一种新的视角去看中断子系统,然后仿照人家的方法去写一个虚拟的中断子系统,我们先来讲讲链式和层级: 链式中断控制器(chained): 上图中,左边的"chained intc"就是链式中断控制器…...
软件测试之接口测试用例设计
1.接口测试用例设计简介 我们对系统的需求分析完成之后,即可设计对应的接口测试用例,然后用接口测试用例进行接口测试。接口测试用例的设计也需要用到黑盒测试方法,其与功能测试用例设计的方法类似,接口测试用例设计中还需要增加…...
猫咪如厕检测与分类识别系统系列【九】视频检测区域在线绘制+支持摄像头+网络摄像头+整体构建【上】
前情提要 家里养了三只猫咪,其中一只布偶猫经常出入厕所。但因为平时忙于学业,没法时刻关注牠的行为。我知道猫咪的如厕频率和时长与健康状况密切相关,频繁如厕可能是泌尿问题,停留过久也可能是便秘或不适。为了更科学地了解牠的如…...
MySQL-运维篇
日志主从复制分库分表读写分离 日志 在任何一种数据库当中都会有各种各样的日志,这些日志记录着数据库运行的各个方面 错误日志 这个命令可以查看文件尾部的50行日志👆 这个命令是实时输出👆 二进制日志 第三个是索引文件,里面…...
mysql关联查询语句
假设存在以下三张表: orders 表:记录订单信息,包含 order_id(订单编号)、customer_id(客户编号)、order_date(订单日期)等字段。customers 表:记录客户信息&…...
Uniapp权限申请优化方案
获取权限前给用户提示,并在用户拒绝后48小时内不再弹窗请求授权。 优化方案分析 您的代码已经实现了基本的权限申请逻辑,但可以进一步优化以满足应用商店的审核要求。 1. 权限申请前的用户提示优化 当前代码中已经包含了权限申请前的提示功能&#x…...
案例驱动的 IT 团队管理:创新与突破之路:第四章 危机应对:从风险预见到创新破局-4.2 人才流失危机-4.2.3梯队建设的“洋葱模型“
👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 梯队建设的"洋葱模型":破解IT团队人才流失危机的创新实践1. 人才流失危机的现实挑战1.1 行业现状与数据警示1.2 传统应对策略的失效 2. 洋葱模型的理论…...
双目视觉中矩阵等参数说明及矫正
以下是标定文件中各个参数的详细解释: 1. 图像尺寸 (imageSize) 参数值: [1280, 1024]含义: 相机的图像分辨率,宽度为1280像素,高度为1024像素。 2. 相机内参矩阵 (leftCameraMatrix / rightCameraMatrix) 结构: yaml data: [fx, 0, cx, 0,…...
烽火ai场控接入deepseek自动回复话术软件
要将烽火AI场控软件与DeepSeek自动回复话术软件进行对接,实现直播间自动互动功能,需通过API接口或脚本工具完成数据互通。以下是具体操作步骤及注意事项: 确认兼容性与准备工作 软件支持检查 确认烽火AI场控是否开放API接口(一般需…...
CSS 美化页面(三)
一、盒模型 盒模型本质上是一个盒子,封装周围的HTML元素 。包含: 外边距,边框,填充,和实际内容 一个盒子由四个区域组成:内容(Content)、内边距(Padding)、外…...
面试题之数据库-mysql高阶及业务场景设计
最近开始面试了,410面试了一家公司 针对自己薄弱的面试题库,深入了解下,也应付下面试。在这里先祝愿大家在现有公司好好沉淀,定位好自己的目标,在自己的领域上发光发热,在自己想要的领域上(技术…...
STM32F407实现SD卡的读写功能
文章目录 前言一、SDIO简介二、SD卡操作1.读操作2.写数据3.擦除操作4.最终效果5.完整工程 前言 在STM32中存储空间是有限的,对于需要存储大量数据的项目就需要外扩存储空间,一般会选择FLASH、EEPROM或者SD卡。SD是这三种中可达空间最大的,所…...
Vue 3中的setup【与Vue 2的区别】
一、前言 在Vue 3中,setup是组合式API(Composition API)的核心入口函数。其核心作用是为组件提供灵活的逻辑组织方式,解决复杂组件中逻辑碎片化的问题。 二、核心作用 1.初始化响应式数据 通过ref和reactive等API声明响应式状态…...
基于PySide6的YOLOv8/11目标检测GUI界面——智能安全帽检测系统
📖 前言 在工业安全领域,智能安全帽检测是保障工人生命安全的重要技术手段。本文将介绍如何利用YOLOv8/YOLOv11目标检测算法与PySide6 GUI框架,开发一套功能完整的智能安全帽检测系统。系统支持: 动态切换检测模型(Y…...
AF3 generate_chain_data_cache脚本解读
AlphaFold3 generate_chain_data_cache 脚本在源代码的scripts文件夹下。该脚本从指定目录中批量解析 mmCIF/PDB 文件的工具,并将每个链的基本信息(序列、分辨率、是否属于聚类等)提取并写入 JSON 文件,主要用于后续蛋白质建模、过滤或训练数据准备。 源代码: import ar…...
C/C++不透明指针
今天在ESP32编程中又看到了这个词,这个词出现在cursor回答中。回答如下: struct esp_netif_obj; typedef struct esp_netif_obj esp_netif_t;esp_netif_obj的具体实现细节被隐藏了用户代码只能通过esp_netif_t类型指针来操作网络接口这种封装方式被称为…...
电力实习中需要注意哪些安全用电问题
电力实习中需要注意哪些安全用电问题 在电工实习中,由于涉及到电力设备和电气设施,安全问题尤为重要。 以下是电工实习中需要注意的安全问题: 一、电气设备及线路安全 使用电气设备前,应确保设备具有良好的电气绝缘,…...
【版本控制】git命令使用大全
大家好,我是jstart千语。今天来总结一下git的使用命令,上文会先将git命令都列出来,便于快速寻找,然后还会对部分常用命令图文讲解,适合新手,让你快速地理解。最后还会总结在idea中使用git。如果有缺失的&am…...
Day09【基于Tripletloss实现的简单意图识别对话系统】
基于Tripletloss实现的表示型文本匹配 目标数据准备参数配置数据处理Triplet Loss目标Triplet Loss计算公式公式说明 模型构建网络结构设计网络训练目标损失函数设计 主程序推理预测类初始化加载问答知识库文本向量化知识库查询主程序main测试测试效果 参考博客 目标 在此之前…...
什么是HIGG验厂,HIGG验厂有什么要求?HIGG验厂有什么作用
什么是Higg验厂? Higg验厂(Higg Facility Environmental Module, FEM & Higg Facility Social & Labor Module, FSLM)是由可持续服装联盟(SAC, Sustainable Apparel Coalition)开发的一套评估工具,…...
SmolVLM新模型技术解读笔记
原文地址:https://huggingface.co/blog/zh/smolervlm 一、核心发布概要 新成员亮相:推出256M(2.56亿参数)与500M(5亿参数)视觉语言模型关键定位:目前全球最小VLM(256M)…...
解决USG5150防火墙web无法连接问题
参考 防火墙usg5500(V300R001C00SPC700)WEB界面无法登陆 现象 Web防火墙突然无法web登录,Ping通,但是Tcpping端口不通。无论是从外网、还是内网都一样。 Probing 192.168.100.1:1234/tcp - No response - time2047.528ms Prob…...
Resilience4j与Spring Cloud Gateway整合指南:构建弹性的API网关
什么是Resilience4j? Resilience4j是一个轻量级的容错库,专为Java 8和函数式编程设计。它借鉴了Netflix Hystrix的设计理念,但更加轻量且专注于Java 8的函数式编程风格。Resilience4j提供了多种容错机制,帮助开发者构建弹性强健的…...
Quipus,LightRag的Go版本的实现
1 项目简介 奇谱系统当前版本以知识库为核心,基于知识库可以快构建自己的问答系统。知识库的Rag模块的构建算法是参考了LightRag的算法流程的Go版本优化实现,它可以帮助你快速、准确地构建自己的知识库,搭建属于自己的AI智能助手。与当前LLM…...
怎样完成本地模型知识库检索问答RAG
怎样完成本地模型知识库检索问答RAG 目录 怎样完成本地模型知识库检索问答RAG使用密集检索器和系数检索器混合方式完成知识库相似检索1. 导入必要的库2. 加载文档3. 文本分割4. 初始化嵌入模型5. 创建向量数据库6. 初始化大语言模型7. 构建问答链8. 提出问题并检索相关文档9. 合…...
研发效率破局之道阅读总结(2)流程优化
研发效率破局之道阅读总结(2)流程优化 Author: Once Day Date: 2025年4月15日 一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦… 漫漫长路,有人对你微笑过嘛… 全系列文章可参考专栏: 程序的艺术_Once-Day…...
解决PIP 安装出错ERROR: cp310-cp310-manylinux_2_28_x86_64.whl is not a supported wheel
ERROR: torch-2.8.0.dev20250325cu128-cp310-cp310-manylinux_2_28_x86_64.whl is not a supported wheel on this platform. 可以 pip debug --verbose | grep manylinux | grep cp310 WARNING: This command is only meant for debugging. Do not use this with automation f…...
b站golang后端开发一面
go和其他语言的对比 Golang(也称为Go语言)是一种静态类型、编译型语言,由Google开发,以其简洁、高效和强大的并发处理能力著称。 Golang的设计哲学强调简洁明了。与Python类似,Go语法简洁,易于学习和编写。…...
Vue3 SSR Serverless架构革命:弹性计算与量子加速
一、全维度Serverless SSR架构 1.1 蜂巢式弹性调度系统 1.2 冷启动时间优化表 优化策略Node.js冷启(ms)Deno冷启(ms)Bun冷启(ms)裸启动1800960420预编译二进制650380210内存快照预热22016090WASM实例池15011075量子状态预载453832 二、边缘渲染协议升级 2.1 流式SSR响应协议…...
深度大脑:AI大模型的设计与运行原理
AI大模型的设计与运行原理涉及多个复杂环节,以下是系统化的总结,结合核心要点与补充细节: 一、AI大模型的设计 1. 深度神经网络架构 Transformer:取代RNN/CNN,解决长程依赖问题。核心组件: 自注意力机制…...
Python网络编程从入门到精通:Socket核心技术+TCP/UDP实战详解
引言 网络编程是构建现代分布式系统的核心能力,而Socket作为通信的基石,其重要性不言而喻。本文将从零开始,通过清晰的代码示例、原理剖析和对比分析,带你彻底掌握Python中的Socket编程技术,涵盖TCP可靠连接、UDP高效…...
使用CMake生成Opencv对应库文件
opencv环境配置:版本3.4/3.2(OpenCV-3.4.3) CMake:3.12.1 D:\OpenCv\opencv\build\x64\vc16\bin路径添加至环境变量中 CMake环境配置: D:\Install_QT\bin路径添加至环境变量中(path中即可) QT5环境变量配置:…...
MySQL 数据库备份和恢复全指南
MySQL 是一款常用的开源数据库系统,在日常运维中,数据备份和恢复是系统管理的重要一环。本文将细致介绍 MySQL 两大备份方案—— mysqldump 和 XtraBackup,包括备份方式、恢复步骤、定时脚本、远程备份和常见问题处理方案。 一、mysqldump 备…...