jyyOS-m1-print-pstree-en

🔗 https://jyywiki.cn/OS/2024/labs/M1.md

The implementation may not be correct and might change as I keep learning; this note is mainly to organize and review the lab for my own reference.

I first try to print a pstree starting from PID 1, without handling command-line options yet.

The problem breaks down into:

  • How to obtain pid.
  • How to obtain ppid.
  • How to store pid and ppid.
  • How to print the tree.
  • Finally, how to parse command-line arguments.

How to store pid and ppid

I define pid, ppid, and the process name name in one struct.

#define MAX_NAME_LEN 256

typedef struct Process{
	pid_t pid;
	pid_t ppid;
	char name[MAX_NAME_LEN];
}

The GNU C Library’s official documentation for pid_t.

The pid_t data type is a signed integer type which is capable
of representing a process ID. In the GNU C Library, this is an int.

Store all processes in an array where each element is a Process struct, and define the process count.

#define MAX_PROC 1024
struct Process processes[MAX_PROC];
int process_count = 0;

Obtaining pid and ppid

As the lab guide says, everything is a file (so the relevant C functions and APIs matter a lot) , we can get processes via directories under /proc named with numbers:

//traverse /proc directory, get all process information
void get_process_list() {
  // read each entry in the proc directory
  struct dirent *entry;
  DIR *dp = opendir("/proc"); // open /proc dir
  
  if(dp==NULL){
    perror("opendir: /proc");
    exit(1);
  }

  while((entry = readdir(dp)) != NULL){
    if (isdigit(*entry->d_name)){
      //printf("Process ID:%s\n", entry->d_name);
      pid_t pid = atoi(entry->d_name);
      if (pid > 0) {
        pid_t ppid;
        char name[MAX_NAME_LEN];

        // read the status file of the process
        if (read_status_file(pid, &ppid, name) == 0) {
          processes[process_count].pid = pid;
          processes[process_count].ppid = ppid;
          strncpy(processes[process_count].name, name, MAX_NAME_LEN);
          process_count++;
        }
      }
    }
  }
  closedir(dp);
}

Official explanation of struct dirent here.

I also searched “members of dirent structure” and used a Stack Overflow answer.

Official explanation of the DIR data type here.

int read_status_file(pid_t pid, pid_t *ppid, char *name) {
  char path[64], buffer[256];
  FILE *file;

  sprintf(path, "/proc/%d/status", pid);
  file = fopen(path, "r");
  if (file == NULL) {
    return -1; //file can't be opened, maybe process already exits
  }

  while (fgets(buffer, sizeof(buffer), file)) {
    if (strncmp(buffer, "Name:", 5) == 0) {
      sscanf(buffer, "Name:\t%s", name);
    }
    else if (strncmp(buffer, "PPid:", 5) == 0) {
      sscanf(buffer, "PPid:\t%d", ppid);
    }
  }

  fclose(file);
  return 0;
}

Inside read_status_file, usage of sprintf, fgets, strncmp, and sscanf was unfamiliar to me; ChatGPT said these are very common when reading and parsing file contents.

Printing the tree

The most straightforward approach is recursion: compare the current process’s pid with other entries’ ppid in the array to decide whether to recurse further. I’ll skip the details here.

Parsing command-line arguments

getopt_long is really handy.

C parses short and long options on the command line.

This is the official definition and examples; while coding I also referred to this example.

I still can’t answer the questions jyy raised at the end of the lab guide—I’ll keep digging deeper.