Static Keys 【ChatGPT】

简介: Static Keys 【ChatGPT】

静态键

警告

  • 已弃用的API:
    直接使用'struct static_key'现在已经被弃用。此外,static_key_{true,false}()也已经被弃用。请勿使用以下内容:
struct static_key false = STATIC_KEY_INIT_FALSE;
struct static_key true = STATIC_KEY_INIT_TRUE;
static_key_true()
static_key_false()
  • 更新后的API替代方案如下:
DEFINE_STATIC_KEY_TRUE(key);
DEFINE_STATIC_KEY_FALSE(key);
DEFINE_STATIC_KEY_ARRAY_TRUE(keys, count);
DEFINE_STATIC_KEY_ARRAY_FALSE(keys, count);
static_branch_likely()
static_branch_unlikely()

摘要

静态键允许在性能敏感的快速路径内核代码中包含很少使用的功能,通过GCC特性和代码修补技术实现。以下是一个快速示例:

DEFINE_STATIC_KEY_FALSE(key);
...
if (static_branch_unlikely(&key))
        执行不太可能的代码
else
        执行可能的代码
...
static_branch_enable(&key);
...
static_branch_disable(&key);
...

static_branch_unlikely()分支将以对可能代码路径的最小影响生成到代码中。

动机

目前,跟踪点是使用条件分支实现的。条件检查需要为每个跟踪点检查一个全局变量。尽管此检查的开销很小,但当内存缓存承受压力时(这些全局变量的内存缓存行可能与其他内存访问共享),开销会增加。随着内核中跟踪点数量的增加,这种开销可能变得更加严重。此外,跟踪点通常处于休眠状态(禁用),并且不提供直接的内核功能。因此,尽量减少它们的影响是非常可取的。虽然跟踪点是这项工作的最初动机,但其他内核代码路径应该能够利用静态键功能。

解决方案

gcc(v4.5)添加了一个新的'asm goto'语句,允许跳转到一个标签:

https://gcc.gnu.org/ml/gcc-patches/2009-07/msg01556.html

使用'asm goto',我们可以创建默认情况下被执行或不被执行的分支,而无需检查内存。然后,在运行时,我们可以修补分支位置以改变分支方向。

例如,如果我们有一个默认情况下被禁用的简单分支:

if (static_branch_unlikely(&key))
        printk("I am the true branch\n");

因此,默认情况下不会发出'printk'。生成的代码将由一个单独的原子'no-op'指令(在x86上为5个字节)组成,位于直线代码路径中。当分支被'翻转'时,我们将在直线代码路径中的'no-op'修补为一个'jump'指令,以跳转到离线的真分支。因此,改变分支方向是昂贵的,但分支选择基本上是'免费'的。这是这种优化的基本权衡。

这种低级修补机制称为'跳转标签修补',它为静态键功能提供了基础。

静态键标签API、用法和示例

为了利用这种优化,您首先必须定义一个键:

DEFINE_STATIC_KEY_TRUE(key);

或者:

DEFINE_STATIC_KEY_FALSE(key);

键必须是全局的,即不能在堆栈上分配或在运行时动态分配。

然后,在代码中使用该键:

if (static_branch_unlikely(&key))
        执行不太可能的代码
else
        执行可能的代码

或者:

if (static_branch_likely(&key))
        执行可能的代码
else
        执行不太可能的代码

通过DEFINE_STATIC_KEY_TRUE()或DEFINE_STATIC_KEY_FALSE定义的键可以在static_branch_likely()或static_branch_unlikely()语句中使用。

可以通过以下方式将分支设置为true:

static_branch_enable(&key);

或通过以下方式将分支设置为false:

static_branch_disable(&key);

然后,可以通过引用计数切换分支:

static_branch_inc(&key);
...
static_branch_dec(&key);

因此,'static_branch_inc()'表示'使分支为true','static_branch_dec()'表示'使分支为false',并进行适当的引用计数。例如,如果键初始化为true,则static_branch_dec()将将分支切换为false。然后,随后的static_branch_inc()将分支改回true。同样,如果键初始化为false,则'static_branch_inc()'将分支改为true。然后,'static_branch_dec()'将再次使分支为false。

