内核提供的用户助手

这些是内核提供的用户代码片段,可从用户空间通过内核内存中的固定地址进行访问。这用于向用户空间提供某些操作,由于许多 ARM CPU 中未实现原生功能和/或指令,这些操作需要内核的帮助。其设计理念是让这些代码直接在用户模式下执行以获得最佳效率,但它们与内核对应部分的关系又过于紧密,不适合交给用户库。事实上,这些代码甚至可能因 CPU 而异,这取决于可用的指令集,或者它是否为 SMP 系统。换句话说,内核保留根据需要更改此代码且不另行通知的权利。只有本文档中记录的入口点及其结果才能保证是稳定的。

这不同于(但不排除)完整的 VDSO 实现,然而 VDSO 会阻止某些带有常量的汇编技巧,这些技巧原本可以实现向这些代码段的高效跳转。而且由于这些代码段在返回用户代码之前只消耗几个时钟周期,VDSO 间接远调用的开销会给这种极简操作带来可测量的开销。

当针对具有必要原生支持的足够新的处理器进行优化时,期望用户空间绕过这些助手例程,并在内联实现这些功能(可以直接由编译器生成的代码中,或者作为库调用实现的一部分),但前提是生成的二进制文件已经由于在其他地方使用了类似的原生指令而与早期的 ARM 处理器不兼容。换句话说,如果你的编译后的代码不会为了其他目的而使用新指令,切勿仅仅为了不使用这些内核助手而导致二进制文件无法在早期处理器上运行。

随着时间的推移可能会添加新的助手,因此较旧的内核可能会缺少较新内核中存在的某些助手。因此,程序在假定调用任何特定助手是安全的之前,必须检查 __kuser_helper_version 的值(见下文)。理想情况下,此检查应在进程启动时仅执行一次,如果进程运行所在的内核版本未提供所需的助手,则应提前中止执行。

kuser_helper_version

位置:0xffff0ffc

参考声明

extern int32_t __kuser_helper_version;

定义

此字段包含正在运行的内核所实现的助手数量。用户空间可以读取此字段以确定特定助手是否可用。

用法示例

#define __kuser_helper_version (*(int32_t *)0xffff0ffc)

void check_kuser_version(void)
{
      if (__kuser_helper_version < 2) {
              fprintf(stderr, "can't do atomic operations, kernel too old\n");
              abort();
      }
}

注意

用户空间可以假设该字段的值在任何单个进程的生命周期内都不会改变。这意味着该字段可以在库的初始化或程序的启动阶段读取一次。

kuser_get_tls

位置:0xffff0fe0

参考原型

void * __kuser_get_tls(void);

Input

lr = 返回地址

Output

r0 = TLS 值

被修改的寄存器

定义

获取先前通过 __ARM_NR_set_tls 系统调用设置的 TLS 值。

用法示例

typedef void * (__kuser_get_tls_t)(void);
#define __kuser_get_tls (*(__kuser_get_tls_t *)0xffff0fe0)

void foo()
{
      void *tls = __kuser_get_tls();
      printf("TLS = %p\n", tls);
}

注意

  • 仅当 __kuser_helper_version >= 1 时有效(自内核版本 2.6.12 起)。

kuser_cmpxchg

位置:0xffff0fc0

参考原型

int __kuser_cmpxchg(int32_t oldval, int32_t newval, volatile int32_t *ptr);

Input

r0 = oldval r1 = newval r2 = ptr lr = 返回地址

Output

r0 = 成功代码(零或非零) C 标志 = 如果 r0 == 0 则置位,如果 r0 != 0 则清除

被修改的寄存器

r3, ip, flags

定义

仅当 *ptr 等于 oldval 时,原子性地将 newval 存入 *ptr。如果 *ptr 被更改则返回零,如果没有发生交换则返回非零。如果 *ptr 被更改,C 标志也会被置位,以便在调用代码中进行汇编优化。

用法示例

typedef int (__kuser_cmpxchg_t)(int oldval, int newval, volatile int *ptr);
#define __kuser_cmpxchg (*(__kuser_cmpxchg_t *)0xffff0fc0)

int atomic_add(volatile int *ptr, int val)
{
      int old, new;

      do {
              old = *ptr;
              new = old + val;
      } while(__kuser_cmpxchg(old, new, ptr));

      return new;
}

注意

  • 此例程已根据需要包含了内存屏障。

  • 仅当 __kuser_helper_version >= 2 时有效(自内核版本 2.6.12 起)。

kuser_memory_barrier

位置:0xffff0fa0

参考原型

void __kuser_memory_barrier(void);

Input

lr = 返回地址

Output

被修改的寄存器

定义

应用任何所需的内存屏障,以保持与手动修改的数据以及 __kuser_cmpxchg 使用的一致性。

用法示例

typedef void (__kuser_dmb_t)(void);
#define __kuser_dmb (*(__kuser_dmb_t *)0xffff0fa0)

注意

  • 仅当 __kuser_helper_version >= 3 时有效(自内核版本 2.6.15 起)。

kuser_cmpxchg64

位置:0xffff0f60

参考原型

int __kuser_cmpxchg64(const int64_t *oldval,
                      const int64_t *newval,
                      volatile int64_t *ptr);

Input

r0 = 指向 oldval 的指针 r1 = 指向 newval 的指针 r2 = 指向目标值的指针 lr = 返回地址

Output

r0 = 成功代码(零或非零) C 标志 = 如果 r0 == 0 则置位,如果 r0 != 0 则清除

被修改的寄存器

r3, lr, flags

定义

仅当 *ptr 等于 *oldval 指向的 64 位值时,原子性地将 *newval 指向的 64 位值存入 *ptr。如果 *ptr 被更改则返回零,如果没有发生交换则返回非零。

如果 *ptr 被更改,C 标志也会被置位,以便在调用代码中进行汇编优化。

用法示例

typedef int (__kuser_cmpxchg64_t)(const int64_t *oldval,
                                  const int64_t *newval,
                                  volatile int64_t *ptr);
#define __kuser_cmpxchg64 (*(__kuser_cmpxchg64_t *)0xffff0f60)

int64_t atomic_add64(volatile int64_t *ptr, int64_t val)
{
      int64_t old, new;

      do {
              old = *ptr;
              new = old + val;
      } while(__kuser_cmpxchg64(&old, &new, ptr));

      return new;
}

注意

  • 此例程已根据需要包含了内存屏障。

  • 由于此序列的长度,它跨越了 2 个传统的 kuser “槽”,因此 0xffff0f80 不被用作有效的入口点。

  • 仅当 __kuser_helper_version >= 5 时有效(自内核版本 3.1 起)。