Linux学习之打印进程树
前言继续Linux的学习,操作系统学到了Linux系统下的进程结构,布置了一个作业是打印进程树,来加深一下对Linux进程的理解。虚拟主机主机:联想Y7000P;64位windows10;CPU:i7-9750H;显卡:GTX 1660 Ti;内存:16G虚拟机:Ubuntu 18.04 LTS;硬盘100G;内存4G;64位;4核心Linux内核:5.11.8本博客原创,转载请注明!!!基础知识
前言
继续Linux的学习,操作系统学到了Linux系统下的进程结构,布置了一个作业是打印进程树,来加深一下对Linux进程的理解。
虚拟主机
主机:联想Y7000P;64位windows10;CPU:i7-9750H;显卡:GTX 1660 Ti;内存:16G
虚拟机:Ubuntu 18.04 LTS;硬盘100G;内存4G;64位;4核心
Linux内核:5.11.8
本博客原创,转载请注明!!!
基础知识补充:
问题解决需要分析两个问题:
- 如何得到系统进程树1号进程信息
- 如何由1号进程开始获取所有进程关联关系
指导书上,给了2种方法解决:
- 访问/proc目录:用户空间编程
- 访问task_struct结构:内核空间编程
接下来分别从两个进行实现
访问/proc目录
有关/proc文件可以看一下这个
参考资料:linux proc目录详解
源码参考:
系统进程树实验
指导书源码
把指导书的源码copy一下:
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netdb.h>
#include<pthread.h>
#include<unistd.h>
#include<dirent.h>
char default_path[1024]="/proc/";
int s=0;
typedef struct file_info
{
int pid; // 进程号
int ppid; // 父进程号
char name[1024]; // 进程名称
int flag; //进程标志
int rec; //打印进程树时用来标志是几级进程的
}info;
int my_getpid(char *str) // 获得进程号
{
int len=strlen(str);
char num[10];
int i,j,ret;
if(strncmp(str,"Pid",3)==0)
{
for(i=0;i<len;i++)
{
if(str[i]>='0'&&str[i]<='9')
break;
}
for(j=0;j<len-i;j++)
{
num[j]=str[i+j];
}
ret=atoi(num);
}
else ret=0;
return ret;
}
int my_getppid(char *str) // 获得父进程号
{
int len=strlen(str);
char num[10];
int i,j,ret;
if(strncmp(str,"PPid",4)==0)
{
for(i=0;i<len;i++)
{
if(str[i]>='0'&&str[i]<='9')
break;
}
for(j=0;j<len-i;j++)
{
num[j]=str[i+j];
}
ret=atoi(num);
}
else ret=0;
return ret;
}
int child_exist(info *file,int count,int ppid) //判断是否存在子进程
{
int i;
for(i=0;i<count;i++)
{
if(file[i].flag==0&&file[i].ppid==ppid)
return 1;
}
return 0;
}
void print_pstree(info *file,int count,int ppid,int rec) // 打印进程树,用递归方法,中序遍历
{
int i,j,k;
for(i=0;i<count;i++)
{
if(file[i].flag==0&&file[i].ppid==ppid)
{
file[i].rec=rec+1;
file[i].flag=1;
for(k=0;k<rec;k++)
printf(" ");
printf("%s\n",file[i].name);
print_pstree(file,count,file[i].pid,file[i].rec);
}
}
}
int main()
{
int i,j,k,total,s1,s2,count,t;
char str[1024],dir[1024];
struct dirent **namelist;
strcpy(dir,default_path);
total=scandir(dir,&namelist,0,alphasort);
printf("path=%s,total=%d\n",dir,total);
for(i=0;i<total;i++)
{
strcpy(str,namelist[i]->d_name);
if(str[0]>='0'&&str[0]<='9')
count++;
}
printf("进程数:%d\n",count);
info file[1024];
i=0;
t=0;
while(i<total)
{
FILE *fp;
char path[1024],name[1024];
int pid,ppid;
strcpy(str,namelist[i]->d_name);
strcpy(path,default_path);
if(str[0]>='0'&&str[0]<='9')
{
strcat(path,str);
strcat(path,"/status");
fp=fopen(path,"r");
while(!feof(fp))
{
fgets(str,1024,fp);
//pid
if((s1=my_getpid(str))!=0)
pid=s1;
//ppid
if((s2=my_getppid(str))!=0)
ppid=s2;
//name
if(strncmp(str,"Name",4)==0)
{
for(j=4;j<strlen(str);j++)
{
if(str[j]>='a'&&str[j]<='z')
break;
}
for(k=j;k<strlen(str);k++)
{
name[k-j]=str[k];
}
name[k-j-1]='\0';
}
file[t].pid=pid;
file[t].ppid=ppid;
strcpy(file[t].name,name);
}
fclose(fp);
t++;
}
i++;
}
memset(&file->flag,0,count);
memset(&file->rec,0,count);
print_pstree(file,count,0,0);
}
这上边是指导书的源码,大概看了一下。讲解一下思路:
整体思路就是一直遍历/proc目录下的进程文件中的status文件,然后暴力获取获取ppid。
二次开发
根据它的思路,二次开发一下,此代码原创,转载请注明!,如有bug,请告知,谢谢。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h> //Windows中没有这个头文件
#include <unistd.h>
#define MAX_PROC_NUM 1024
#define MAX_PROC_NAME_LEN 254
#define ROOT_FILE "/proc"
struct procInfo
{
char name[MAX_PROC_NAME_LEN]; //进程的名字
int pid; //进程id
int ppid; //进程的父进程
int floor; //递归的层次
}procs[MAX_PROC_NUM];
int procNum = 0;
int max_floor = 0;
//int atoi(const char *nptr); 把str变成int
void getProcStatus(const char* str,struct procInfo *proc)
{
FILE *fp; //获取状态信息
char t_title[MAX_PROC_NAME_LEN];
char t_info[MAX_PROC_NAME_LEN];
fp = fopen(str,"r");
if(fp == NULL)
{
strcpy(proc->name,"NULL");
proc->ppid = -1;
proc->floor = -1;
}
else
{
while( fscanf(fp,"%s",t_title) != EOF )
{
if(strncmp(t_title,"Name:",5) == 0)
{
fscanf(fp,"%s",proc->name);
}
else if(strncmp(t_title,"PPid:",5) == 0)
{
fscanf(fp,"%s",t_info);
proc->ppid = atoi(t_info);
}
}
}
fclose(fp);
}
void readDirInfo(const char *str)
{
DIR *dir;
struct dirent *ptr;
int tmpLength=0;
dir = opendir(str); //打开一个目录
char procStatusString[64];
//开始获取当前有多少个进程
while( (ptr = readdir(dir)) != NULL )
{
tmpLength = strlen((ptr->d_name));
int i=0;
for(i=0;i<tmpLength;i++)
{
if((ptr->d_name)[i] <'0' || (ptr->d_name)[i] > '9' )
break;
}
if(i == tmpLength)
{
procs[procNum].pid = atoi(ptr->d_name);
sprintf(procStatusString,"%s/%d/status",ROOT_FILE,procs[procNum].pid);
//开始获取Status中的信息
getProcStatus(procStatusString,&(procs[procNum]));
procNum++;
}
if(procNum >= MAX_PROC_NUM)
break;
}
closedir(dir);
}
void GetProcTree(int pid,int step)
{
for(int i=0;i<procNum;i++)
{
if(procs[i].ppid == pid)
{
procs[i].floor = step;
for(int j=0;j<step;j++)
printf(" ");
printf("%s\n",procs[i].name);
GetProcTree(procs[i].pid,step+1);
}
}
}
int main()
{
readDirInfo(ROOT_FILE);
GetProcTree(0,0);
printf("procNum = %d\n",procNum);
return 0;
}
我一共定义了3个子函数,其中void GetProcTree(int pid,int step)
是递归用来打印进程树的,而void readDirInfo(const char *str)
函数是用来获取/proc
目录下的文件夹信息的,void getProcStatus(const char* str,struct procInfo *proc)
是读取每个文件夹中的Status
文件的。
执行完readDirInfo(ROOT_FILE);
这句,就获取了系统目录/proc
下的所有Status
信息,然后用GetProcTree()
函数来递归打印进程树。
访问PCB结构方案
task_struct结构
在Linux系统下,每一个进程都有一个task_struct结构体,包括进程之间的族系成员关系
pid_t pid;
struct task_struct* parent;
struct list_head children;
struct list_head sibling;
char comm[16]; ///进程名称
大体思路:
先找到根进程for(cur=current; cur->pid!=1; cur = cur->parent)
;然后用深度优先算法(BFS)递归打印子进程。结束的标志:子进程pid=0
;
指导书源码
直接看源码:
myPsTree.c
文件
#include <linux/input.h>
#include <linux/sched.h>
#include <linux/unistd.h>
#include <linux/list.h>
#include <linux/init.h>
#include <linux/module.h>//任何模块程序的编写都需要包含linux/module.h这个头文件
#include <linux/kernel.h>
MODULE_LICENSE("Dual BSD/GPL");//
void pstreea(struct task_struct* p,int b){
int i;
for(i=1;i<=b;i++)
printk(" ");
printk("|--%s\n",p->comm);
struct list_head* l;
for (l = p->children.next; l!= &(p->children);l = l->next){
//作用同list_for_each()
struct task_struct*t=list_entry(l,struct task_struct,sibling);//将children链上的某一节点作为sibling赋给task_struct即
pstreea(t,b+1); //实现了向children节点的移动
}
}
static int pstree_init(void){
struct task_struct* p;
int b=0;
for ( p = current; p != &init_task; p = p->parent ) ;//回溯到初始父进程
pstreea(p,b);
return 0;
}
static void pstree_exit(void){
printk("Hello, kernel!/n"); //注意在这儿使用的是printk()函数(不要习惯性地写成printf),printk()函数是由Linux内核定义的,功能与printf相似。字符串<1>表示消息的优先级,printk()的一个特点就是它对于不同优先级的消息进行不同的处理,之所以要在这儿使用高优先级,是因为默认优先级的消息可能不能显示在控制台上。这个问题就不详细讲了,可以用man命令寻求帮助。
}
module_init(pstree_init);
module_exit(pstree_exit);//函数init ()和函数exit ( )是模块编程中最基本的也是必须的两个函数。
//init ()向内核注册模块所提供的新功能;
//exit ()负责注销所有由模块注册的功能。
Makefile
文件
obj-m:=myPsTree.o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
然后安装过程和之前一样
编译 make
安装模块 sudo insmod pstree.ko
运行模块 sudo dmesg
删除模块 sudo rmmod pstree
内核模块修改与添加比较复杂,不再开发。
基于JavaFX实现进程树
你以为结束了嘛,不!还没有,接下来用Java和JavaFx实现一个GUI版的进程树,思路源自方法一,只贴源码,不再解释。
本代码原创,转载请注明!
基于JavaFX的Linux进程树
总结
思路简单,递归比较麻烦一些,学到了很多东西,收益匪浅。=w=
更多推荐
所有评论(0)