📜  create syscall - C 编程语言(1)

📅  最后修改于: 2023-12-03 15:00:03.536000             🧑  作者: Mango

创造系统调用 - C 编程语言

什么是系统调用?

系统调用是操作系统提供给应用程序的一组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_syscallexit_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 是一个简单的用户态测试程序,它使用了我们的系统调用。

总结

创建自己的系统调用可能会比较困难,但它也是一项有趣的挑战。通过深入了解操作系统内核原理,并遵循一些基本的规则,我们就可以成功地创建我们自己的系统调用。