通过打印进程控制块中的信息深刻认识进程:
一、通过top查看进程信息:
通过在终端输入top命令可查看动态的进程信息,查看结果截图如下所示:

进阶版:
使用man指令可更方便高效;
二、探索并打印task_struct字段中信息:
2.1、打印task_struct字段(初试):
在task_struct字段中包含着与进程相关的大部分信息,该字段被定义在linux/sched.h文件中的721行,在通过阅读csdn中相关文章后,打算打印若干个task_struct字段中的部分信息,代码如下:
内核模块代码
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h> //task结构体
#include <linux/fdtable.h> //files
#include <linux/fs_struct.h> //fs
#include <linux/mm_types.h> //打印内存信息
#include <linux/init_task.h>
#include <linux/types.h>
#include <linux/atomic.h>
#include "policy.h"
#include "state.h"
MODULE_LICENSE("GPL"); //许可证
//入口函数
static int __init print_pcb(void) //init宏是由init.h文件所支持
{
struct task_struct *task,*p;
struct list_head *pos; //双向链表
int count=0; //统计当前系统进程一共有多少个
printk("\n-----------------------------------------\n打印个进程信息瞅瞅捏!\n\n");
//对链表遍历时,希望从第一个开始
task=&init_task; //指向0号进程pcb
//遍历操作,使用pos指向,传入的参数task指向tasks字段.0号进程的tasks进行遍历。tasks将所有的进程连在一块。
list_for_each(pos,&task->tasks)
{
p=list_entry(pos,struct task_struct,tasks);
//找到一个节点,就可以用这个节点的tasks字段,找到这个结构体的地址.对应的字段tasks
//此时的p指针已经指向task_struct结构体的首部,后面就可以通过p指针进行操作
count++; //找到一个进程,自加
printk("\n\n");
printk("pid号: %d\t进程名:%s\t状态: %s\t动态优先级: %d\t静态优先级: %d\t
实时优先级: %d\t父进程pid: %d\tCPU: %d\t调度策略: %s\n ",
p->pid,p->comm,tran_state(p->__state), p->prio,p->static_prio,p->rt_priority, (p->parent)->pid,task_cpu(p),tran_policy(p->policy));
}
printk("进程的个数:%d\n",count);
return 0;
}
static void __exit exit_pcb(void) //出口函数
{
printk("俺溜咧~~\n");
}
// 指明入口点与出口点,入口/出口点是由module.h支持的
module_init(print_pcb);
module_exit(exit_pcb);
通过tran_policy()函数将p->policy输出的unsigned int型参数转为具体的宏,便于观看信息;
/*
* Scheduling policies
*/
/*
* #define SCHED_NORMAL 0
* #define SCHED_FIFO 1
* #define SCHED_RR 2
* #define SCHED_BATCH 3
*/
/* SCHED_ISO: reserved but not implemented yet */
/*
* #define SCHED_IDLE 5
*/
/* Can be ORed in to make sure the process is reverted back to SCHED_NORMAL on fork */
/*
* #define SCHED_RESET_ON_FORK 0x40000000
*/
char *tran_policy(unsigned int policy)
{
char *cp;
switch (policy) {
case 0:
cp = "SCHED_NORMAL";
break;
case 1:
cp = "SCHED_FIFO";
break;
case 2:
cp = "SCHED_RR ";
break;
case 3:
cp = "SCHED_BATCH";
break;
case 5:
cp = "SCHED_IDLE";
break;
case 0x40000000:
cp = "SCHED_RESET_ON_FORK";
break;
default:
cp = "unknown";
break;
}
return cp;
}
通过tran_state()函数将p->__state输出的volatile long型参数转为具体的宏,便于观看信息;
/*
* #define TASK_RUNNING 0
* #define TASK_INTERRUPTIBLE 1
* #define TASK_UNINTERRUPTIBLE 2
* #define __TASK_STOPPED 4
* #define __TASK_TRACED 8
*/
/* in tsk->exit_state */
/*
* #define EXIT_ZOMBIE 16
* #define EXIT_DEAD 32
*/
/* in tsk->state again */
/*
* #define TASK_DEAD 64
* #define TASK_WAKEKILL 128
* #define TASK_WAKING 256
* #define TASK_PARKED 512
*/
char *tran_state(volatile long state)
{
char *s = NULL;
switch (state) {
case 0:
s = "TASK_RUNNING ";
break;
case 1:
s = "TASK_INTERRUPTIBLE";
break;
case 2:
s = "TASK_UNINTERRUPTIBLE";
break;
case 4:
s = "__TASK_STOPPED";
break;
case 8:
s = "__TASK_TRACED";
break;
case 16:
s = "EXIT_ZOMBIE";
break;
case 32:
s = "EXIT_DEAD";
break;
case 64:
s = "TASK_DEAD";
break;
case 128:
s = "TASK_WAKEKILL";
break;
case 256:
s = "TASK_WAKING";
break;
case 512:
s = "TASK_PARKED";
break;
default:
s = "unknown ";
break;
}
return s;
}
此处为Makefile文件:
obj-m:=pr_task_info.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL:=$(shell uname -r)
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
输出打印结果:


