操作系统M1实验笔记

分类:Operating System, 发布于:2019-02-27 19:55:00, 更新于:2019-04-19 00:09:40。 评论

M1任务回顾

  • 实现pstree,打印树状的进程结构
  • 支持-p-n-V三个基本选项

实现过程

框架代码

当我怀着喜庆的心情迎接OSLabs的框架代码的时候,打开发现pstree.c其实就是个Hello World,只想说一句nmd wsm。

这个框架代码真是太精致(简陋)了啊

识别选项

这个部分就用到了ICS-PA带来的丰富经验。与其用循环来一个字符一个字符的判断选项,不如直接开一个数组,用一次循环来匹配所有可能的选项字符串,如果成功就把对应的开关打开,如果没有匹配到就报错结束运行。

读取进程文件

读取文件夹需要<dirent.c>,使用DIR *dir = opendir('path_to_dir');来打开文件夹,然后用while ((dp = readdir(dir)) != NULL)来读取所有的内容。此处的dp需要是struct dirent*类型。

有一点比较奇幻的是,readdir的读取顺序是先...,然后按照字母顺序读,最后按照数字从小到大(非字典序)读取文件夹。这样就省去了手动排序的麻烦。

读取线程文件

/proc/$PID/task文件夹中存放了线程相关信息,但对应的stat文件中的内容和进程不同。进程的父母进程使用ppid表示,而线程则是使用tgid来表示。

但是在pstree里,只需要把父母进程的pid直接赋值成线程的ppid,然后把线程的名称更改为{ParentName},就可以和进程同等对待了。

构建进程树

进程树的根是systemd(1)进程。这个节点是一个静态的全局变量。整个树使用指针相互连接,每个节点分别有指向先♂辈、第一个子孙和下一个兄妹的3个指针。

构建进程树我使用了两个函数,分别是

  • struct process* findProcess(pid_t pid),用于在树上遍历找到指定pid的进程节点并返回地址,或者返回NULL
  • void addProcess(struct process*),用于往树中插入一个节点,首先找到父亲节点,然后根据是否需要排序决定其在兄弟链表上的插入位置。

打印进程树

打印进程树使用了双层递归的想法,整个代码没超过20行。

  • void printProcess(struct process*),打印当前节点并递归打印子孙和兄弟。由于先打印子孙,整个树先在横向上扩展,然后向下扩展打印子孙的兄弟。子孙递归结束后再打印本层的其他兄弟。
  • void printParentProcesses(struct process*),打印当前节点的前辈。这个函数是用来打印一个节点前面的空格和竖线的。方法是不断向parent方向递归,打印和名称一样数量的空格,然后再打印一条竖线。

用到的小技巧

  • ICS-PA式的数组和字符串判断,上面已经介绍过了。
  • 如果要打印pid,就把pid打印到进程结构体的名称数组里,这样在打印空格的时候就可以直接代码复用。
  • 利用函数参数是否为NULL来执行不同的功能,实现代码复用。读取进程的时候,如果parent == NULL,则是读取进程,否则是读取子进程/线程。
  • 输出多个空格的方法:
    printf("%*s", n, "");

踩到的坑

  • sizeof函数对移植并不友好。他的长度在32和64位系统下不一致。同时由于一开始使用了#define导致无法指定数字的格式,就产生了错误。
  • sprintf需要考虑数组的长度。如果打印%d,就可能由于字符串长度不足产生溢出(提示为warning)。对于字符串可以使用%.ns来限制打印的长度。如果是数字可以先打印成字符串再进行第二次打印。
    sprintf(tmp, "%d", src);
    sprintf(dst, "%.ns", tmp);
  • 递归是先递归还是先执行函数,这是一个很玄学的问题。如果脑子转不过弯来不如排列组合尝试一下总归有一种是对的。
  • FILE stream一定要记得关掉。测试的时候由于只有fopen而没有fclose导致后面很多进程文件没能打开,整个进程树少了一大半。

总结与经验

  • KISS。美观程度不重要,先把功能做出来了再说。在实现功能的时候我总是想着一步到位,结果打印出来的格式乱七八糟,也找不到问题出在哪。(后来发现是链表插入和递归打印格式同时出错。)
  • 写程序前先总体构思,由上至下,有利于提高代码模块化、复用程度并减少bug出现的概率。

评论