既上一篇介绍了ARM64内核系统调用详解之后,本篇文章来介绍如何添加自己的系统调用到内核中。
根据上一篇我们知道如下关键的几点:
(1)ARM64的系统调用分为32-bit模式和64-bit模式。
(2)32-bit模式的系统调用syscall定义在头文件arch/arm64/include/asm/unistd32.h
(3)64-bit模式的系统调用syscall定义在头文件include/uapi/asm-generic/unistd.h
(4)默认系统为定义的系统调用号会直接执行一个no implement handler,也就是do_ni_syscall
下面我们就来介绍两种添加自定义系统调用的方法。
第一种方式:
我们以系统调用getpid为例进行讲解。
(1)实现sys_getpid函数体
kernel/sys.c:
/**
* sys_getpid - return the thread group id of the current process
*
* Note, despite the name, this returns the tgid not the pid. The tgid and
* the pid are identical unless CLONE_THREAD was specified on clone() in
* which case the tgid is the same in all threads of the same group.
*
* This is SMP safe as current->tgid does not change.
*/
SYSCALL_DEFINE0(getpid)
{
return task_tgid_vnr(current);
}
SYSCALL_DEFINE0的定义如下:
#define SYSCALL_DEFINE0(sname) \
SYSCALL_METADATA(_##sname, 0); \
asmlinkage long sys_##sname(void)
(2)头文件中声明系统调用函数
include/linux/syscalls.h:
asmlinkage long sys_getpid(void);
(3)把系统调用函数加入到syscall数组中
64-bit模式系统调用数组添加:
include/uapi/asm-generic/unistd.h:
#define __NR_getpid 172
__SYSCALL(__NR_getpid, sys_getpid)
32-bit模式系统调用数组添加:
arch/arm64/include/asm/unistd32.h:
#define __NR_getpid 20
__SYSCALL(__NR_getpid, sys_getpid)
至此我们就完成了一个系统调用的添加了。
第二种方式:
通过前面的介绍我们知道Linux中没有意义的系统调用号都会调用到arch/arm64/kernel/trap.c:do_ni_syscall函数。
那么我们可以考虑在此函数中实现我们新添加的系统调用,使用这种方式添加系统调用就不用再去更改系统调用数组了。
arch/arm64/kernel/trap.c:
asmlinkage long do_ni_syscall(struct pt_regs *regs)
{
#ifdef CONFIG_COMPAT
long ret;
if (is_compat_task()) {
ret = compat_arm_syscall(regs);
if (ret != -ENOSYS)
return ret;
}
#endif
if (show_unhandled_signals_ratelimited()) {
pr_info("%s[%d]: syscall %d\n", current->comm,
task_pid_nr(current), (int)regs->syscallno);
dump_instr("", regs);
if (user_mode(regs))
__show_regs(regs);
}
return sys_ni_syscall();
}
我们可以在此函数的起始处加入一个我们自己的syscall handle函数,并在此函数中处理所有我们要加入的系统调用。
比如:
long custom_add_syscall(struct pt_regs *regs)
{
unsigned int no;
if (is_compat_task()) {
no = regs->regs[7]; // regs[7] :32bit program
else
no = regs->regs[8]; //regs[8] :64bit program
return handle_all_add_syscall(regs, no);
}
注意一定要区分32-bit和64-bit模式,可以使用is_compat_task()函数来判断是否处于32-bit运行模式。对于32-bit程序,regs[7]中是syscall number,而对于64-bit程序,regs[8]中才是syscall number。得到了syscall number之后,我们可以进一步处理我们要加入的新的syscall了。
整个代码执行流程如下:
查找系统调用表—>表中未定义实际的处理函数—>执行默认的处理函数do_ni_syscall—>执行我们新添加的custom_add_syscall—>在我们的函数中根据系统调用号进一步处理