2.2、打印task_struct字段(再试,加入传参功能):
为了实现打印指定进程的指定信息,我在2.1中代码的基础上做了升级,可以通过传递模块参数processnunm及flag;其中processnum是特定进程的pid号,flag是自己设置的标志位,0代表打印全部进程信息,1代表打印指定进程的所有子进程的部分信息,2代表打印指定进程的所有兄弟进程的部分信息
2.2.1、task_struct 中 parent/children/sibling 三者的关系:
在打印特定进程的子进程信息中,发现需要用到sibling链表,所以对 parent/children/sibling 三者的关系产生了疑惑。在《深入理解Linux内核》中有如下一图表明 task_struct 中 parent/children/sibling 三者的关系:

- sibling.next指向进程的下一个兄弟进程的进程描述符sibling成员,若其后没有其他兄弟进程,则指向父进程;而sibling.prev指向进程的上一个兄弟进程,若其之前没有兄弟进程,则指向父进程。
- children.next指向父进程的第一个子进程的sibling成员(而不是children成员!),而children.prev却指向父进程的最后一个子进程的sibling成员。
- 特别注意children.next指向的是sibling成员,因此在使用list_entry()获得task_struct指针时,参数要用sibling而不是children,更不是tasks成员。
所以在下面的代码中是:
list_for_each(pos,&p->children)
{
tmp=list_entry(pos,struct task_struct,sibling);
count++;
printk("pid号: %ld\t进程名:%s\t状态: %s\t动态优先级: %ld\t静态优先级: %ld\t实时优先级: %ld\t父进程pid: %ld\tCPU: %ld\t调度策略: %s\n",tmp->pid,tmp->comm,tran_state(tmp->__state),tmp->prio,tmp->static_prio,tmp->rt_priority,(tmp->parent)->pid,task_cpu(tmp),tran_policy(tmp->policy));
}
2.2.2、代码:
如下是功能增加后的代码部分,其中state.h及policy.h和2.1中一致,此处不做展示
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/sched.h> //task结构体
#include <linux/fdtable.h> //files
#include <linux/fs_struct.h> //fs
#include <linux/mm_types.h> //打印内存信息
#include <linux/init_task.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/atomic.h>
#include "policy.h"
#include "state.h"
MODULE_LICENSE("GPL"); //许可证
static int processnum = 156;
module_param(processnum, int, 0644);
MODULE_PARM_DESC(processnum, "give a pid num to print infomation of it's children/sibling!");
static int flag = 0;
module_param(flag, int, 0644);
MODULE_PARM_DESC(flag, "1 means children and 2 means sibling ");
void print(struct task_struct *p )
{
printk("pid号: %d\t进程名:%s\t\t状态: %s\t动态优先级: %d\t静态优先级: %d\t实时优先级: %d\t\t父进程pid: %d\tCPU: %d\t调度策略: %s\n",
p->pid,p->comm,tran_state(p->__state),p->prio,p->static_prio,p->rt_priority,(p->parent)->pid,task_cpu(p),tran_policy(p->policy));
}
//入口函数
static int __init print_pcb(void) //init宏是由init.h文件所支持
{
struct task_struct *task,*p,*tmp,*q;
struct list_head *pos;
//struct task_struct *p = pid_task(find_vpid(processnum),PIDTYPE_PID);//通过find_task_by_vpid(函数找到pid对应进程的task_struct结构体)
int count =0;
printk("\n-----------------------------------------\n打印个进程信息瞅瞅捏!\n\n");
if(flag==0){
task=&init_task; //指向0号进程pcb
printk("全部进程信息:\n");
list_for_each(pos,&task->tasks)
{
p=list_entry(pos,struct task_struct,tasks);
//找到一个节点,就可以用这个节点的tasks字段,找到这个结构体的地址.对应的字段tasks
//此时的p指针已经指向task_struct结构体的首部,后面就可以通过p指针进行操作
count++; //找到一个进程,自加
print(p);
}
printk("进程的个数:%d\n",count);
return 0;
}
p=pid_task(find_get_pid(processnum),PIDTYPE_PID);//找到processnum号对应的task_struct结构体;
print(p);
if(flag==1){
printk("%d进程%s的全部子进程信息:\n",processnum,p->comm);
list_for_each(pos,&p->children)
{
tmp=list_entry(pos,struct task_struct,sibling);
//找到一个节点,就可以用这个节点的tasks字段,找到这个结构体的地址.对应的字段tasks
//此时的p指针已经指向task_struct结构体的首部,后面就可以通过p指针进行操作
count++; //找到一个进程,自加
print(tmp);
}
printk("进程%s子进程个数:%d\n",p->comm,count);
}
else if(flag==2){
printk("%d进程%s的全部兄弟进程信息:\n",processnum,p->comm);
q=p;
p=p->parent;
list_for_each(pos,&p->children)
{
tmp=list_entry(pos,struct task_struct,sibling);
//找到一个节点,就可以用这个节点的tasks字段,找到这个结构体的地址.对应的字段tasks
//此时的p指针已经指向task_struct结构体的首部,后面就可以通过p指针进行操作
count++; //找到一个进程,自加
print(tmp);
}
printk("进程%s的兄弟进程个数:%d\n",q->comm,count);
}
return 0;
}
static void __exit exit_pcb(void) //出口函数
{
printk("俺溜咧~~\n");
}
// 指明入口点与出口点,入口/出口点是由module.h支持的
module_init(print_pcb);
module_exit(exit_pcb);
2.2.2、调试部分:
将写好的代码文件make编译

