内核提供的用户助手¶
这些是内核提供的用户代码段,可以在内核内存中的固定地址从用户空间访问。这用于为用户空间提供一些需要内核帮助的操作,因为许多 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, 标志
定义
仅当 *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, 标志
定义
仅当 *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)时有效。