可以使用'static_key_enabled()'和'static_key_count()'获取状态和引用计数。通常情况下,如果使用这些函数,应该使用与启用/禁用或增加/减少函数相同的互斥锁进行保护。

请注意,切换分支会导致一些锁被获取,特别是CPU热插拔锁(为了避免内核在修补期间引入CPU时发生竞争)。因此,在热插拔通知器中调用静态键API肯定会导致死锁。为了仍然允许使用该功能,提供了以下函数:

static_key_enable_cpuslocked() static_key_disable_cpuslocked() static_branch_enable_cpuslocked() static_branch_disable_cpuslocked()

这些函数不是通用的,只能在确切知道自己处于上述上下文且没有其他上下文时使用。

如果需要一个键的数组,可以定义为:

DEFINE_STATIC_KEY_ARRAY_TRUE(keys, count);

或者:

DEFINE_STATIC_KEY_ARRAY_FALSE(keys, count);

架构级代码修补接口,'跳转标签'

为了利用这种优化,架构必须实现一些函数和宏。如果没有架构支持,我们将简单地回退到传统的加载、测试和跳转序列。此外,struct jump_entry表的对齐方式必须至少为4字节,因为static_key->entry字段使用了最低的两个有效位。

  • 选择HAVE_ARCH_JUMP_LABEL,
参见:arch/x86/Kconfig
  • define JUMP_LABEL_NOP_SIZE,
参见:arch/x86/include/asm/jump_label.h
  • __always_inline bool arch_static_branch(struct static_key *key, bool branch),
参见:arch/x86/include/asm/jump_label.h
  • __always_inline bool arch_static_branch_jump(struct static_key *key, bool branch),
参见:arch/x86/include/asm/jump_label.h
  • void arch_jump_label_transform(struct jump_entry *entry, enum jump_label_type type),
参见:arch/x86/kernel/jump_label.c
  • struct jump_entry,
参见:arch/x86/include/asm/jump_label.h

静态密钥/跳转标签分析结果(x86_64):

举个例子,让我们给'getppid()'添加以下分支,使得系统调用看起来像这样:

SYSCALL_DEFINE0(getppid)
{
      int pid;
+     if (static_branch_unlikely(&key))
+             printk("我是真分支\n");
      rcu_read_lock();
      pid = task_tgid_vnr(rcu_dereference(current->real_parent));
      rcu_read_unlock();
      return pid;
}

GCC生成的带有跳转标签的结果指令是:

ffffffff81044290 <sys_getppid>:
ffffffff81044290:       55                      push   %rbp
ffffffff81044291:       48 89 e5                mov    %rsp,%rbp
ffffffff81044294:       e9 00 00 00 00          jmpq   ffffffff81044299 <sys_getppid+0x9>
ffffffff81044299:       65 48 8b 04 25 c0 b6    mov    %gs:0xb6c0,%rax
ffffffff810442a0:       00 00
ffffffff810442a2:       48 8b 80 80 02 00 00    mov    0x280(%rax),%rax
ffffffff810442a9:       48 8b 80 b0 02 00 00    mov    0x2b0(%rax),%rax
ffffffff810442b0:       48 8b b8 e8 02 00 00    mov    0x2e8(%rax),%rdi
ffffffff810442b7:       e8 f4 d9 00 00          callq  ffffffff81051cb0 <pid_vnr>
ffffffff810442bc:       5d                      pop    %rbp
ffffffff810442bd:       48 98                   cltq
ffffffff810442bf:       c3                      retq
ffffffff810442c0:       48 c7 c7 e3 54 98 81    mov    $0xffffffff819854e3,%rdi
ffffffff810442c7:       31 c0                   xor    %eax,%eax
ffffffff810442c9:       e8 71 13 6d 00          callq  ffffffff8171563f <printk>
ffffffff810442ce:       eb c9                   jmp    ffffffff81044299 <sys_getppid+0x9>

没有跳转标签优化的情况下,看起来像:

