Linux 系统调用

实验环境

  • 本地开发操作系统:Windows 10

  • 远程开发操作系统:CentOS Linux release 7.6.1810 (Core)

  • (选用,也可直接使用 vim)VSCode:1.56.1

实验环境配置方法见使用 VSCode 配置远程开发

简介

什么是系统调用

系统调用(在 linux 中常称作 syscalls)是由操作系统实现提供的所有系统调用所构成的集合即程序接口或应用编程接口(Application Programming Interface,API)。是应用程序同系统之间的接口。简单来说,系统调用就是用户程序和硬件设备之间的桥梁,用户程序在需要的时候,通过系统调用来使用硬件设备等。

系统调用和 API

一般情况下,应用程序通过应用编程接口 API 而不是直接通过系统调用来编程

应用编程接口 API 与系统调用的关系如下:(应用程序编程接口实际上并不需要和内核提供的系统调用对应)

  • 一个 API 可以实现成一个系统调用

  • 一个 API 可以通过调用多个系统调用来实现

  • 一个 API 可以完全不使用任何系统调用

系统调用的意义

  1. 用户程序通过系统调用来使用硬件,而不用关心具体的硬件设备,这样大大简化了用户程序的开发
  2. 系统调用使得用户程序有更好的可移植性
  3. 系统调用使得内核能更好的管理用户程序,增强了系统的稳定性
  4. 系统调用有效的分离了用户程序和内核的开发

Linux 系统调用的共性

  1. 系统调用通常通过函数进行调用
  2. 系统调用通常都需要定义一个或几个参数
  3. 系统调用通常会通过一个 long 类型的返回值来表示成功或者错误(负数值返回值来表明错误,0值表明成功)

添加系统调用示例

使用系统调用

使用”获取当前程序进程号的系统调用“来说明系统调用的使用

  1. 编写调用系统调用测试代码

task01.c

   #include <syscall.h>
   #include <unistd.h>
   #include <stdio.h>
   #include <sys/types.h>
   
   int main(void) {
       long CC1,CC2;
       // 通过 syscall 调用 SYS_getpid
       CC1 = syscall(SYS_getpid);
       printf ("syscall(SYS_getpid)=%ld\n", CC1);
       // C 函数库调用
       CC2 = getpid();
       printf ("getpid()=%ld\n", CC2);
       // 用 execve 执行 shell 命令
       // 传递给执行文件的参数数组,这里包含执行文件的参数
       char *argv[]={"ps","aux",NULL,NULL};
       // 传递给执行文件新的环境变量数组
       char *envp[]={0,NULL};
       execve("/bin/ps",argv,envp);
       return (0);
   }
  • syscall(SYS_getpid)getpid()函数是直接调用系统调用的两种方式
  • execve 是执行 shell 命令过程

  1. 在VSCode中按F5编译并运行,控制台中可以得到如下输出

添加系统掉用

功能:把源文件2的内容追加到源文件1的末尾。

  1. 下载内核源码,并解压,参考 Linux 编译内核

    $ cd ~/download/
    $ wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.16.tar.gz
    $ cd /usr/src/kernels/
    $ tar -zxvf ~/download/linux-4.1.16.tar.gz
    $ cd linux-4.1.16/
    

VSCode 文件-打开文件夹,选择 Linux 源码所在路径

  1. 修改系统调用的入口表,文件路径:./arch/x86/syscalls/syscall_64.tbl,在文件末尾插入自定义的系统调用信息(这里多定义一个打印名字的系统调用)

    #
    # This is my system call
    #
    546 64 my_name     customize_print_name
    547 64 my_addition customize_file_addition
    

四个参数含义分

  • 系统调用号
  • ABI:应用程序二进制接口,可选值:common、64、x32
  • 系统调用的名称
  • 入口点:入口函数名

  1. 添加系统调用声明,文件路径:./include/linux/syscalls.h,在文件尾部进行如下添加

    /**
    * This is my system call statement
    */
    asmlinkage long customize_print_name(void);
    asmlinkage long customize_file_addition(char* sorceFilePath, char* goalFilePath);
    

注意:如果是没有参数的调用那么就一定要写void,否则会失败

  1. 实现系统调用,文件路径:./kernel/sys.c,在文件尾部进行如下添加具体的系统调用实现

    /**
    * This is my system call code
    */
    asmlinkage long customize_print_name(void) {
    	printk("my name is foleyzhao\n");
    	return 0;
    }
       
    #define O_RDONLY	00000000
    #define O_WRONLY	00000001 
    #define O_APPEND	00002000
    asmlinkage long customize_file_addition(char* sourceFilePath, char* goalFilePath) {
    	// 源文件、目标文件描述符
       int from_fd, to_fd;
       int ret = 1;
       
    	// 打开源文件
       if ((from_fd = sys_open(sourceFilePath, O_RDONLY, 777)) == -1)
       {
           printk("open source file error\n");
           return 1;
       }
       	
    	// 打开目标文件
       if ((to_fd = sys_open(goalFilePath, O_WRONLY|O_APPEND, 777)) == -1)
       {
           printk("open goal file error\n");
           return 1;
       }
       
    	// 进行文件内容追加
       while (ret)
       {
    		char buffer[1024];
           ret = sys_read(from_fd, buffer, 1024);
           if (ret == -1)
           {
               printk("read source file error\n");
               return 1;
           }
           sys_write(to_fd, buffer, ret);
       }
       
       // 关闭文件
       sys_close(from_fd);
       sys_close(to_fd);
    	return 0;
    }
    

实现系统调用也可以自己写一个文件,但是需要修改Makefile,这里不演示

  1. 编译内核并

参考 Linux 编译内核

  1. 测试系统调用

    • 测试 my_name:

      #include <syscall.h>
      #include <unistd.h>
      #include <stdio.h>
      #include <sys/types.h>
      #define MY_NAME 546
           
      int main(void) {
       // 通过 syscall 调用 my_name
       syscall(MY_NAME);
       return (0);
      }
      

    按 F5 编译执行后,终端输入 dmesg 得到如下输入:

    • 测试 my_addition:

      1. 在源码路径下新建两个文件 1.txt2.txt

        touch 1.txt 2.txt
        

      1. 1.txt 中输入 aaa2.txt中输入 bbb

      1. 编写测试代码

        #include <syscall.h>
        #include <unistd.h>
        #include <stdio.h>
        #include <sys/types.h>
        #define MY_ADDITION 547
                
        int main(void) {
        // 通过 syscall 调用 my_addition
        syscall(MY_ADDITION, "/root/code/syscall/1.txt", "/root/code/syscall/2.txt");
        return (0);
        }
        
      2. 查看文件 2.txt 中的内容,1.txt 中的内容成功追加至文件 2.txt

完成自定义系统调用