在插入模块之前,首先要找到要打印进程的pid号用来传参。为了方便展示,先通过pstree命令来找到有子进程或有兄弟进程的进程,再通过top命令和管道工具找到该进程的进程号。调试部分如下:


找到两个进程systemed、vmware-vmblock-的进程树及其对应的进程号:

插入模块,并传参:
- flag置为0,打印所有进程信息:
xhb@xhb-virtual-machine:~/mycode/test8$ sudo insmod pr_task_info.ko flag=0


- 打印systemed的子进程相关信息:
xhb@xhb-virtual-machine:~/mycode/test8$ sudo insmod pr_task_info.ko processnum=1 flag=1


- 在打印systemed子进程中我们看到其中一个pid号为485的进程vmware-vmblock-,打印该进程的兄弟进程相关信息:
xhb@xhb-virtual-machine:~/mycode/test8$ sudo insmod pr_task_info.ko processnum=485 flag=2


- 打印bash的子进程及sh的兄弟进程:
xhb@xhb-virtual-machine:~/mycode/test8$ sudo insmod pr_task_info.ko processnum=1901 flag=1
xhb@xhb-virtual-machine:~/mycode/test8$ sudo insmod pr_task_info.ko processnum=1946 flag=2



三、代码中涉及到的源码分析:
更新中…
文章介绍了如何通过编写内核模块,打印Linux进程的task_struct字段信息,包括进程ID、名称、状态等,并探讨了parent/children/sibling关系。作者还展示了如何通过模块参数指定打印特定进程及其子进程或兄弟进程的信息。
9220