ffffffff810441f0 <sys_getppid>:
ffffffff810441f0:       8b 05 8a 52 d8 00       mov    0xd8528a(%rip),%eax        # ffffffff81dc9480 <key>
ffffffff810441f6:       55                      push   %rbp
ffffffff810441f7:       48 89 e5                mov    %rsp,%rbp
ffffffff810441fa:       85 c0                   test   %eax,%eax
ffffffff810441fc:       75 27                   jne    ffffffff81044225 <sys_getppid+0x35>
ffffffff810441fe:       65 48 8b 04 25 c0 b6    mov    %gs:0xb6c0,%rax
ffffffff81044205:       00 00
ffffffff81044207:       48 8b 80 80 02 00 00    mov    0x280(%rax),%rax
ffffffff8104420e:       48 8b 80 b0 02 00 00    mov    0x2b0(%rax),%rax
ffffffff81044215:       48 8b b8 e8 02 00 00    mov    0x2e8(%rax),%rdi
ffffffff8104421c:       e8 2f da 00 00          callq  ffffffff81051c50 <pid_vnr>
ffffffff81044221:       5d                      pop    %rbp
ffffffff81044222:       48 98                   cltq
ffffffff81044224:       c3                      retq
ffffffff81044225:       48 c7 c7 13 53 98 81    mov    $0xffffffff81985313,%rdi
ffffffff8104422c:       31 c0                   xor    %eax,%eax
ffffffff8104422e:       e8 60 0f 6d 00          callq  ffffffff81715193 <printk>
ffffffff81044233:       eb c9                   jmp    ffffffff810441fe <sys_getppid+0xe>
ffffffff81044235:       66 66 2e 0f 1f 84 00    data32 nopw %cs:0x0(%rax,%rax,1)
ffffffff8104423c:       00 00 00 00

因此,禁用跳转标签的情况下,相比于使用跳转标签的情况,会增加一个'mov'、'test'和'jne'指令,而使用跳转标签的情况下只有一个'no-op'或'jmp 0'指令。('jmp 0'在启动时被修补为一个5字节的原子no-op指令。)因此,禁用跳转标签的情况下增加了:

6(mov)+ 2(test)+ 2(jne)= 10 - 5(5字节跳转0)= 5个额外字节。

如果我们考虑填充字节,跳转标签代码节省了16个指令内存字节。在这种情况下,不使用跳转标签的函数长度为80字节。因此,我们节省了20%的指令占用空间。实际上,我们甚至可以进一步改进,因为5字节的no-op实际上可以是2字节的no-op,因为我们可以用2字节的jmp到达分支。然而,我们还没有实现最佳的no-op大小(它们目前是硬编码的)。

由于调度程序路径中存在许多静态密钥API用法,可以使用'pipe-test'(也称为'perf bench sched pipe')来显示性能改进。在3.3.0-rc2上进行的测试如下:

禁用跳转标签:

执行'bash -c /tmp/pipe-test'的性能计数器统计(50次运行):

Performance counter stats for 'bash -c /tmp/pipe-test' (50 runs):
855.700314 task-clock                #    0.534 CPUs utilized            ( +-  0.11% )
200,003 context-switches          #    0.234 M/sec                    ( +-  0.00% )
0 CPU-migrations            #    0.000 M/sec                    ( +- 39.58% )
487 page-faults               #    0.001 M/sec                    ( +-  0.02% )
1,474,374,262 cycles                    #    1.723 GHz                      ( +-  0.17% )
  <not supported> stalled-cycles-frontend
  <not supported> stalled-cycles-backend
1,178,049,567 instructions              #    0.80  insns per cycle          ( +-  0.06% )
208,368,926 branches                  #  243.507 M/sec                    ( +-  0.06% )
5,569,188 branch-misses             #    2.67% of all branches          ( +-  0.54% )
1.601607384 seconds time elapsed                                          ( +-  0.07% )

启用跳转标签:

执行'bash -c /tmp/pipe-test'的性能计数器统计(50次运行):

Performance counter stats for 'bash -c /tmp/pipe-test' (50 runs):
841.043185 task-clock                #    0.533 CPUs utilized            ( +-  0.12% )
200,004 context-switches          #    0.238 M/sec                    ( +-  0.00% )
0 CPU-migrations            #    0.000 M/sec                    ( +- 40.87% )
487 page-faults               #    0.001 M/sec                    ( +-  0.05% )
1,432,559,428 cycles                    #    1.703 GHz                      ( +-  0.18% )
  <not supported> stalled-cycles-frontend
  <not supported> stalled-cycles-backend
