#linux-kernel #key #static #flags #applications #instructions #jump

nightly no-std static-keys

为 Rust 用户空间应用程序重实现 Linux 内核静态键功能

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 模式

Download history 193/week @ 2024-07-20 165/week @ 2024-07-27 234/week @ 2024-08-03

每月 592 次下载

MIT/Apache

53KB
846

static-keys

Rust CI status Crates.io Version docs.rs

静态键 是 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 {} 检查为 jmpnop

例如,如果用户指定的 flagfalse,汇编指令将被动态修改为以下 nop 指令。

    nop     DWORD PTR [rax+rax*1+0x0]
do_common_routines:
    ; Do common routines
    ret
do_something:
    ; Do something
    jmp     do_common_routines

如果 flagtrue,那么我们将动态修改指令为无条件跳转指令。

    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 的多个线程之间共享,这比仅仅使用 nopjmp 有更大的开销。

用法

要使用这个包,目前需要 nightly Rust。在包根目录顶部,您应声明使用不稳定特性 asm_gotoasm_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 中使用。如果修改了静态键,则使用此静态键的所有位置将相应地修改为 jmpnop

fn run() {
    loop {
        if static_branch_unlikely!(FLAG_STATIC_KEY) {
            do_something();
        }
        do_common_routines();
    }
}

参考

依赖项

~0–36MB
~528K SLoC