Linux添加系统调用

Linux添加系统调用

  1. 前提知识(系统调用如何执行) 先看文章 裁剪Linux内核,用qemu进行调试

    • 我们通常写的c,有相对应的开源的标准库glibc,(2.23的glibc中还是有很多缺陷的)其中的头文件unistd.h就包含了许多的系统调用,如read/write/open等等
    • 系统调用是通过的修改寄存器eax的值,然后通过触发 软中断使系统进入内核空间,比如32位下经典的int 80以及64位的syscall。如果你想更清楚的了解系统调用可以查看我之前写的 ret2syscall
    • 所以内核的中断处理函数是根据系统调用号来调用相应的内核函数,c中的read/write,其实调用了内核函数sys_read/sys_write
    • 因此,添加一个系统调用需要注册 系统调用号 以及相应的 中断处理函数,如此,内核才能找到这个系统调用和执行对应的内核函数。内核的汇编代码最终会在include/generated/asm/syscalls_64.h中查找调用号,不过我们并不修改这里,x86平台提供了一个专门用来注册系统调用的文件/arch/x86/entry/syscalls/syscall_64.tbl,在编译时会运行同目录下的syscalltbl.sh脚本,将这个文件中登记过的系统调用都生成到前面的syscalls_64.h文件中。因此我们后面要添加系统调用就是修改这个tbl文件。
    • 系统执行相应功能,调用的是C代码编写的函数,而不是汇编代码,减轻了编写实现系统调用的负担。系统调用的函数定义include/linux/syscalls.h中,因为汇编代码到C代码的参数传递是通过栈实现的,所以可以看到所有系统调用的函数前面都使用了asmlinkage宏,它意味着编译时限制只使用栈来传递参数。
    • 系统调用函数的实现kernel/sys.c 中,当我们注册了相应的系统调用号以及定义了 系统调用的函数 之后,我们就可以在此文件的代码的最后添加自己的函数,这个文件中有很多已经实现的系统调用的函数作为参考。
  2. 添加系统调用号

    • 编辑调用号注册表
    cd  arch/x86/entry/syscalls/
    vi syscall_64.tbl
    
    • 添加系统调用号

      #上面省略
      328     64      pwritev2                sys_pwritev2
      329     common  pkey_mprotect           sys_pkey_mprotect
      330     common  pkey_alloc              sys_pkey_alloc
      331     common  pkey_free               sys_pkey_free
      332     common  statx                   sys_statx
      333     common  ps_counter              sys_ps_counter
      # 下面的我们x32先不管,系统调用号,就在330左右,在此行上面添加
      # x32-specific system call numbers start at 512 to avoid cache impact
      # for native 64-bit operation.
      #
      
      

      Linux添加系统调用_第1张图片

  3. 定义系统调用函数

    • 编辑系统调用函数原型定义

      vi include/linux/syscalls.h 
      
    • 添加函数原型(__user 是为了向用户空间传值)

      #以上省略
      asmlinkage long sys_pkey_mprotect(unsigned long start, size_t len,
                                        unsigned long prot, int pkey);
      asmlinkage long sys_pkey_alloc(unsigned long flags, unsigned long init_val);
      asmlinkage long sys_pkey_free(int pkey);
      asmlinkage long sys_ps_counter(int __user *num);
      asmlinkage long sys_statx(int dfd, const char __user *path, unsigned flags,
                                unsigned mask, struct statx __user *buffer);
      
      #endif
      
      

    Linux添加系统调用_第2张图片

  4. 编写实现函数

    • 实现函数文件

      vi kernel/sys.c 
      
    • 实现函数(这里就只要统计task数量到用户空间,我顺便输出了内核相关的task的信息,可以做调试用)

      SYSCALL_DEFINE1(ps_counter, int __user*, num)
      {
              struct task_struct *task; 
              int counter=0;
              printk("[Syscall] ps_counter\n");
              printk("Now pid = %ld",current->pid);#current是宏,指向当前task
              printk("Pid    Parent->pid"); 
              for_each_process(task){
                      counter++;
                      printk(" %ld    %ld\n",task->pid,task->parent->pid);
              }
              copy_to_user(num, &counter, sizeof(int)); #结果显示到用户空间
              return 0;
      }
      

      Linux添加系统调用_第3张图片

  5. 编写测试代码

    • 源文件
    vi test_syscall.c 
    
    #include 
    #include 
    #include 
    int main(void){
            int result;
            syscall(333,&result); //系统调用号,是你自己在`tbl`中注册的
            printf("process number is %d\n",result);
            return 0;
    }
    
    • 编译(一定静态编译,因为你的qemu下的linux可能没有这些库)
    gcc -static -o get_ps_num get_ps_num.c 
    
    • 将编译好的二进制,放到busybox下的_install 下的tmp文件夹中,重新压缩gpio文件,这一步看之前的文章 裁剪Linux内核,用qemu进行调试
    find . -print0 | cpio --null -ov --format=newc   | gzip -9 > ../initramfs.cpio.gz
    
  6. 运行测试程序

    • 编译添加完系统调用号,定义了系统调用函数,完成了函数实现之后,编译linux内核

      sudo make -j8
      
    • 使用新编译的内核以及刚打包的根文件系统,启动虚拟机

      qemu-system-x86_64 -kernel ~/Desktop/linux-4.14/arch/x86/boot/bzImage -initrd ~/Desktop/busybox-1.32.1/initramfs.cpio
      

      Linux添加系统调用_第4张图片

你可能感兴趣的:(Linux内核)