为 Linux 添加系统调用

本文实际上是浙江大学操作系统课程实验二的一个记录,所以会有一些很奇怪的操作。

实验目的

添加一个系统调用 mysyscall,获取系统中 Page Fault 的次数。基于 Linux Kernel 4.6 的代码完成。

实验方法

这里需要说明一下的是 include/uapi/asm-generic/unistd.hinclude/linux/syscalls.harch/x86/entry/syscalls/syscall_64.tblarch/x86/entry/syscalls/syscall_32.tbl 的区别。unistd.h 是一个 POSIX 标准的头文件,其中包含了 POSIX 标准的系统调用的函数封装;syscalls.h 是 Linux 系统调用真实的声明文件;syscall_<32/64>.tbl 是对应架构的系统调用列表,这里存在 32 和 64 两个版本是因为 x86 架构有 x86_64 这样的扩展。

因此,如果我们需要在一台 x86_64 架构的 Linux 系统中添加一个系统调用,我们对于以上的文件都需要做出修改。但是需要注意的一点是,他们中的系统调用号不必相同,但是最终在 user mode 中使用该系统调用的时候,使用的应该是对应版本 tbl 中的编号。比如一个 x86_64 架构的 PC,最终调用的编号应该和 syscall_64.tbl 中的编号相同。

接下来我们可以开始添加系统调用了,假设我们系统调用的名字为 mysyscall,实现函数体的原型为 asmlinkage long sys_mysyscall(void);

  1. 在 include/linux/syscalls.h 中写入系统调用函数的声明,注意需要写在最后一行 #endif 之前。比如我们这里就需要加入 asmlinkage long sys_mysyscall(void);
  2. 在 include/uapi/asm-generic/unistd.h 中写入 POSIX 标准通用的系统调用声明,虽然类似于 x86 这样的架构有自己的系统调用表,但是我们应该保持兼容。这里我们需要加入这样的两行:
    #define __NR_mysyscall 292
    __SYSCALL(__NR_mysyscall, sys_mysyscall)
  3. 在 kernel/sys_ni.c 中写入刚才加入的系统调用,这个提供了一个 fallback stub implementation,返回值是 -ENOSYS (Function not implemented.)
    cond_syscall(sys_mysyscall);
  4. 在 arch/x86/entry/syscalls/syscall_64.tbl 中写入刚才添加的系统调用,如果不需要在 x86 和 x86_64 上有不同的表现,则应该将属性定为 common
    340    common     mysyscall    sys_mysyscall
  5. 在 arch/x86/entry/syscalls/syscall_32.tbl 中也加入类似的东西,这是为 i386 架构服务的
    380    i386    mysyscall    sys_mysyscall
  6. 实现 Page Fault 统计
    include/linux/mm.h 文件中声明变量 pfcountextern unsigned long pfcount;
    在进程 task_struct 中增加成员 pf,在 include/linux/sched.h 文件中的 task_struct 结构中添加 pf 字段 unsigned long pf;
    在进程创建过程中将 pf 字段置 0,修改 kernel/fork.c 文件中的 dup_task_struct() 函数:

    static struct task_struct *dup_task_struct(struct task_struct *orig)
    {   …… 
        tsk = alloc_task_struct_node(node); 
        if (!tsk) return NULL;
        ……    
     ++ tsk->pf=0; 
        ……
    }

    arch/x86/mm/fault.c 文件中定义变量 pfcount;并修改 arch/x86/mm/fault.cdo_page_fault() 函数。每次产生缺页中断,do_page_fault() 函数会被调用,pfcount 变量值递增1,记录系统产生缺页次数,current->pf 值递增 1,记录当前进程产生缺页次数:

    …
    ++ unsigned long pfcount;
    __do_page_fault(struct pt_regs *regs, unsigned long error_code)
    {
        …
     ++ pfcount++;
     ++ current->pf++;
        …
    }
  7. sys_mysyscall 的实现
    usr/src/linux-4.6 下建立 mysyscall 文件夹,其中建立三个文件 Makefilemysyscall.cmysyscall.h,分别如下:
    mysyscall.h

    asmlinkage long sys_mysyscall(void);

    mysyscall.c

    #include<linux/kernel.h>
    #include<linux/init.h>
    #include<linux/sched.h>
    #include<linux/syscalls.h>
    #include "mysyscall.h"
    asmlinkage long sys_mysyscall(void)
    {
     printk("Current Page Fault Count: %lu\n", current->pf);
     return 0;
    }

    Makefile

    obj-y:=mysyscall.o
    
  8. 修改主 Makefile 以编译 mysyscall
    找到 usr/src/linux-4.6/Makefile 中这样一行 core -y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/,改为 core -y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ mysyscall/
  9. 编译安装内核
    sudo make bzImage -j $(nproc) && sudo make modules -j $(nproc) && sudo make modules_install && sudo make install && sudo update-grub
  10. 重启
  11. 编写用户态程序,由于我本地是 x86_64 架构,因此使用刚才设定的 340 号系统调用
    #include <stdio.h>
    #include <linux/kernel.h>
    #include <sys/syscall.h>
    #include <unistd.h>
    #define __NR_mysyscall 340
    int main()
    {
      long ret = syscall(__NR_mysyscall);
      printf("Return Value: %ld\n", ret);
      return 0;
    }
    
  12. 在 /var/log/kern.log 中查看输出,或者直接使用 dmesg 查看输出

一些额外的收获

/usr/src/linux-4.6/scripts/checksyscall.sh 是一个很神奇的脚本,它对于所有的代码进行 gcc 预处理之后,判断系统调用是否被正确写入和注册,如果这个测试不能通过,编译成功后也不一定能正常运行该系统调用。

查看 #include 命令调用的真实文件:echo '#include <unistd.h>' | gcc -E -x c -,会得到类似于 # 1 "/usr/include/unistd.h" <some numbers> 这样的输出,意思是后面这些 number 标记的行的代码来自于 /usr/include/unistd.h 

如果要看真实的引用文件,可以这么用:echo '#include <unistd.h>' | gcc -E -x c - | egrep '# [0-9]+ ' | awk '{print $3;}' | sort -u

kern.log 是 Line-Buffered,因此每个 printk 之后需要换行才能在 dmesg 中看到输出

编译内核模块的时候如果发现 arch/x86/Makefile:148: CONFIG_X86_X32 enabled but no binutils support 这样的错误,首先检查一下路径中是否有空格,如果有空格会引发这样的错误

参考资料

Linux Kernel 4.6 代码

https://unix.stackexchange.com/questions/1697/how-can-i-know-which-unistd-h-file-is-loaded

https://www.kernel.org/doc/html/v4.12/process/adding-syscalls.html

http://www.cs.fsu.edu/~cop4610t/lectures/project2/system_calls/system_calls.pdf

https://medium.com/@ssreehari/implementing-a-system-call-in-linux-kernel-4-7-1-6f98250a8c38

https://stackoverflow.com/questions/11237420/error-syscall-function-not-implemented

 

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注