加载内核模块,实现新的系统调用:遍历系统当前所有进程的任务描述符,并将pid组织成树状结构显示
2016-02-25 23:19
1281 查看
一、题目要求
在Linux内核中增加一个系统调用,并编写对应的linux应用程序。利用该系统调用能够遍历系统当前所有进程的任务描述符,并按进程父子关系将这些描述符所对应的进程id(PID)组织成树形结构显示。二、分析
系统调用是内核为用户进程提供服务的一种方式。通过系统调用,内核能够提供给用户模式下的进程和硬件设备的接口,保护对内核所管理的资源的访问,提高系统安全,提高程序的可移植性。系统调用的概念如图1所示。[align=center]图1 系统调用的概念
[/align]
题目中要求增加一项新的系统调用,可以实现对当前系统进程的遍历访问。其本质就是在内核中实现一个新的函数,调用该函数可以得到进程遍历的结果。增加系统调用的方式有两种:
其一,修改内核源码。我们在内核源码中,找到对应文件,增加新的系统调用编号,系统调用跳转表项和相应的例程。然后重新编译内核。利用编译好的内核重启系统,则该系统就支持我们新加的这项系统调用。
其二,加载内核模块。内核模块是一种没有经过链接,不能独立运行的目标文件,运行在内核空间中。经过链接装载到内核里面,成为内核的一部分,可以访问内核的公用符号,其概念如图2所示。我们可以设计一个内核模块,在其中实现程序逻辑,然后将其加载到内核中,这样也可实现在内核中增加新的系统调用。
图2 内核模块概念
显而易见的是,第二种方法符合模块化设计思想,根据需要动态使能/禁止模块,可以保证计算机资源。避免了第一种方法重新编译内核的麻烦,而且避免了使内核臃肿。我们下面程序实现通过加载内核模块的方式。
三、程序设计思路
根据分析,我们采用加载内核模块的方式来实现题目要求。那么程序设计思路就很清晰了,其主要步骤包括:1、内核模块的代码框架
内核模块代码有自己的程序框架,包括需要的若干头文件,模块入口函数和模块退出函数,GPL许可下引入识别代码宏。2、逻辑代码
根据题目要求,我们需要对当前系统中的进程进行遍历,并且按照父子关系进行树状结构输出结果。这一部分属于逻辑代码,我们可以实现一个函数processtree(),然后加入到内核模块代码中。通过修改原系统调用地址,来执行我们自定义的函数,从而实现该功能。3、用户测试程序
编译好内核模块后,加载到系统中。我们需要知道该项系统调用是否成功。编写测试程序,调用该系统调用,打印返回结果查看。涉及问题是内核态数据和用户态数据的交换四、程序设计
1、内核程序框架
内核模块程序包含的头文件包括:其中,kernel.h包含了内核提供的一些基本的函数接口,包括内核打印函数printk(),它类似于我们在用户态下的printf()函数。
模块入口函数为init_addsyscall(),由module_init()宏指定,在模块被加载的时候被调用向系统注册。入口函数的返回值:0表示成功,非0表示失败。
模块的退出函数为exit_addsyscall(),由module_exit()宏指定,在模块被卸载时被调用向系统注销,主要来完成资源的清理工作。它被调用完毕后,就模块就被内核清除了。一个模块最少需要有入口和退出函数。
MODULE_LICENSE(“GPL”)表示设置模块遵守GPL证书,取消警告信息。
2、逻辑代码
最后结果需要以树状形式展示所有进程的父子关系。我们定义processtree()递归函数来访问遍历,并且将结果存储在数组中,以便提供给用户态访问。我们在sys_mycall()中,从当前进程开始,递归调用processtree()函数,将进程信息存储在数组中。然后利用copy_to_user函数将内核信息传递给用户态下,用户态下的测试程序对结果进行展示。
3、测试程序
测试程序中利用syscall(num,para1,para2…)来进行系统调用,num表示该系统调用在系统调用跳转表中的号码。本程序中我们选用223号,在内核中将223本来对应的系统调用,临时链到我们自定义的sys_mycall()中。通过该系统调用后获得数组a,然后将其以树状结构打印出来。4、编写Makefile文件
五、需要考虑的两个问题
1、进程个数确定
系统可运行的最大进程数,通过ulimit –u 查看有7896个。我们通过ps –ef|wc –l命令实际查看当前运行进程数量为178个。
于是,我们定的存储进程信息的数组大小为512是够用的。
2、内核和用户态数据交换
我们在内核模块程序中,将进程遍历信息存储在数组中,然后需要将其传递给用户态下。采用copy_from_user()和copy_to_user()这两个函数,这两个函数负责在用户空间和内核空间传递数据。因此我们在测试程序中,将空数组a的地址作为参数传递给内核模块程序,在内核中使用copy_to_user()函数将内核中的数组信息传递给用户态下的地址。六、运行程序过程
1、将编译出来的内核模块hello.ko加载到内核中
加载内核模块命令:insmod xxx.ko2、通过dmesg查看输出信息是否正确
3、运行测试程序,输出树状打印结果
说明:因结果较长,截取部分页面展示出来。
4、卸载该模块
卸载内核模块命令:rmmod xxx
至此,工作就完成了。
七、附录:源代码
1、内核模块程序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 223 #define sys_call_table_address 0xc15b3000 static int counter = 0; struct process { int pid; int depth; }; struct process a[512]; unsigned int clear_and_return_cr0(void); void setback_cr0(unsigned int val); asmlinkage long sys_mycall(char __user *buf); int orig_cr0; unsigned long *sys_call_table = 0; static int (*anything_saved)(void); void processtree(struct task_struct * p,int b) { struct list_head * l; a[counter].pid = p -> pid; a[counter].depth = b; counter ++; for(l = p -> children.next; l != &(p->children); l = l->next) { struct task_struct *t = list_entry(l,struct task_struct,sibling); processtree(t,b+1); } } unsigned int clear_and_return_cr0(void) { unsigned int cr0 = 0; unsigned int ret; asm("movl %%cr0, %%eax":"=a"(cr0)); ret = cr0; cr0 &= 0xfffeffff; asm("movl %%eax, %%cr0"::"a"(cr0)); return ret; } void setback_cr0(unsigned int val)//读取val的值到eax寄存器,再将eax寄存器的值放入cr0中 { asm volatile("movl %%eax, %%cr0"::"a"(val)); } static int __init init_addsyscall(void) { printk("hello,lihuan kernel\n"); sys_call_table = (unsigned long *)sys_call_table_address;//获取系统调用服务首地址 printk("%x\n",sys_call_table); anything_saved = (int(*)(void)) (sys_call_table[my_syscall_num]);//保存原始系统调用的地址 orig_cr0 = clear_and_return_cr0();//设置cr0可更改 sys_call_table[my_syscall_num]= (unsigned long)&sys_mycall;//更改原始的系统调用服务地址 setback_cr0(orig_cr0);//设置为原始的只读cr0 return 0; } asmlinkage long sys_mycall(char __user * buf) { int b = 0; struct task_struct * p; printk("This is lihuan_syscall!\n"); /* if(num%2==0) {num=num%10000;} else {num=num%100000;} return num; */ /* for(i=0;i<20;i++) a[i]=15; if(copy_to_user(buf,a,20*sizeof(int))) return -EFAULT; else return sizeof(a); */ for(p = current; p != &init_task; p = p->parent ); processtree(p,b); if(copy_to_user((struct process *)buf,a,512*sizeof(struct process))) return -EFAULT; else return sizeof(a); } static void __exit exit_addsyscall(void) { //设置cr0中对sys_call_table的更改权限。 orig_cr0 = clear_and_return_cr0();//设置cr0可更改 //恢复原有的中断向量表中的函数指针的值。 sys_call_table[my_syscall_num]= (unsigned long)anything_saved; //恢复原有的cr0的值 setback_cr0(orig_cr0); printk("call lihuan exit \n"); } module_init(init_addsyscall); module_exit(exit_addsyscall); MODULE_LICENSE("GPL");
2、测试程序hello_test.c
#include <linux/unistd.h> #include <syscall.h> #include <sys/types.h> #include <stdio.h> struct process { int pid; int depth; }; struct process a[512]; int main() { int i,j; printf("the result is:%d\n",syscall(223,&a)); for(i = 0; i < 512; i++) { for(j = 0; j < a[i].depth; j++) printf("|-"); printf("%d\n",a[i].pid); if(a[i+1].pid == 0) break; } return 0; }
3、Makefile文件
KVERS = $(shell uname -r) # Kernel modules obj-m += hello.o # Specify flags for the module compilation. #EXTRA_CFLAGS=-g -O0 build: kernel_modules user_test kernel_modules: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules user_test: gcc -o hello_test hello_test.c clean: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
相关文章推荐
- UVA 725(p182)----Division
- 欢迎使用CSDN-markdown编辑器
- mysql中的trigger
- chart控件的简单使用
- C语言的整型和长整型的字节长
- 大龄恐惧症 (zz)
- Exynos4412裸机开发 —— RTC 实时时钟单元
- UVA 714(p244)----Copying Books
- MySQL知识(十二)——数据的插入、更新和删除
- 自定义ViewGroup——卫星式菜单的实现
- 关于论坛、博客、SNS三者之间的区别
- hdu 1551(二分)
- UESTC 360 Another LCIS 线段树
- UVA 712(p176)----S-Trees
- 【每日算法】哈希表(Hash Table)
- 几种TCP连接中出现RST的情况
- PL/SQL之复合类型详解
- 调查问卷
- 宏定义 button 方法 --备
- MFC绘制动态曲线,用双缓冲绘图技术防闪烁