实验要求:

为Linux内核增加一个系统调用,并编写用户进程的程序来测试。

要求该系统调用能够完成以下功能

 (1) 该系统调用有1个整型参数,接收输入自己的学号;
       
 (2) 若参数为奇数,则返回自己学号的最后5位。如你的学号为16130120101 ,则返回20101;
       
 (3) 若参数为偶数,则返回自己的学号的最后6位。如你的学号为16130120102 ,则返回120102 。

提交内容为:

截图1: 运行结果

截图2:源代码

1 [知识点] 什么是系统调用

系统调用

Linux 的运行空间内核空间用户空间

  • 逻辑上相互隔离
  • 用户进程通常情况下 不允许 访问内核数据,也无法使用内核函数
  • 内核 提供了 用户进程与内核进行交互的一组接口:用户进程可以通过这组接口来获得操作系统内核提供的服务

系统调用的作用

  • 提供用户模式的进程和硬件设备的接口
  • 保证系统的稳定和安全
  • 实现多任务和虚拟内存

系统调用的分类

  • 控制硬件
    • 硬件资源 与 用户空间的抽象接口
    • 读写文件:write/read
  • 进程管理
    • 保证系统中的进程能以多任务在虚拟内存环境下运行
    • fork,clone,execve
  • 设置系统状态或读取内核数据
    *

系统调用和 API 接口

  • 一般,应用程序通过 应用程序 API而不是 直接通过系统调用编程
  • 应用程序使用的 应用程序API,不需要和内核提供的 系统调用 一一对应
    • 一个API 接口可以 用一个或多个 系统调用实现

系统调用和系统命令

  • 系统命令应用程序接口更高一层
  • 每一个系统命令都是一个可执行程序
    • ls
  • 系统命令的实现调用了系统调用
  • strace ls可以 查看系统命令所调用的系统调用

系统调用与内核函数

  • 内核函数只是在内核中实现的函数
  • 系统调用 是用户进入内核的接口层,本身不是内核函数,是由内核函数实现的
  • 进入内核后,不同的系统调用 找到 各自对应的内核函数
    • 这些内核函数:系统调用的“服务例程”
    • Linux 系统调用 对应的 内核例程全部以”sys_” 开头:sys_fork

2 [知识点] 系统调用实现原理

系统调用处理程序

  • 当用户态的进程 调用一个系统调用时,CPU 切换到内核态,并且执行一个内核函数
  • 系统调用处理程序执行的操作:
    • 在内核栈 保存大多数寄存器的内容
    • 调用所谓系统调用的服务例程相应 的 C函数处理系统调用
    • 通过 ret_from_sys_call 函数从系统调用返回

服务例程

  • Linux 的系统调用有200多个,相应的服务例程也是

  • 定义_syscall0 - _syscall5六个:对相应的例程进行封装

    • 每个宏名字 后缀的数字:系统调用 所用的参数个数(系统调用号除外)
  • 每个宏:需要2+2*n 个参数

    • n : 系统调用的参数个数
    • 一对参数:系统调用的返回值类型和名字
    • n对参数:系统调用参数的类型和名字
  • 例如:write() 例程的封装:

    • _syscall3(int, write, int, fd, const char*, buf, unsigned int, count)
  • 一般,系统调用于用户程序中,内核态也可以同样调用封装了的系统调用:

    • 区别:用户态进行系统调用时,需要进行用户态堆栈到内核态堆栈的切换

系统调用过程

image-20210515141634577

  • 首先:软中断:通过软件指令触发而非外设引发的中断

    • 时编程人员开发的一种异常
    • 具体是调用int $0x80汇编指令,产生向量为 0x80 的编程异常
  • 然后:内核进行中断服务的处理,并执行system_call函数

    • 进入系统调用入口的公共处理函数,在这个函数中会按照寄存器eax中的内容识别对应的系统调用
  • 进入系统内核后,使用system_call_tableeax 查到真正的系统调用,并执行相应的内核例程

  • 最后:从系统调用中返回后,最终执行syscall_exit,并调用resume_userspace返回用户空间

image-20210515142513417

image-20210515142702100

  • 从用户角度向内核:
    • 系统命令
    • 编程接口
    • 系统调用
    • 内核函数

系统调用实现

  • Linux 中,每个系统调用被赋予一个系统调用号

  • 通过系统调用号可以关联系统调用

  • 内核记录系统调用表中所有已注册的系统调用列表,存储于sys_call_table

  • linux-版本号/arch/x86/include/asm/unsted_32.h:定义了所有系统调用的编号

  • 总个数:NR_syscalls

  • 系统调用号是固定的

1.通过 异常 使进程切换到内核模式int 80h指令完成

​ 系统调用中断的入口syscall(系统调用调用号)

2.系统调用跳转表linux/arch/x86/kernel/syscall_table_32.S,并调用相应的函数

3 添加用户自定义的系统调用

要求该系统调用能够完成以下功能

 (1) 该系统调用有1个整型参数,接收输入自己的学号;
       
 (2) 若参数为奇数,则返回自己学号的最后5位。如你的学号为16130120101 ,则返回20101;
       
 (3) 若参数为偶数,则返回自己的学号的最后6位。如你的学号为16130120102 ,则返回120102 。

修改系统调用表文件-> 修改系统调用号文件->中增加系统调用声明->添加实现

  • ==需要特别注意的是==:long 型至多表示10位十进制数,而学号是11位,需要使用long long接收参数

步骤一:打开 系统调用号表syscall_64.tbl文件,并添加自定义的系统调用

  • 命令:sudo gedit arch/x86/entry/syscalls/syscall_64.tbl
  • image-20210515210516396

步骤二:在内核函数声明文件syscalls.h中添加系统调用的内核函数声明:

  • sudo gedit include/linux/syscalls.h

  • 在最后 函数声明:

    • image-20210515210608651

    • ```c
      /* My Own syscall */
      asmlinkage long sys_mysyscall(long long number);
      #endif

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22

      **步骤三**:在内核函数定义文件`sys.c`文件中添加函数定义:

      * `sudo gedit kernel/sys.c`

      * ![image-20210515210709408](E:\Hexo\Blog\source\_posts\专题二系统调用.assets\image-20210515210709408-1621084030357.png)

      * ```c
      /* My Own syscall */
      SYSCALL_DEFINE1(mysyscall, long long, number){
      long ans=0;
      if(number%2 == 0 ){
      ans = number%1000000;
      }
      else{
      ans = number%100000;
      }

      return ans;
      }
      #endif /* CONFIG_COMPAT */

接下来,==重新编译内核==

  • 净化内核
    • make mrproper
      • 删除所有编译生成文件、内核配置文件等
    • make clean
      • 删除前一次编译过程残留的数据
  • 重新编译安装内核::因为 make 太久而在结束的时候忘记 安装操作 的憨憨是我
    • make menuconfig
    • make
    • make modules_install
    • make install
  • 最后重启reboot

接着可以验证添加的系统调用是否成功:

4 验证添加的系统调用

写一段调用系统调用的程序:

image-20210515210743109

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <linux/kernel.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc,char **argv)
{
//443:long long sys_mysyscall(long long)
long long number;
long ans;
printf("输入学号:\n");
scanf("%lld",&number);
ans = syscall(443,number);
printf("mysyscall return %ld\n",ans);
return 0;
}

  • gcc -o test.c test

  • ./test

  • 结果

    • image-20210515210811389