内核提供的用户辅助程序

这些是内核提供的用户代码片段,可以从用户空间以内核内存中的固定地址访问。它用于为用户空间提供一些需要内核帮助的操作,因为许多 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);

输入

lr = 返回地址

输出

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);

输入

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

输出

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);

输入

lr = 返回地址

输出

被破坏的寄存器

定义

应用任何需要的内存屏障,以保持与手动修改的数据和 __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);

输入

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

输出

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)。