1,175,363,994 instructions              #    0.82  insns per cycle          ( +-  0.04% )
206,859,359 branches                  #  245.956 M/sec                    ( +-  0.04% )
4,884,119 branch-misses             #    2.36% of all branches          ( +-  0.85% )
1.579384366 seconds time elapsed

节省的分支百分比为0.7%,我们在'branch-misses'上节省了12%。这正是我们期望获得最大节省的地方,因为这种优化是为了减少分支的数量。此外,我们在指令上节省了0.2%,在周期上节省了2.8%,在经过时间上节省了1.4%。

相关文章
|
安全 虚拟化
GIC规格学习(一)
GIC规格学习(一)
914 0
|
6月前
|
人工智能 自然语言处理 安全
“养龙虾玩Skill”指南!OpenClaw(Clawdbot)阿里云/本地部署+新手3种 Skill 挖掘AI工具核心价值
OpenClaw(昵称“龙虾”)的爆火,让“养龙虾玩Skill”成了2026年的新潮流——有人用它自动化处理工作,有人靠定制部署服务变现,甚至闲鱼上500元一次的上门部署生意都应运而生。但多数新手入手后,只停留在基础对话层面,殊不知OpenClaw的核心价值藏在Skill生态中——这些可执行的“能力单元”,就像一个个专属机器人,能将自然语言指令转化为实际操作,从文档处理到硬件控制,覆盖全场景需求。
3644 5
|
Kubernetes Linux 网络安全
基于eBPF的微服务网络安全
基于eBPF的微服务网络安全
551 2
|
存储 Linux Android开发
Volatility3内存取证工具安装及入门在Linux下的安装教程
Volatility 是一个完全开源的工具,用于从内存 (RAM) 样本中提取数字工件。支持Windows,Linux,MaC,Android等多类型操作系统系统的内存取证。针对竞赛这块(CTF、技能大赛等)基本上都是用在Misc方向的取证题上面,很多没有听说过或者不会用这款工具的同学在打比赛的时候就很难受。以前很多赛项都是使用vol2.6都可以完成,但是由于操作系统更新,部分系统2.6已经不支持了,如:Win10 等镜像,而Volatility3是支持这些新版本操作系统的。
4330 0
|
缓存 安全 Linux
通过层级内隔离提升软件的安全性|龙蜥大讲堂第112期
本次分享的主题是通过层级内隔离提升软件的安全性,由中科院计算所的武成岗分享。主要分为以下两个部分: 1. 计算系统的安全关乎着整个“数字化”世界的安全 2. 目标:同时获取微内核的安全性和宏内核的高性能 3. 层级内隔离手段 4. 总结
804 12
|
存储 NoSQL Linux
深入理解Linux内核task_struct结构
深入理解Linux内核task_struct结构
|
存储 安全 Linux
调整 core dump 的存储位置或限制
【10月更文挑战第1天】
1951 2
|
存储 NoSQL Linux
linux之core文件如何查看和调试
通过设置和生成 core 文件,可以在程序崩溃时获取详细的调试信息。结合 GDB 等调试工具,可以深入分析 core 文件,找到程序崩溃的具体原因,并进行相应的修复。掌握这些调试技巧,对于提高程序的稳定性和可靠性具有重要意义。
8367 6
|
Linux 开发者
【亮剑】Linux系统中的四种文件锁定机制:flock、fcntl、lockfile和flockfile,用于多进程环境下协调共享资源访问,防止数据损坏和竞争条件
【4月更文挑战第30天】本文介绍了Linux系统中的四种文件锁定机制:flock、fcntl、lockfile和flockfile,用于多进程环境下协调共享资源访问,防止数据损坏和竞争条件。flock适合脚本,fcntl提供底层灵活性,lockfile用于管理锁定文件,flockfile则结合两者功能。选择锁定策略时需考虑应用场景,如脚本可选flock,复杂需求则用fcntl。理解并正确使用这些工具对保证系统稳定性和数据一致性至关重要。
1582 1
|
安全 Linux 网络安全
在Linux中,如何在Linux中实现虚拟化的安全最佳实践?
在Linux中,如何在Linux中实现虚拟化的安全最佳实践?