個(gè)門戶網(wǎng)站站長(zhǎng)之家ip查詢工具
文章目錄
- 背景
- 1. static-key的使用方法
- 1.1. static-key定義
- 1.2 初始化
- 1.3 條件判斷
- 1.4 修改判斷條件
- 2、示例代碼
- 參考鏈接
背景
內(nèi)核中有很多判斷條件在正常情況下的結(jié)果都是固定的,除非極其罕見的場(chǎng)景才會(huì)改變,通常單個(gè)的這種判斷的代價(jià)很低可以忽略,但是如果這種判斷數(shù)量巨大且被頻繁執(zhí)行,那就會(huì)帶來性能損失了。內(nèi)核的static-key機(jī)制就是為了優(yōu)化這種場(chǎng)景,其優(yōu)化的結(jié)果是:對(duì)于大多數(shù)情況,對(duì)應(yīng)的判斷被優(yōu)化為一個(gè)NOP指令,在非常有場(chǎng)景的時(shí)候就變成jump XXX一類的指令,使得對(duì)應(yīng)的代碼段得到執(zhí)行。
1. static-key的使用方法
1.1. static-key定義
static_key 結(jié)構(gòu)體的定義如下:
#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的主要區(qū)別就是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_unlikely
和 static_branch_likely
只是填充指令的方式不同(可以參考上面的代碼注釋), 當(dāng)static_key
為false時(shí),都會(huì)進(jìn)入else邏輯語句中。
if (static_branch_unlikely((&static_key)))do likely work;
elsedo unlikely work
1.4 修改判斷條件
使用static_branch_enable
和 static_branch_disable
可以改變static_key 狀態(tài)
#define static_branch_enable(x) static_key_enable(&(x)->key)
#define static_branch_disable(x) static_key_disable(&(x)->key)
底層是調(diào)用static_key_slow_dec
, static_key_slow_dec
來改變key->enabled計(jì)數(shù)。
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對(duì)程序分支跳轉(zhuǎn)硬編碼的影響。
#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時(shí),匯編代碼如下:
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
對(duì)比可以發(fā)現(xiàn),在0x8地址處,使用static-key編碼在編譯時(shí)將cbz指令替換為了nop指令,減少了程序運(yùn)行時(shí)對(duì)比次數(shù)。
參考鏈接
- Linux內(nèi)核中的static-key機(jī)制
- Linux內(nèi)核jump label與static key的原理與示例
- static-keys.html | 靜態(tài)鍵
- Linux Jump Label/static-key機(jī)制詳解