《操作系统:设计与实现》MiniLab1

本系列是南京大学蒋炎岩老师的操作系统课程学习笔记

课程主页:老师的wiki

课程视频:B站合集


第一个MiniLab是实现一个简易版的pstree,谨记老师的两条教导:

  1. 计算机的世界没有玄学,一切都建立在确定的机制上
  2. 不要慌,相信自己

因此,在实验指导书的帮助下,伴随着RTFM和STFW,我也算是勉强完成了这个实验 虽然看起来代码量不大,但花费的时间还是不少的,好在有所收获吧

获取命令行参数

这里主要是用getopt_long进行处理,用long是因为要同时处理长选项和短选项(-p, --show-pids


  struct option long_options[] = {{"show-pids", no_argument, &pflag, 1},
                                  {"numeric-sort", no_argument, &nflag, 1},
                                  {"version", no_argument, &vflag, 1},
                                  {0, 0, 0, 0}};

  while ((c = getopt_long(argc, argv, "npV", long_options, NULL)) != -1) {
    switch (c) {
    case 'n':
      nflag = 1;
      break;
    case 'p':
      pflag = 1;
      break;
    case 'V':
      vflag = 1;
      break;
    case 0:
      break;
    case '?':
      printf("Unknown option: %s\n", argv[optind - 1]);
      return 1;
    default:
      printf("Unexpected option: %c\n", c);
      return 1;
    }
  }

这里需要注意使用option结构体时的一个坑,手册里写了一句话The last element of the array has to be filled with zeros. 这还是我自测时不小心输入了--show-p这种部分匹配的选项时触发了段错误才发现的,不过具体为什么会崩溃没有深入的研究

获取全部进程号

Linux下我们可以在/proc目录看到很多以数字命名的目录,每个目录对应一个进程,也是该进程的进程号,因此只需要遍历该目录找到所有数字命名的目录即可。由于对C语言不熟所以在网上找了很多示例代码,最终用opendirreaddir解决

  char *base_path = "/proc";
  struct dirent *dent;
  DIR *srcdir = opendir(base_path);
  if (srcdir == NULL) {
    perror("Open fail");
    return -1;
  }

  // Proc array.
  proc *procs;
  procs = malloc(sizeof(proc) * (pid_max + 1));
  pid_t *ppids;
  ppids = malloc(sizeof(pid_t) * pid_max);
  proc *p = procs;

  // Read proc directory.
  while ((dent = readdir(srcdir)) != NULL) {
    if (dent->d_name[0] < '0' || dent->d_name[0] > '9') {
      continue;
    }
    // Save the pids.
    p->pid = atoi(dent->d_name);

    // Read the stat to get name and ppid.
    char path[13 + strlen(dent->d_name) + 1], buf[1024];
    memset(path, 0, sizeof(path));
    strcat(path, base_path);
    strcat(path, "/");
    strcat(path, dent->d_name);
    strcat(path, "/status");
    FILE *f = fopen(path, "r");
    while ((fscanf(f, "%s", buf) != EOF)) {
      if (strcmp(buf, "Name:") == 0) {
        fscanf(f, "%s", p->name);
      }
      if (strcmp(buf, "PPid:") == 0) {
        fscanf(f, "%d", &ppids[p - procs]);
      }
    }
    fclose(f);
    ++p;
  }

获取每个进程的父进程

这个信息其实也在/proc目录中,每个进程的目录里有一个status文件,包含了进程的许多信息。里面一项就是PPid。所以在第二步的时候可以一起读出来

$ cat /proc/$$/status
Name:   bash
Umask:  0022
State:  S (sleeping)
Tgid:   17248
Ngid:   0
Pid:    17248
PPid:   17200
TracerPid:      0
Uid:    1000    1000    1000    1000
Gid:    100     100     100     100
FDSize: 256
Groups: 16 33 100
NStgid: 17248
NSpid:  17248
NSpgid: 17248
NSsid:  17200
VmPeak:     131168 kB
VmSize:     131168 kB
VmLck:           0 kB
VmPin:           0 kB
VmHWM:       13484 kB
VmRSS:       13484 kB
RssAnon:     10264 kB
RssFile:      3220 kB
RssShmem:        0 kB
VmData:      10332 kB
VmStk:         136 kB
VmExe:         992 kB
VmLib:        2104 kB
VmPTE:          76 kB
VmPMD:          12 kB
VmSwap:          0 kB
HugetlbPages:          0 kB        # 4.4
CoreDumping:   0                       # 4.15
Threads:        1
SigQ:   0/3067
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000010000
SigIgn: 0000000000384004
SigCgt: 000000004b813efb
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: ffffffffffffffff
CapAmb:   0000000000000000
NoNewPrivs:     0
Seccomp:        0
Speculation_Store_Bypass:       vulnerable
Cpus_allowed:   00000001
Cpus_allowed_list:      0
Mems_allowed:   1
Mems_allowed_list:      0
voluntary_ctxt_switches:        150
nonvoluntary_ctxt_switches:     545

打印树

按老师的思路,使用递归函数实现,为此需要定义一个结构体,每个进程能找到自己所有的子进程从而递归打印

typedef struct {
  char name[1024];
  pid_t pid;
  pid_t *children;
  int child_num;
} proc;

void PrintTree(proc *p, proc *procs, int depth, int pflag) {
  if (depth > 0) {
    printf("\n%*s |\n", (depth - 1) * 4, "");
    printf("%*s", (depth - 1) * 4, "");
    printf(" +--");
  }
  printf("%s", p->name);
  if (pflag) {
    printf("(%d)", p->pid);
  }
  for (int i = 0; i < p->child_num; ++i) {
    proc *tmp = procs;
    while (tmp != NULL) {
      if (tmp->pid == p->children[i]) {
        break;
      }
      ++tmp;
    }
    PrintTree(tmp, procs, depth + 1, pflag);
  }
}

完整代码放在Github仓库