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

本博客原创,转载请注明!!!

基础知识补充:
问题解决需要分析两个问题:

  1. 如何得到系统进程树1号进程信息
  2. 如何由1号进程开始获取所有进程关联关系

指导书上,给了2种方法解决:

  1. 访问/proc目录:用户空间编程
  2. 访问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=

  • 8
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值