# 为 Linux 内核增加一个系统调用
## 题目选择:
在 Linux 内核中增加一个系统调用,并编写对应的 Linux 应用程序。利用该系统调用能够遍历系统当前 所有进程的任务描述符,并按进程父子关系将这些描述符所对应的进程 id(PID)组织成树形结构显示。
## 前期调研:
关于方法的选择,总体来说,关于添加新的系统调用有两种方法:内核模块法和编译内核法。
重新编译内核:即在内核源码中,找到包含系统调用号的文件,在其中添加系统调用编号、系统调用跳 转表和相应历程。 添加内核模块:通过将增加系统调用的所有指令封装成一个模块,并在其中实现新的系统调用的功能函 数。
比较两种方法,我们发现修改内核源码来实现系统调用不仅步骤繁琐,更需要重新编译内核源码, 需要花费较多时间。和修改内核源码相比,添加内核模块的做法更加符合模块化程序的设计思想,设计 思想也相对比较清晰。符合程序设计模块化的思想。操作也更加便捷,因此本实验选择内核模块法来完 成系统调用的新增。
在本实验中,系统的版本及内核版本如下所示 系统版本和内核版本:
Ubuntu 14.04.1 LTS
3.13.0-128-generic 32 位
![](https://www.writebug.com/myres/static/uploads/2021/12/20/020e81802f9dd1a7f74e5cec13626577.writebug)
![](https://www.writebug.com/myres/static/uploads/2021/12/20/66713c07dc7776c55a396e287154aeb4.writebug)
## 设计思路
整个程序的构思是将增加系统调用号的所有操作在一个文件中体现,之后将该程序运行得到内核模块,将内核模块加载进入系统内核中,之后利用测试程序测试内核模块是否添加成功以及新增的系统调用的功能是否能够实现。
### 各模块的说明:
1、首先我们先进入内核查看系统调用表,找到空余的系统调用号以及系统调用表所对应的内存地址 以用于添加新的系统调用。系统调用表所在路径位 */boot/System.map-3.13.0-128-generic*
从下图可以发现有两个空余的系统调用号 222、223 没有被使用,选择 222 号作为本实验所要求的新的系统 调用号。
![](https://www.writebug.com/myres/static/uploads/2021/12/20/0477a2d52ca40838c4da80b2c830a5c5.writebug)
在 */boot/System.map-3.13.0-128-generic* 当我们查看系统调用表时可以看到系统调用表只有“读”权限,因此在添加系统调用号之前首先要对内存区域进行权限修改
![](https://www.writebug.com/myres/static/uploads/2021/12/20/4bf77a683b4defdb316a5e1de701ab7c.writebug)
2、关于修改寄存器的权限属性:利用内嵌汇编代码来实现。
asm("movl %%cr0, %%eax":"=a"(cr0)); 是 Linux C 中内嵌的汇编代码,格式位"movl %1,%0":"=r"(result):"m"(input) , "movl %1,%0" 为指令模板,冒号后面是每个操作数对应的 C 表达式(括号的内容)和表达式的说明和限制(引号里的内容)。冒号后第一项对应的是 %0,第二项对应的是 %1。
![](https://www.writebug.com/myres/static/uploads/2021/12/20/06cd316bf43e5863e0929b4890c7c5f4.writebug)
3、构造树形图函数
![](https://www.writebug.com/myres/static/uploads/2021/12/20/a607a9f77102c13ecee0694d36e5fbd0.writebug)
4、用 sys_mycall 作为新的系统调用,功能是遍历所有进程的并记录任务描述符
![](https://www.writebug.com/myres/static/uploads/2021/12/20/c25d7bb9c13c1b4285aa4a4ddbc87079.writebug)
5、将要新增的系统调用号的函数加入到内核中初始化内核
![](https://www.writebug.com/myres/static/uploads/2021/12/20/817a17f5aa4cf5a56edc01470380e37a.writebug)
6、内核模块的加载与卸载
对于内核构造函数 module_init(init_addsyscall); , 函数原型必须是 module_init() ,括号内的是函数指针.模块析构函数为 module_exit(exit_addsyscall); ,函数原型为 module_exit(); ,此函数在执行卸载内核模块时会被调用。MODULE_LICENSE("GPL"); 是模块许可声明,是内核程序使用的许可证。
![](https://www.writebug.com/myres/static/uploads/2021/12/20/50ea92f276f8ea50505277e7b1dc1452.writebug)
7、内核模块卸载函数
![](https://www.writebug.com/myres/static/uploads/2021/12/20/7a6b021b070065659d294ff291a30a78.writebug)
遇到的问题及解决方法:
![](https://www.writebug.com/myres/static/uploads/2021/12/20/304ec6b962fe36d64084d029d90e8036.writebug)内核空间与用户空间不能直接访问,如何在用户空间显示内核中的进程树图?
利用 copy_to_user 函数来实现内核与用户空间的数据交换,copy_to_user(void __user *to,const void *from, unsigned long n),这里的 to 是目标地址即用户空间地址, from 是原地址即内核地址;From 源地址, n 是将要拷贝的数据的字节数。
![](https://www.writebug.com/myres/static/uploads/2021/12/20/85c653a6577a953f1a5218685a89977a.writebug)对于用户空间中用于存储进程信息的数组的大小应该如何选择?
数组大小的设置原则是不让数据溢出,首先查看系统所能支持的最大的进程数,本系统中为 14951![](https://www.writebug.com/myres/static/uploads/2021/12/20/ee596629d41106c0f017ed933ae5ed07.writebug),但是实际运行的进程数远小于这个数,在实验中,我们选择的数组大小是 1000,查看当前执行的程序数量![](https://www.writebug.com/myres/static/uploads/2021/12/20/af008172714637901a5fef67423751f9.writebug),80 远小于 1000,故不会存在数据溢出的情况。
![](https://www.writebug.com/myres/static/uploads/2021/12/20/24d1ed9e1d6de719dded7d6bd3eacb3b.writebug)如何让判断系统调用模块添加到了内核中是否成功?利用 dmesg 命令可查看内核中环形缓冲区信息,看输出是否正确。
## 程序结果:
1、加载内核模块:
![](https://www.writebug.com/myres/static/uploads/2021/12/20/d9fa2db3910263bf0ad46b37a0d6aa56.writebug)
可以看到出现了一个 hello 模块
2、运行测试程序
![](https://www.writebug.com/myres/static/uploads/2021/12/20/69a932e59bcbab11a31100a2aa81d78e.writebug)![](https://www.writebug.com/myres/static/uploads/2021/12/20/81a7422c4b6378f5ef323d51deda0d51.writebug)
![](https://www.writebug.com/myres/static/uploads/2021/12/20/4994c69ce2e91c193fbdc2d6087cef16.writebug)
3、卸载内核模块
![](https://www.writebug.com/myres/static/uploads/2021/12/20/3706670dd45f0a3dd513aafbd60378f0.writebug)
4、 卸载内核后运行测试程序
![](https://www.writebug.com/myres/static/uploads/2021/12/20/950f73fb76137792b91ee44c3640c220.writebug)
5、利用 dmesg 指令查看输出是否正确
![](https://www.writebug.com/myres/static/uploads/2021/12/20/85f366c96803ce930561052ee3c7d632.writebug)
## 源代码:
### hello.c
```
#include < linux / init.h >
#include < linux / module.h >
#include < linux / kernel.h >
#include < linux / unistd.h >
#include < asm / uaccess.h >
#include < linux / sched.h >
#define my_syscall_num 222 //新增系统调用的调用号
#define sys_call_table_address 0xc1672140 //系统调用表的地址
static int counter = 0;
struct process {
int pid;
int depth;
};
struct process a[1000];
unsigned int clear_and_return_cr0(void);
void setback_cr0(unsigned int val);
asmlinkage long sys_mycall(char __user * buf); //asmlinkage的作用是告诉编译器,函数参数不是用
用寄存器来传递,而是用堆栈来传递的int orig_cr0;
unsigned long * sys_call_table = 0;
static int( * anything_saved)(void);
unsigned int clear_and_return_cr0(void) //寄存器权限修改函数
{
unsigned int cr0 = 0;
unsigned int ret;
asm("movl %%cr0, %%eax": "=a" (cr0)); //输入部分为空,即直接从cr0寄存器中取数;输出部分为cr0变量,a表示将cr0和eax相关联。这句话的作用是cr0寄存器中的值就赋给了变量cr0 ret = cr0;
cr0 &= 0xfffe
没有合适的资源?快使用搜索试试~ 我知道了~
温馨提示
资源包含文件:设计报告word+程序代码 在 Linux 内核中增加一个系统调用,并编写对应的 Linux 应用程序。利用该系统调用能够遍历系统当前 所有进程的任务描述符,并按进程父子关系将这些描述符所对应的进程 id(PID)组织成树形结构显示。 整个程序的构思是将增加系统调用号的所有操作在一个文件中体现,之后将该程序运行得到内核模块,将内核模块加载进入系统内核中,之后利用测试程序测试内核模块是否添加成功以及新增的系统调用的功能是否能够实现。 详细介绍参考:https://blog.csdn.net/sheziqiong/article/details/125458435
资源推荐
资源详情
资源评论
收起资源包目录
为Linux内核增加一个系统调用.zip (7个子文件)
实验报告.docx 166KB
实验报告.pdf 452KB
README.md 11KB
LICENSE 1KB
source_code
hello.c 3KB
hello_test.c 399B
Makefile 344B
共 7 条
- 1
shejizuopin
- 粉丝: 1w+
- 资源: 1300
下载权益
C知道特权
VIP文章
课程特权
开通VIP
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功
- 1
- 2
- 3
- 4
前往页