NJUOS:M1 pstree

http://jyywiki.cn/OS/2022/labs/M1

Keep it simple, stupid && Everything is a file.

可以用strace看一下pstree的系统调用,找下思路

找到进程关系

proc 文件系统 (procfs) 是类 Unix 操作系统中的一种特殊文件系统,它以分层文件结构呈现有关进程的信息和其他系统信息,为动态访问内核中保存的进程数据提供了一种更方便和标准化的方法。

dirent.h头文件里有读取文件夹的func https://man7.org/linux/man-pages/man0/dirent.h.0p.html

目录操作函数opendir、readdir和closedir

#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
打开一个与给定的目录名name相对应的目录流,并返回一个指向该目录流的指针。打开后,该目录流指向了目录中的第一个目录项。
struct dirent *readdir(DIR *dir);
readdir函数返回一个指向dirent结构体的指针,该结构体代表了由dir指向的目录流中的下一个目录项;如果读到end-of-file或者出现了错误,那么返回NULL
int closedir(DIR *dir);
closedir函数关闭与指针dir相联系的目录流。成功时返回0;失败是返回-1,并设置相应的错误代码errno。
struct dirent {
ino_t d_ino; /* inode number */
off_t d_off; /* offset to the next dirent */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file */
char d_name[256]; /* filename */
};

/proc/PID/stat:在 Linux 操作系统中,/proc/PID/stat 文件提供了有关指定进程 PID 的进程状态信息。该文件的格式是一个文本文件,其中包含了进程的多个状态信息,这些信息通常以空格分隔。

/proc/PID/status是可读性更好的stat

/proc/PID/task:该目录包含了与指定进程 PID 关联的所有线程的信息,每个线程都有一个相应的子目录。这些子目录的名称为每个线程的线程 ID,其下包含了与该线程相关的信息,包括线程状态、线程所属的进程 ID、线程运行的 CPU 时间、线程栈的大小和内存地址等。此目录可以用于监视和管理进程中的多个线程。

这里我们只需要遍历/proc/PID/stat拿到ppid就行

/proc/PID/stat 文件的一般格式和其中包含的常见进程属性

  • pid:进程 ID
  • comm:进程的命令名称
  • state:进程状态
  • ppid:父进程 ID
  • pgrp:进程组 ID
  • session:会话 ID
  • tty_nr:所使用的终端设备号
  • tpgid:进程组 ID
  • flags:进程标志
  • minflt:未分配页面的数量(内存不足)
  • cminflt:未分配文件缓存页面的数量
  • majflt:分配了的页面的数量
  • cmajflt:分配了的文件缓存页面的数量
  • utime:进程在用户模式下花费的时间(以时钟滴答为单位)
  • stime:进程在内核模式下花费的时间(以时钟滴答为单位)
  • cutime:子进程在用户模式下花费的时间

建树打印

typedef struct processtree {
pid_t pid;
char pidname[36];
} Processtree;

所有进程都是进程1的子进程,我们把1进程作为root,边递归边打印树就可以

code

#include <assert.h>

assert(exp)可以帮助我们快速定位错误,exp为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。

调试结束后可以在#include <assert.h>上方#define NDEBUG来禁用assert调用

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <getopt.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>
typedef struct pipinfo {
char pidname[36];
pid_t pid;
pid_t ppid;
} Pipinfo;
typedef struct processtree {
pid_t pid;
char pidname[36];
} Processtree;

Pipinfo allpid[666] = { 0 };
int pidcount = 0;

void setProcessInfo();
pid_t getPpid(char*, char*);
void creatTree(bool pid, Processtree* root, int paddinglen);
void findAllchild(pid_t pid, int* childrenar);

int main(int argc, char* argv[]) {
setProcessInfo();
Processtree* root = (Processtree*)malloc(sizeof(Processtree));
root->pid = allpid[0].pid;
strcpy(root->pidname, allpid[0].pidname);

int opt;
struct option longopt[] = {
{"show-pids",0,NULL,'p'},
{"numeric-sort",0,NULL,'n'},
{"version",0,NULL,'V'}
};
switch (opt = getopt_long(argc, argv, "Vpnh", longopt, NULL)) {
case 'V':
printf("Grxer 2023 3 25\n");
break;
case 'p':
creatTree(true, root, 0);
break;
case 'n':
creatTree(false, root, 0);
break;
}
if (optind == argc) {
creatTree(false, root, 0);
}
else
printf("pstree -h for help");

}

void setProcessInfo() {
DIR* proc = opendir("/proc");
if (!proc) {
fprintf(stderr, "%s", "Can 't open /proc/");
exit(1);
}
else {
int pid = 0;
struct dirent* entry;
while (NULL != (entry = readdir(proc))) {
if (0 == (pid = atoi(entry->d_name)))
continue;
// printf("%d\n", pid);
allpid[pidcount].pid = pid;
allpid[pidcount].ppid = getPpid(entry->d_name, allpid[pidcount].pidname);
// printf("%d:%s:%d\n", allpid[pidcount].pid, allpid[pidcount].pidname, allpid[pidcount].ppid);
pidcount++;
}

}
closedir(proc);
}
//拿到父进程
pid_t getPpid(char* pid, char* name) {
char processpath[30] = "/proc/";
strcat(processpath, pid);
strcat(processpath, "/stat");
// printf("%s\n", processpath);
FILE* fp = fopen(processpath, "r");
if (fp) {
char i;
pid_t _pid, ppid;
fscanf(fp, "%d (%s %c %d", &_pid, name, &i, &ppid);
name[strlen(name) - 1] = 0;
// printf("%d:%s:%d\n", _pid, name, ppid);
fclose(fp);
return ppid;
}
fprintf(stderr, "%s %s %s", "open", processpath, "fail");
exit(1);
}

void creatTree(bool pidflag, Processtree* root, int paddinglen) {
int childrens[666] = { 0 };
findAllchild(root->pid, childrens);
char str[60] = { 0 };
if (childrens[0] == 0) {//没有子进程直接打印返回
if (pidflag)
printf("%s(%d)", root->pidname, root->pid);
else
printf("%s", root->pidname);
return;
}
if (pidflag)
sprintf(str, "%s(%d)-+-", root->pidname, root->pid);
else
sprintf(str, "%s-+-", root->pidname);
printf("%s", str);
for (size_t i = 0; i < 666 && 0 != childrens[i]; i++) {
Processtree* temp = (Processtree*)malloc(sizeof(Processtree));
if (temp == NULL) {
printf("malloc failed");
exit(1);
}
temp->pid = allpid[childrens[i]].pid;
strcpy(temp->pidname, allpid[childrens[i]].pidname);
creatTree(pidflag, temp, strlen(str) + paddinglen);//递归打印
if (i + 1 < 500 && childrens[i + 1] != 0)//下一个子进程还不为空,对齐,打印树枝
{
printf("\n");
for (size_t i = 0; i < strlen(str) + paddinglen; i++) {
printf(" ");
}
printf("|-");
}
}
free(root);

}

//找到全部子线程 csapp 5.8 循环展开优化
void findAllchild(pid_t pid, int* childrenar) {
size_t index = 0;
size_t i = 0;
for (;i < pidcount - 1; i += 2) {
if (allpid[i].ppid == pid)
childrenar[index++] = i;
if (allpid[i + 1].ppid == pid)
childrenar[index++] = i + 1;
}
for (; i < pidcount; i++) {
if (allpid[i].ppid == pid)
childrenar[index++] = i;
}

}