在Linux中添加新的系统调用
系统调用
需要更新多个文件以添加新的系统调用:
- new/path/to/implementation - 系统调用函数实现(如果实现位置在一个新的文件或者目录,还需要进行一些Makefile更改)
- include/linux/syscalls.h - 函数原型
通用和体系结构特定的系统调用表:- include/uapi/asm-generic/unistd.h - 用户空间API中的通用系统调用表
- arch/x86/entry/syscalls/syscall_64.tbl - 体系结构特定的系统调用表
函数实现(Function Implementation)
首先,创建一个新目录,其中包含自己的Makefile和hello_world.c文件:
# from the root of the linux source tree
mkdir hello_world
touch hello_world/{Makefile,hello_world.c}
将以下内容添加到Makefile中:
obj-y := hello_world.o
在 hello_world/hello_world.c 中,使用SYSCALL_DEFINE
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/syscalls.h>
SYSCALL_DEFINE2(hello_world, const char*, message, unsigned long, len) {
char *kmessage;
int res;
if (len > 0x1000) {
pr_err("User message length was unreasonably long\n");
return -EINVAL;
}
kmessage = kzalloc(len + 1, GFP_KERNEL);
if (!kmessage) {
pr_err("Failed to allocate %08lux bytes\n", len + 1);
return -ENOMEM;
}
res = copy_from_user(kmessage, message, len);
if (res) {
return -EFAULT;
}
pr_info("IN HELLO WORLD! Message: %s\n", kmessage);
return 0;
}
关于这段代码需要注意以下几点:
- 绝对不要直接访问用户内存!请使用copy_from_user/copy_to_user/get_user/put_user函数。
- 在Linux中可以通过多种调用来分配内存。这里Linux内核文档中的官方内存分配指南值得阅读一下。
- 返回值是错误码负值,或者为0表示成功。
- pr_
(...)函数是包装了printk(KERN_ ...)函数的宏。有关详细信息,请参阅 [Message logging with printk](https:_www.kernel.org_doc_html_latest_core-api_printk-basics)。
函数声明(Function Prototype)
函数声明本身位于include/linux/syscalls.h中:
asmlinkage long sys_hello_world(const char* message, unsigned long len);
这是新系统调用的主入口点,一般这些系统调用函数声明是以sys_为前缀。
系统调用表(System Call Table)
通用系统调用表(include/uapi/asm-generic/unistd.h)和特定架构的系统调用表(arch/x86/entry/syscalls/syscall_64.tbl)都存在。该系统调用应添加到通用表和所有支持它的架构中。
对于通用系统调用表的更改(不要忘记增加__NR_syscalls!):
diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h
index 1c48b0ae3ba3..395bd563d6f2 100644
--- a/include/uapi/asm-generic/unistd.h
+++ b/include/uapi/asm-generic/unistd.h
@@ -886,8 +886,11 @@ __SYSCALL(__NR_futex_waitv, sys_futex_waitv)
#define __NR_set_mempolicy_home_node 450
__SYSCALL(__NR_set_mempolicy_home_node, sys_set_mempolicy_home_node)
+#define __NR_hello_world 451
+__SYSCALL(__NR_hello_world, sys_hello_world)
+
#undef __NR_syscalls
-#define __NR_syscalls 451
+#define __NR_syscalls 452
对x86系统调用表的更改。
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index c84d12608cd2..e44d6d33e7ce 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -372,6 +372,7 @@
448 common process_mrelease sys_process_mrelease
449 common futex_waitv sys_futex_waitv
450 common set_mempolicy_home_node sys_set_mempolicy_home_node
+451 common hello_world sys_hello_world
重新构建内核
在进行更改后,重新构建内核。如果想首先清除所有内容(包括配置文件),需要运行 make mrproper,然后重新配置。
测试系统调用
在之前关于构建内核的帖子基础上,我们将使用qemu来调试系统调用。
首先,我们需要在用户空间中实际进行系统调用。我们将使用syscall(2)函数直接调用我们的新系统调用:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char **argv) {
const char* message = "Hello, world!";
int res;
printf("Going to call hello_world, syscall 451\n");
res = syscall(451, message, strlen(message));
printf("Tried to call it! Result: %d\n", res);
}
编译其为 hello_world_test
gcc hello_world_test.c -o hello_world_test
使用qemu-system-x86_64运行Linux内核,并运行我们的hello_world_test二进制文件。
qemu-system-x86_64 \
-kernel arch/x86_64/boot/bzImage \
-nographic \
-append "console=ttyS0" \
-m 1024 \
-initrd initfs \
--enable-kvm \
-cpu host \
-s -S \
-fsdev local,path=$(pwd),security_model=none,id=test_dev \
-device virtio-9p,fsdev=test_dev,mount_tag=test_mount
通过远程调试会话(在另一个终端中)附加gdb。
$> gdb vmlinux
(gdb) target remote :1234
(gdb) c
回到qemu shell,在我们运行的虚拟机中将 9p 共享目录挂载到/shared目录。这样,Linux源代码树的根目录(或者当你运行qemu-system-x86时所在的位置)就可以在虚拟机内访问了。
(initramfs) mkdir /shared
(initramfs) mount -t 9p -o trans=virtio test_mount /shared/ -oversion=9p2000.L,posixacl,msize=512000,cache=loose
在qemu shell中,现在应该能够从/shared目录下运行已构建的hello_world_test二进制文件。
(initramfs) /shared/hello_world_test
Going to call hello_world, syscall 451
[ 38.305249] hello_world: IN HELLO WORLD! Message: Hello, world!
Tried to call it! Result: 0
[ 38.308184] hello_world_tes (186) used greatest stack depth: 27392 bytes left