GENIA

nju-os M1

2024-10-30

要求:M1: 打印进程树 (pstree) (jyywiki.cn)

用时:4h 而且只做了一部分

不能放源码,所以只讲讲思路,总结一下做的时候碰到的问题。

01 /proc

要建树,首先需要素材,需要知道当前运行的所有进程的基本信息,包括该进程的 PID、NAME 以及它的父进程。

linux 本身有许多可以查看进程信息的命令行工具,譬如 ps,它们本身也要通过操作系统获得自己要输出的信息,可以通过阅读它们的源码看看它们是怎么获取进程信息的。

老师的实验指南中告诉我们 /proc 目录是一切的关键:

会发现该目录下有许多临时目录,而这些临时目录的名称就对应着当前的进程号。

不难发现临时目录下的 status 文件包含了我们需要的信息:

那就简单了,只要把 NAME-PID-PPID 一一对应地收集起来就好了。

因为我编程基础很薄弱,实在想不到什么聪明办法,就只能很暴力地去做这件事。

我选择把 /proc 目录下的目录名称用正则表达式滤一遍,留下目录名为数字的,放到一个叫dir 的数组里。再遍历这个数组,一个一个读取 /proc/%s/status 里的内容,把 NAME 和 PPID 读出来,放到 name_dirs 和 p_dirs 里面。

正则表达式和文件 I/O 这里花了我一些时间。值得庆幸的是 status 内容的读取不需要 root 权限。

02 建树与打印

数据结构

我依旧选择很笨的方法,直接构建单向多叉树:

1
2
3
4
5
6
typedef struct TreeNode{
char *p_name;
char *pid;
struct TreeNode **child;
int child_count;
}TreeNode;

除了为每个节点维护一个 child 指针数组,我想不到什么更好的方法(

遵循数据结构课的经验,还是设计四个函数用于建树、free和 print:

1
2
3
4
5
TreeNode* createNode(const char *str1, const char *str2);
void addChild(TreeNode *parent, TreeNode *child);
void build_the_tree(TreeNode *root,int count,char *dirs[],char *name_dirs[],char *p_dirs[]);
void freeTree(TreeNode *root);
void printTree(TreeNode *node, int is_last, const char *prefix);

createNode

为新节点分配内存,用 strdup 函数将参数指向的字符串内容复制到新的地址空间。

节点其它内容也进行初始化。

addChild

用 realloc 为 child 指针数组重新分配内存,增加一个 sizeof(TreeNode *)

把指向孩子节点的指针添加进去。

build_the_tree

创建 TreeNode 节点数组 nodes,为每个进程创建一个节点并初始化,节点地址放在 nodes 中待用。

遍历 nodes,孩子找爹。

最后 free nodes 数组。

freeTree

递归 free 即可,直到 root == NULL。

printTree

我觉得这个是最难的。但本质还是一个递归输出。

  1. 打印当前节点信息。

  2. 如果当前节是子节点,则直接返回。

  3. 如果是父节点,则遍历子节点并对子节点进行递归调用。

这是我做出来的最终效果:

遇到的问题

根节点要正确初始化!

因为一开始根节点忘记初始化或者没有完全初始化,导致一运行就 segmentation fault。

具体的问题包括但不限于:

  1. 一开始忘记给 root 分配内存,导致 build_the_tree 时完全找不到它的地址
  2. child_count 未初始化时在内存中的值很大,导致 printTree 函数判断出错,怎么都运行不完
  3. 在 printf 时发生段错误,调试发现参数是一个不合法内存地址,因为 root-> p_name 没赋值
  4. ……………………

在经历了几小时 gdb 爆调后,程序才算是跳出 segmentation fault 诅咒了。彻底明白自己的 C 指针和数据结构学得很烂了(

可移植性

我试着用 gcc 编译了 32 位和 64 位两个版本,都可以正常运行。

关于可移植性这块我没有做更深入的研究。

没实现的功能

  1. 命令行的使用。我不知道怎么实现 通过命令行控制输出。pstree -n 和 -p 的输出差距还是很大的。我觉得太过复杂了。

  2. 得到进程号以后发现文件没了 (进程终止了) 的情况我没考虑。

  3. pstree -n 会把同级的同名进程合并在一起,这个功能我自己实现不了。

  4. ……

希望在后续的学习中能进一步完善代码。