📅  最后修改于: 2023-12-03 15:00:03.536000             🧑  作者: Mango
系统调用是操作系统提供给应用程序的一组API。应用程序可以利用这些API与操作系统进行通信,请求执行某些特定的操作,例如打开/关闭文件、读取/写入文件、发送/接收数据等等。
每个操作系统都提供了一组独特的系统调用,因此在不同的操作系统上开发应用程序可能需要不同的API。但是,大多数的操作系统提供了一些常用的系统调用,例如Linux、Windows、macOS等等。
在Linux操作系统中,我们可以通过添加一个新的系统调用来创建我们自己的系统调用。
首先,我们需要编写我们自己的系统调用函数。这个函数可以接受参数,执行某些操作,然后返回结果。例如,我们可以编写一个简单的系统调用函数,用于在控制台输出一条消息。
#include <linux/kernel.h>
#include <linux/syscalls.h>
asmlinkage long sys_hello(void)
{
printk(KERN_ALERT "Hello, world!\n");
return 0;
}
这里,我们使用了 asmlinkage
关键字来告诉编译器,这个函数需要使用不同的调用约定(不是默认的C/C++调用约定)。我们还使用了 sys_hello
来命名这个系统调用函数。
接下来,我们需要修改系统调用表,以便内核可以找到我们的新系统调用。系统调用表是一个保存所有系统调用函数指针的数组。通过向这个数组添加一个新的条目,我们可以将我们的系统调用函数添加到内核中。
#include <linux/kernel.h>
#include <linux/syscalls.h>
asmlinkage long sys_hello(void)
{
printk(KERN_ALERT "Hello, world!\n");
return 0;
}
#define __NR_hello 333
static const sys_call_ptr_t orig_sys_call_table[__NR_syscall_max+1] = {
[__NR_hello] = sys_hello,
/* ... */
};
void disable_write_protection(void)
{
unsigned long cr0 = read_cr0();
clear_bit(16, &cr0); /* CR0_WP */
write_cr0(cr0);
}
void enable_write_protection(void)
{
unsigned long cr0 = read_cr0();
set_bit(16, &cr0); /* CR0_WP */
write_cr0(cr0);
}
static int __init init_hello_syscall(void)
{
disable_write_protection();
sys_call_table[__NR_hello] = orig_sys_call_table[__NR_hello];
enable_write_protection();
return 0;
}
static void __exit exit_hello_syscall(void)
{
disable_write_protection();
sys_call_table[__NR_hello] = NULL;
enable_write_protection();
}
module_init(init_hello_syscall);
module_exit(exit_hello_syscall);
在这个例子中,我们使用 #define
来定义了我们的系统调用号为333。然后,我们在 orig_sys_call_table
中添加了一个新的条目,将它指向我们的系统调用函数。最后,我们编写了 init_hello_syscall
和 exit_hello_syscall
函数,分别用于在内核加载和卸载时修改系统调用表。
这里需要注意,修改系统调用表本身是一个敏感的操作。因为系统调用表位于内核空间,因此我们需要使用读/写特权级别才能访问它。另外,修改系统调用表可能会使得系统变得不稳定或不安全,因此应该小心使用。
最后,我们需要将我们的系统调用函数编译为内核模块,并将其加载到内核中。这个过程与一般的内核模块编程相似。
编写一个 Makefile
文件,类似于下面的例子:
obj-m += my_syscall.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
然后,我们可以使用 make
命令来编译我们的内核模块并将其加载到内核中:
$ make
$ sudo insmod my_syscall.ko
现在,我们的新系统调用就已经被添加到了内核中了!我们可以使用 strace
命令来跟踪它的调用:
$ sudo strace -e hello ./test
Hello, world!
+++ exited with 0 +++
这里的 test
是一个简单的用户态测试程序,它使用了我们的系统调用。
创建自己的系统调用可能会比较困难,但它也是一项有趣的挑战。通过深入了解操作系统内核原理,并遵循一些基本的规则,我们就可以成功地创建我们自己的系统调用。