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 可以完全不使用任何系统调用
系统调用的意义
- 用户程序通过系统调用来使用硬件,而不用关心具体的硬件设备,这样大大简化了用户程序的开发
- 系统调用使得用户程序有更好的可移植性
- 系统调用使得内核能更好的管理用户程序,增强了系统的稳定性
- 系统调用有效的分离了用户程序和内核的开发
Linux 系统调用的共性
- 系统调用通常通过函数进行调用
- 系统调用通常都需要定义一个或几个参数
- 系统调用通常会通过一个 long 类型的返回值来表示成功或者错误(负数值返回值来表明错误,0值表明成功)
添加系统调用示例
使用系统调用
使用”获取当前程序进程号的系统调用“来说明系统调用的使用
- 编写调用系统调用测试代码
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 命令过程
- 在VSCode中按
F5
编译并运行,控制台中可以得到如下输出
添加系统掉用
功能:把源文件2的内容追加到源文件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 源码所在路径
修改系统调用的入口表,文件路径:./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
- 系统调用的名称
- 入口点:入口函数名
添加系统调用声明,文件路径:./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,否则会失败
实现系统调用,文件路径:./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,这里不演示
- 编译内核并
参考 Linux 编译内核
测试系统调用
测试 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.txt
、2.txt
touch 1.txt 2.txt
- 在
1.txt
中输入aaa
,2.txt
中输入bbb
编写测试代码
#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.txt
中的内容,1.txt
中的内容成功追加至文件2.txt
完成自定义系统调用