根据分析,我们采用加载内核模块的方式来实现题目要求。那么程序设计思路就很清晰了,其主要步骤包括:
内核模块代码有自己的程序框架,包括需要的若干头文件,模块入口函数和模块退出函数,GPL许可下引入识别代码宏。
根据题目要求,我们需要对当前系统中的进程进行遍历,并且按照父子关系进行树状结构输出结果。这一部分属于逻辑代码,我们可以实现一个函数processtree(),然后加入到内核模块代码中。通过修改原系统调用地址,来执行我们自定义的函数,从而实现该功能。
编译好内核模块后,加载到系统中。我们需要知道该项系统调用是否成功。编写测试程序,调用该系统调用,打印返回结果查看。涉及问题是内核态数据和用户态数据的交换
内核模块程序包含的头文件包括:
其中,kernel.h包含了内核提供的一些基本的函数接口,包括内核打印函数printk(),它类似于我们在用户态下的printf()函数。
模块入口函数为init_addsyscall(),由module_init()宏指定,在模块被加载的时候被调用向系统注册。入口函数的返回值:0表示成功,非0表示失败。
模块的退出函数为exit_addsyscall(),由module_exit()宏指定,在模块被卸载时被调用向系统注销,主要来完成资源的清理工作。它被调用完毕后,就模块就被内核清除了。一个模块最少需要有入口和退出函数。
最后结果需要以树状形式展示所有进程的父子关系。我们定义processtree()递归函数来访问遍历,并且将结果存储在数组中,以便提供给用户态访问。
我们在sys_mycall()中,从当前进程开始,递归调用processtree()函数,将进程信息存储在数组中。然后利用copy_to_user函数将内核信息传递给用户态下,用户态下的测试程序对结果进行展示。
测试程序中利用syscall(num,para1,para2…)来进行系统调用,num表示该系统调用在系统调用跳转表中的号码。本程序中我们选用223号,在内核中将223本来对应的系统调用,临时链到我们自定义的sys_mycall()中。通过该系统调用后获得数组a,然后将其以树状结构打印出来。
系统可运行的最大进程数,通过ulimit –u 查看有7896个。
我们通过ps –ef|wc –l命令实际查看当前运行进程数量为178个。 于是,我们定的存储进程信息的数组大小为512是够用的。加载内核模块命令:insmod xxx.ko
说明:因结果较长,截取部分页面展示出来。
#include
#include
#include
#include
#include
#include
#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");
#include
#include
#include
#include
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;
}
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