要求: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 | typedef struct TreeNode{ |
除了为每个节点维护一个 child 指针数组,我想不到什么更好的方法(
遵循数据结构课的经验,还是设计四个函数用于建树、free和 print:
1 | TreeNode* createNode(const char *str1, const char *str2); |
createNode
为新节点分配内存,用 strdup 函数将参数指向的字符串内容复制到新的地址空间。
节点其它内容也进行初始化。
addChild
用 realloc 为 child 指针数组重新分配内存,增加一个 sizeof(TreeNode *)
。
把指向孩子节点的指针添加进去。
build_the_tree
创建 TreeNode 节点数组 nodes,为每个进程创建一个节点并初始化,节点地址放在 nodes 中待用。
遍历 nodes,孩子找爹。
最后 free nodes 数组。
freeTree
递归 free 即可,直到 root == NULL。
printTree
我觉得这个是最难的。但本质还是一个递归输出。
打印当前节点信息。
如果当前节是子节点,则直接返回。
如果是父节点,则遍历子节点并对子节点进行递归调用。
这是我做出来的最终效果:

遇到的问题
根节点要正确初始化!
因为一开始根节点忘记初始化或者没有完全初始化,导致一运行就 segmentation fault。
具体的问题包括但不限于:
- 一开始忘记给 root 分配内存,导致 build_the_tree 时完全找不到它的地址
- child_count 未初始化时在内存中的值很大,导致 printTree 函数判断出错,怎么都运行不完
- 在 printf 时发生段错误,调试发现参数是一个不合法内存地址,因为 root-> p_name 没赋值
- ……………………
在经历了几小时 gdb 爆调后,程序才算是跳出 segmentation fault 诅咒了。彻底明白自己的 C 指针和数据结构学得很烂了(
可移植性
我试着用 gcc 编译了 32 位和 64 位两个版本,都可以正常运行。
关于可移植性这块我没有做更深入的研究。
没实现的功能
命令行的使用。我不知道怎么实现 通过命令行控制输出。pstree -n 和 -p 的输出差距还是很大的。我觉得太过复杂了。
得到进程号以后发现文件没了 (进程终止了) 的情况我没考虑。
pstree -n 会把同级的同名进程合并在一起,这个功能我自己实现不了。
……
希望在后续的学习中能进一步完善代码。