HDU操作系统课程设计实验二
这是一个比较简单、容易的实验,前提条件是对Linux内核的父子进程链表遍历、查找、访问等有一定的了解。注意:实验时可能因为模块代码有问题导致模块无法正常加载,被阻塞在hello_init()函数中,以至于模块无法被rmmod命令卸载。重启虚拟机,模块自动卸载。
一、设计目的
Linux提供的模块机制能动态扩充Linux功能而无需重新编译内核,已经广泛应用在Linux内核的许多功能的实现中。在本实验中将学习模块的基本概念、原理及实现技术,然后利用内核模块编程访问进程的基本信息,加深对进程概念的理解,掌握基本的模块编程技术。
二、内容要求
(1)设计一个模块,要求列出系统中所有内核线程的程序名、PID、进程状态、进程优先级、父进程的PID,输出按列对齐。
(2)设计一个带参数的模块,其参数为某个进程的PID号,模块的功能是列出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID号及进程状态,同时实现类似pstree的输出。
(3)请根据自身情况,进一步阅读分析程序中用到的相关内核函数的源码实现。
三、实验内容
实验思路
实验过程
- 编写模块代码和Makefile文件。
- 使用
make
命令编译模块代码。 - 使用
insmod
或modprobe
命令将需要载入的模块以目标代码的形式加载到内核中,将自动调用init_module宏。 - 使用
lsmod
命令查看已载入系统的模块信息。 - 使用
modinfo
命令查看指定的模块信息。 - 使用
rmmod
命令删除指定的模块。
输出按列对齐输出系统中所有内核线程的程序名、PID、进程状态、进程优先级、父进程的PID
Linux系统中的每个进程都有一个父进程(init进程除外);每个进程还有0个或多个子进程。在进程描述符中parent指针指向其父进程,还有一个名为children的子进程链表(父进程task_struct中的children相当于链表的表头)。
通过for_each_process(task_struct)函数遍历内核进程对齐输出即可。
参考代码(不完整):
static int myallkt_init(void)
{
struct task_struct *p;
p = NULL;
printk(KERN_ALERT"myallkt begin\n%*s程序名%*s进程号%*s进程状态%*s进程优先级%*s父进程进程号\n",14," ",0," ",0," ",0," ",0," ");
for_each_process(p)
{
if(p->mm == NULL)
{
printk(KERN_ALERT"%20s %6d %8ld %10d %12d\n",p->comm,p->pid,p->state,p->prio,p->parent->pid);
}
}
printk(KERN_ALERT"\n");
return 0;
}
参数为某个进程的PID号,类似pstree的输出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID号及进程状态
通过list_for_each(list_head*,task_struct*)函数遍历,task_struct中的children指针指向其某个子进程的进程描述符task_struct中children的地址而非直接指向某个子进程的地址,也就是说子进程链表中存放的仅仅是各个task_struct成员children的地址。
我们可以用list_entry(ptr,type,member)这个宏来获取task_struct成员的地址,ptr是指向该数据中list_head成员的指针,type是节点的类型,member是节点类型中list_head成员的变量名。
然后按照树状打印即可。
安装模块时传入参数:
module_param(pid, int ,0644); // module_param()宏来修饰要传入的参数变量
insmod 模块名.ko 变量名=值 // 加载模块时出入参数的命令
参考代码(不完整):
void show_it_children(struct task_struct *p,char fout1[100],int fl,int nps)
{
struct task_struct *pchildren[500];
struct list_head *L;
int i = 0,npc = 0,ml = 0;
char out[100];
char fout2[100];
list_for_each(L,&p->children){
pchildren[npc++]=list_entry(L,struct task_struct,sibling);
}
//输出当前进程信息
sprintf(out,"─%s(pid:%d,state:%ld)",p->comm,p->pid,p->state);
ml = strlen(out) - 1;
if(npc)
{
if(npc != 1)
sprintf(fout2,"%s%s─┬─",fout1,out);
else
sprintf(fout2,"%s%s───",fout1,out);
}
else
{
printk("%s%s\n",fout1,out);
return ;
}
//输出子进程信息
if(nps - 1 > 0)
sprintf(fout1,"%*s│%*s",fl,"",ml,"");
else
sprintf(fout1,"%*s",fl + ml + 2,"");
for(i = 0;i < npc;i++)
{
sprintf(out,"%s(pid:%d,state:%ld)",pchildren[i]->comm,pchildren[i]->pid,pchildren[i]->state);
if(i)
{
if(i != npc - 1)
printk("%s├─%s\n",fout1,out);
else
printk("%s└─%s\n",fout1,out);
}
else
{
printk("%s%s\n",fout2,out);
}
}
}
static int mypetree_init(void)
{
struct task_struct *p;
struct task_struct *psibling[100];
struct list_head *L;
int i = 0,nps = 0,fl = 0,tps = 0;
char out[100];
char fout1[100];
p=pid_task(find_vpid(pid),PIDTYPE_PID);
list_for_each(L,&p->parent->children){
psibling[nps++]=list_entry(L,struct task_struct,sibling);
}
//输出父进程信息
if(p->parent==NULL)
sprintf(out,"无父进程─");
else
sprintf(out,"%s(pid:%d,state:%ld)─",p->parent->comm,p->parent->pid,p->parent->state);
fl = strlen(out) - 2;
if(nps)
sprintf(fout1,"%s┬",out);
else
sprintf(fout1,"%s─",out);
show_it_children(p,fout1,fl,nps);
tps = nps - 1;
//输出兄弟进程信息
for(i = 0;i < nps;i++)
{
if(psibling[i] != p)
{
--tps;
if(tps)
sprintf(out,"%*s├─",fl,"");
else
sprintf(out,"%*s└─",fl,"");
show_it_children(psibling[i],out,fl,tps);
}
}
return 0;
}
四、实验核心代码
allkt.c:按列对齐输出系统中所有内核线程的程序名、PID、进程状态、进程优先级、父进程的PID。
Makefile1:allkt.c的Makefile文件。
pstree.c:设计一个带参数的模块,其参数为某个进程的PID号,模块的功能是类似pstree的输出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID号及进程状态。
Makefile2:pstree.c的Makefile文件。
完整实验代码详见:HDU-operation-system-course-design-code/实验二/