通过打印进程控制块中的信息深刻认识进程:
一、通过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
三、代码中涉及到的源码分析:
更新中…