专题二系统调用
实验要求:
为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)
一般,系统调用于用户程序中,内核态也可以同样调用封装了的系统调用:
- 区别:用户态进行系统调用时,需要进行用户态堆栈到内核态堆栈的切换
系统调用过程
首先:软中断:通过软件指令触发而非外设引发的中断
- 时编程人员开发的一种异常
- 具体是调用
int $0x80
汇编指令,产生向量为 0x80 的编程异常
然后:内核进行中断服务的处理,并执行
system_call
函数- 进入系统调用入口的公共处理函数,在这个函数中会按照寄存器
eax
中的内容识别对应的系统调用
- 进入系统调用入口的公共处理函数,在这个函数中会按照寄存器
进入系统内核后,使用
system_call_table
和eax
查到真正的系统调用,并执行相应的内核例程最后:从系统调用中返回后,最终执行
syscall_exit
,并调用resume_userspace
返回用户空间
- 从用户角度向内核:
- 系统命令
- 编程接口
- 系统调用
- 内核函数
系统调用实现
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
步骤二:在内核函数声明文件syscalls.h
中添加系统调用的内核函数声明:
sudo gedit include/linux/syscalls.h
在最后 函数声明:
```c
/* My Own syscall */
asmlinkage long sys_mysyscall(long long number);
#endif1
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 验证添加的系统调用
写一段调用系统调用的程序:
1 |
|
gcc -o test.c test
./test
结果: