目录

一.题目介绍

二.实验思路

三.内核模块代码

任务(1)代码

 任务(2)代码

四.遇到问题及解决方法

五.参考文献



一.题目介绍

(1)设计一个模块,要求列出系统中所有内核线程的程序名、PID、进程状态、进程优先级、父进程的PID。

(2)设计一个带参数的模块,其参数为某个进程的PID号,模块的功能是列出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID号、进程状态。

二.实验思路

图1.题目(2)代码分析流程图

        主要是通过find_vpid()来获取struct pid结构体,然后通过pid_task()函数获得当前的task_struct,以上步骤都是比较简单。增加的内容就是显示所查询进程的pid namespace的level,为了获取指定pid namespace空间的pid,我们还需要获取指定的pid_namespace *ns,然后通过内核函数pid_nr_ns()获取到指定ns的pid即可。值得注意的是,在不同的pid namespace中,可能会存在相同pid号的进程。

三.内核模块代码

任务(1)代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
//列出系统中所有内核线程的程序名、PID、进程状态、进程优先级、父进程的PID

static int md1_init(void)
{
	struct task_struct *p=NULL;
	struct task_struct *m=NULL;
	// p = &init_task;
	m=&init_task;
	p=next_task(next_task(m));
    printk(KERN_ALERT"--------------------------------------------------------------------------");//KERN_ALERT提升输出信息的优先级
	struct list_head *child;
	list_for_each(child, &p->children)
	{
		m = list_entry(child, struct task_struct, sibling);
		printk(KERN_ALERT"name:%-16s pid:%-5d state:%-5ld prio:%-10d ppid:%-5d", m->comm, m->pid, m->state, m->normal_prio, m->parent->pid);
		printk(KERN_ALERT"--------------------------------------------------------------------------");
	}
	return 0;
}
static void md1_exit(void)
{
	printk(KERN_ALERT"md1_exit!n");
}
 
module_init(md1_init);
module_exit(md1_exit);
MODULE_LICENSE("GPL");

/*
#define for_each_process(p) \
	for (p = &init_task ; (p = next_task(p)) != &init_task ; )
    */

 任务(2)代码

#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/sched.h>
#include<linux/moduleparam.h>
#include <linux/pid_namespace.h>
static int pid=1;
module_param(pid,int,S_IRUGO);
//包括父进程、兄弟进程和子进程的程序名、PID号、进程状态
//init
//1026表示1024+2,其中1024是系统无可运行进程的状态
static int md2_init(void)
{
	struct task_struct *p=NULL;
	struct task_struct *m=NULL;
	struct pid_namespace *ns=NULL;
    int level=-1;
	int crt_pid=-1;
	//pid_task
    //find_vpid
	p = pid_task(find_vpid(pid), PIDTYPE_PID);
	if (likely(pid_alive(p))) 
    {    
        //get tgid
		ns=task_active_pid_ns(p);
		level=ns->level;                  
        p = p->group_leader;
        //pid_nr_ns得到的是指定ns的pid                                                                                                          
        crt_pid = (int)pid_nr_ns((p->pids[PIDTYPE_PID].pid), ns);\
		printk(KERN_ALERT"self-name   :%-20s  0-level-pid:%-7d  state:%-8ld  level:%-5d  this-level-pid:%-7d"
		,p->comm, p->pid, p->state,level,crt_pid);
    }
	int pid=crt_pid;
	//parent
	if(p->parent != NULL) {
        ns=task_active_pid_ns(p->parent);
		level=ns->level;
        //pid_nr_ns得到的是指定ns的pid                                                                                                          
        crt_pid = (int)pid_nr_ns((p->parent->pids[PIDTYPE_PID].pid), ns);     
        printk(KERN_ALERT"parent-name :%-20s  0-level-pid:%-7d  state:%-8ld  level:%-5d  this-level-pid:%-7d",p->parent->comm, p->parent->pid, 
		p->parent->state,level,crt_pid);
		
        //brother          
        struct list_head *brother;
        // #define list_for_each(pos, head) \
        // for (pos = (head)->next; pos != (head); pos = pos->next)
		list_for_each(brother, &p->parent->children)
		{                             
			m = list_entry(brother, struct task_struct, sibling);//sibling
            //children指向的下一个为sibling,而不是children
			if(m->pid!=pid) //避免重复输出self_task
				printk(KERN_ALERT"brother-name:%-20s  pid:%-7d  state:%-8ld",m->comm, m->pid, m->state);
		}
	}
	else {
        printk(KERN_ALERT"No Parent\n");
        printk(KERN_ALERT"No brother\n");
	}

	//children
    struct list_head *child;
	list_for_each(child, &p->children)
	{
		m = list_entry(child, struct task_struct, sibling);
		ns=task_active_pid_ns(m);
		level=ns->level;
		crt_pid = (int)pid_nr_ns((m->pids[PIDTYPE_PID].pid), ns);  
		printk(KERN_ALERT"child-name :%-20s  0-level-pid:%-7d  state:%-8ld  level:%-5d  this-level-pid:%-7d",m->comm, m->pid, m->state,
		level,crt_pid);
	}
 
	return 0;
}

//exit 
static void md2_exit(void)
{
    printk(KERN_ALERT"md2_exit!\n");
}

module_init(md2_init);
module_exit(md2_exit);
MODULE_LICENSE("GPL");

四.遇到问题及解决方法

1.在使用list_entry(brother, struct task_struct, sibling)函数遍历兄弟进程时,也就是遍历父进程的children,一开始第三个参数使用成了children导致运行错误,因为children指向的下一个为sibling,而不是children。

2.在新的pid namespace空间去访问pid=2进程来遍历其子进程获得内核线程的时候,发现结果不对,因为此时空间的pid中,pid=2并不是kthreadd进程。

3.一开始遍历内核进程是使用task_struct mm==NULL来判断是否为内核进程,但是经过对linux 0,1,2号进程了解过后,发现2号进程是所有内核进程的父进程,所以相比之下,直接对2号进程的子进程进行遍历会比遍历所有的进程来判断mm==NULL效率更高。但后面发现这种方法的缺陷是不一定能在新的pid namespace使用,因为此时的pid namespace 2号进程并非kthreadd,可以改用一种方法 ,直接向&init_task往后遍历两次,就能得到kthreadd进程的地址,这样虽然经历了两次遍历,但也只用遍历kthreadd的子进程, 效率也依然很高。

五.参考文献

1. Linux中的RCU机制[一] - 原理与使用方法[EB/OL]. []. https://zhuanlan.zhihu.com/p/89439043.

2. Linux内核中的RCU[EB/OL]. []. https://zhuanlan.zhihu.com/p/67520807.

3. rcu 机制简介[EB/OL]. []. https://zhuanlan.zhihu.com/p/113999842.

4. linux内核命名空间[EB/OL]. []. https://zhuanlan.zhihu.com/p/136404837.

5. pid namespace详细解读[EB/OL]. []. https://tinylab.org/pid-namespace/.

6. 浅谈current宏[EB/OL]. []. https://www.cnblogs.com/crybaby/p/14082593.html.

7. Linux源码[EB/OL]. []. Linux source code (v4.16.3) - Bootlin.

8. list_entry详解[EB/OL]. []. linux源代码中的容器:list_entry_51CTO博客_linux 源代码.