既上一篇介绍了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 %dn", 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—>在我们的函数中根据系统调用号进一步处理