在我使用的某国产ARM64笔记本上(安装的统信UOS系统), Linux内核有2个系统调用表sys_call_table
和compat_sys_call_table
其中sys_call_table
为原生64位程序使用, 而compat_sys_call_table
是为了在ARM64中兼容32位ARM应用程序而实现的
今天我们简单研究下这个兼容的系统调用表compat_sys_call_table
是如何定义和初始化的
compat_sys_call_table
的定义在/arch/arm64/kernel/sys32.c
中:
#undef __SYSCALL
#define __SYSCALL(nr, sym) asmlinkage long __arm64_##sym(const struct pt_regs *);
#include <asm/unistd32.h>
#undef __SYSCALL
#define __SYSCALL(nr, sym) [nr] = __arm64_##sym,
const syscall_fn_t compat_sys_call_table[__NR_compat_syscalls] = {
[0 ... __NR_compat_syscalls - 1] = __arm64_sys_ni_syscall,
#include <asm/unistd32.h>
};
这里的关键是出现了2次的#include <asm/unistd32.h>
, 其中的部分定义如下, 为了方便理解我删除了一些代码
...
#define __NR_write 4
__SYSCALL(__NR_write, sys_write)
#define __NR_open 5
__SYSCALL(__NR_open, compat_sys_open)
...
#define __NR_ftruncate64 194
__SYSCALL(__NR_ftruncate64, compat_sys_aarch32_ftruncate64)
可以看到这里就是简单调用了__SYSCALL
宏, 所以:
- 第1次
#include <asm/unistd32.h>
声明了所有系统调用的函数原型 - 第2次
#include <asm/unistd32.h>
完成了对系统调用表compat_sys_call_table
的初始化, 即把上面声明的函数赋值给系统调用表相应的项
值得注意的是这些系统调用的名字
- 有些和传统的系统调用名字一样, 没有
compat_
的前缀, 说明这些系统调用和ARM64的一样, 可以直接使用ARM64实现好的 - 有些有
compat_
前缀, 这说明这些系统调用需要额外实现, 并且其他已经实现好了, 我们直接使用即可, 例如上面的compat_sys_open
已经在/fs/open.c
中实现了 - 有些有
compat_sys_aarch32_
前缀, 这些是需要我们自己实现的, 例如aarch32_ftruncate64
在/arch/arm64/kernel/sys32.c
中实现
参考资料
https://elixir.bootlin.com/linux/v4.19.274/source/arch/arm64/kernel/sys32.c#L143
https://elixir.bootlin.com/linux/v4.19.274/source/arch/arm64/include/asm/unistd32.h#L21
https://elixir.bootlin.com/linux/v4.19.274/source/arch/arm64/include/asm/syscall_wrapper.h#L18
https://elixir.bootlin.com/linux/v4.19.274/source/fs/open.c#L1129