实验四 增加Linux系统调用
一、实验目的
学习如何产生一个系统调用以及怎样同过往内核中增加一个新函数从而在内核空间中实现对用户空间的读/写。
所有的内核函数入口表集中在/user/src/linux/arch/i386/kernel/entrys.S
中。系统调用是在sys_call_table
中定义的,这样当增加一个新的系统调用是,就必须在这个表中增加一个新的表项。编辑该文件,增加自己的系统调用。
注意,Linux
系统自身保留了221个系统调用。这就意味着,你自己增加的系统调用至少要在第222项以后开始,还要小于256。内核函数的实现体具有以下的形式:
asmlinkage <type> <func_name> (paramlist){}
其中,asmlinkage
关键字指名了函数的参数的传递方式——必须使用堆栈进行。这里涉及到一个问题——int 80
到底做了些什么?事实上,它要完成一系列的动作:
-
在系统调用表中找出对应于系统调用号的表项;
-
确定系统调用的参数条目;
-
将参数从用户地址空间拷贝到u区;
-
保存当前进程的上下文;
-
调用内核中的系统调用代码;
-
返回用户进程。
如何在用户态调用内核系统函数呢?原则上只有使用致限引发0x80中断一种方法。例如:当你调在用一个具有两个参数的你同调用sys_xxx_call
,若要传入参数param1
和param2
并且将返回值存放在res
中,就要采用如下方法:
long res;
asm volatile (“int $0x80” \:”=a”(res) \:”0” (__NR_sys_xxx_call), “b”((long)(param1)),\“c”((long)(param2)));
return res;
另外,系统定义了syscall0
~syscall5
这六个宏,分别封装对就有0个到5个参数的内核函数的调用。
最后要注意的是,在编译使用内和函数的C文件时要通知编译器把这些代码当作内和代码而不是普通的用户代码来编译,这可以通过向编译器传递__KERNEL__
标志来实现,使用-W编译选项来向装载程序传递all。如下所示:
gcc –Wall –DKERNEL xxx.c
当然,如果你采用重新编译内核的方法,并不需要这样做——只要修改内核源码目录的Makefile
,并将你的文件添加至O_OBJS
列表中就可以了。因为Makefile
的FLAGS
标志已经包含上述编译选项了。
二、实验内容
-
添加一个新的内核系统调用,具体完成某个你希望实现的功能。
-
重新编译内核,使你的系统调用可用。
-
编写一个用户态的程序,验证你增加的系统调用。
三、实验步骤
-
在
arch/i386/kernel/entry.S
文件中的系统调用入口表中,找到某个空项:查看系统调用表,标注成
sys_ni_syscall
的就是暂时空闲的调用。.long SYMBOL_NAME(sys_ni_syscall)
修改为新加系统调用的入口项,本次实验使用第254号:
.long SYMBOL_NAME(my_syscall)
记住系统调用号(在系统调用入口表中的第几个入口)。
-
在核心文件
kernel/sys.c
中添加实现系统调用SYS_my_call()
的代码段:#include <linux/kernel.h> #include <linux/module.h> #include <linux/sched.h> /* struct task_struct */ asmlinkage int sys_my_syscall() { printk("Hello, now my name DXH & my stu_number 20182640 are in the kernel"); return 0; }
-
在
/usr/include/asm/unistd.h
文件中增加:#define __NR_my_syscall 254
-
进入相应的文件位置,生成和运行新内核
首先进行编译的设置
cd /usr/src/linux-2.4.32 make menuconfig
使用默认设置(直接
ESC
然后选择Yes
即可)make clean make dep make bzImage make install
然后重启计算机
reboot
-
编写c程序使用新的系统调用(见“实验结果”小节)
四、实验结果
在应用程序中使用新添加的系统调用mycall
,编写如下C
程序 进行验证。
#define __NR_my_syscall 254
int errno;
#include <linux/unistd.h>
_syscall1(int, my_syscall);
int main(int argc, char *argv[]) {
my_syscall();
}
编译该程序
gcc -o main main.c
执行编译好的程序
./main
读取系统日志文件
dmesg
输出内容如下所示
五、实验心得
本次实验的主要内容是增加一个属于自己的系统调用。实验本的操作步骤相对较多,设计多个文件,需要厘清每个步骤的操作内容以及作用,才能顺利的完成本次实验。
实验完成后应当尽可能将内核恢复到原始状态,便于日后的使用。