6 个版本 (重大更改)
新 0.5.0 | 2024 年 8 月 10 日 |
---|---|
0.4.0 | 2024 年 8 月 7 日 |
0.3.1 | 2024 年 8 月 5 日 |
0.3.0 | 2024 年 7 月 27 日 |
0.1.0 | 2024 年 7 月 21 日 |
#316 in Rust 模式
每月 592 次下载
53KB
846 行
static-keys
静态键 是 Linux 内核用于加快检查不常更改功能的机制。我们将其带到了 Linux、macOS 和 Windows 的 Rust 用户空间应用程序中!(目前需要 nightly Rust。原因见 常见问题解答)。
当前 CI 测试的平台
-
Linux
x86_64-unknown-linux-gnu
x86_64-unknown-linux-musl
i686-unknown-linux-gnu
aarch64-unknown-linux-gnu
riscv64gc-unknown-linux-gnu
loongarch64-unknown-linux-gnu
-
macOS
aarch64-apple-darwin
-
Windows
x86_64-pc-windows-msvc
i686-pc-windows-msvc
注意:当使用 cross-rs 构建目标 loongarch64-unknown-linux-gnu
时,应使用 GitHub 上可用的最新 cross-rs。有关更多详细信息,请参阅 Evian-Zhang/static-keys#4。
有关更全面的解释和常见问题解答,您可以参考 GitHub Pages (中文版文档)。
动机
现代应用程序通常通过 CLI 选项或配置文件进行配置。由配置标志控制这些值通常在应用程序初始化后不会更改,并且在整个应用程序生命周期中频繁访问。
let flag = CommandlineArgs::parse();
loop {
if flag {
do_something();
}
do_common_routines();
}
尽管 flag
在应用程序初始化后不会修改,但 if
-检查仍然在每个轮次中发生,在 x86-64 中,它可能被编译为以下 test
-jnz
指令。
test eax, eax ; Check whether eax register is 0
jnz do_something ; If not zero, jump to do_something
do_common_routines:
; Do common routines
ret
do_something:
; Do something
jmp do_common_routines ; Jump to do_common_routines
尽管 if-check
只不过是由 test
-jnz
指令组成,但它仍然可以进行优化。将检查操作改为 jmp
(跳过 do_something
分支)或者 nop
(始终 do_something
)如何?这正是静态键所做的事情。简单来说,我们在运行时修改指令。在获取命令行传递的 flag
后,我们根据 flag
的值动态修改 if flag {}
检查为 jmp
或 nop
。
例如,如果用户指定的 flag
是 false
,汇编指令将被动态修改为以下 nop
指令。
nop DWORD PTR [rax+rax*1+0x0]
do_common_routines:
; Do common routines
ret
do_something:
; Do something
jmp do_common_routines
如果 flag
是 true
,那么我们将动态修改指令为无条件跳转指令。
jmp do_something
do_common_routines:
; Do common routines
ret
do_something:
; Do something
jmp do_common_routines
不再有 test
和条件跳转,只有 nop
(这意味着这条指令什么都不做)或 jmp
。
虽然将 test
-jnz
对替换为 nop
可能只是微小的改进,但是,如 Linux 内核文档中所述,如果配置检查涉及全局变量,这种替换可以降低内存缓存压力。在服务器应用程序中,这种配置可能在 Arc
的多个线程之间共享,这比仅仅使用 nop
或 jmp
有更大的开销。
用法
要使用这个包,目前需要 nightly Rust。在包根目录顶部,您应声明使用不稳定特性 asm_goto
和 asm_const
。
#![feature(asm_goto)]
#![feature(asm_const)]
首先,将此包添加到您的 Cargo.toml
。
[dependencies]
static-keys = "0.5"
在 main
函数的开始处,您应该调用 static_keys::global_init
以进行初始化。
fn main() {
static_keys::global_init();
// Do other things...
}
然后,您应该定义一个静态键来保存受用户控制标志影响的价值,并根据用户传递的标志启用或禁用它。
// FLAG_STATIC_KEY is defined with initial value `false`
define_static_key_false!(FLAG_STATIC_KEY);
fn application_initialize() {
let flag = CommandlineArgs::parse();
if flag {
unsafe {
FLAG_STATIC_KEY.enable();
}
}
}
请注意,您可以在任何时候启用或禁用静态键任意次数。更重要的是,如果在多线程环境中修改静态键,这是非常危险的。始终在修改此类静态键之后启动线程。为了更清楚地说明,以下是在多线程环境中使用此静态键是绝对安全的。静态键的修改可能效率较低,但由于静态键用于很少改变的功能,因此修改很少发生,所以效率低下并不是问题。请参阅 常见问题解答 了解更多解释。
定义之后,您可以在 if-check
中像往常一样使用此静态键(您可以在 此处 和 此处 了解有关 likely
-unlikely
API 语法的更多信息)。静态键可以在多个 if-check
中使用。如果修改了静态键,则使用此静态键的所有位置将相应地修改为 jmp
或 nop
。
fn run() {
loop {
if static_branch_unlikely!(FLAG_STATIC_KEY) {
do_something();
}
do_common_routines();
}
}
参考
- Linux 内核官方文档: static-keys
- Linux
static_key
内部机制 - Rust for Linux 同样实现了静态键,请参阅 Rust-for-Linux/linux#1084 获取更多信息。我的基于
break
的内联汇编布局灵感来源于这项伟大工作。
依赖项
~0–36MB
~528K SLoC