MIT6.S081Lab1: Xv6 and Unix utilities

MIT6.S081 Lab1: Xv6 and Unix utilities

Official documentation

一.Boot xv6

How to successfully boot xv6 can be seen in the previous article MIT6.S081 Experimental Environment Construction, there is just one more step, execute it in the clone folder

git checkout util

Just switch to the util branch.

二.sleep

Write a program in user/sleep.c to complete the sleep program where the user can specify the number of tricks to sleep.

step1

Check parameters, convert parameters

#include "kernel/types.h"
#include "user.h"

int main(int argc, char* argv[]) {<!-- -->
  // Check the number of parameters
  if (argc != 2) {<!-- -->
    fprintf(2, "Wrong number of output parameters!\\
");
    exit(1);
  }

  // Number of conversion parameters
  int times = atoi(argv[1]);
  exit(0);
}

step2

Call sleep in user to trigger system call

#include "kernel/types.h"
#include "user.h"

int main(int argc, char* argv[]) {<!-- -->
  // Check the number of parameters
  if (argc != 2) {<!-- -->
    fprintf(2, "Wrong number of output parameters!\\
");
    exit(1);
  }

  // Number of conversion parameters
  int times = atoi(argv[1]);
  sleep(times);
  exit(0);
}

step3

Obvious delays can be observed when running sleep in xv6

The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly.

Execute in the corresponding folder command line

sudo ./grade-lab-util sleep

can be seen

The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly.

Written successfully!

三.pingpong

The parent and child processes send each other a byte and print the content output.

step1

Create two pipes

#include "kernel/types.h"
#include "user.h"

int main() {<!-- -->
  // Used to receive a byte
  char buf[1];

  //Two pipes, p1 is used for parent writing and child reading, p2 is used for child writing and parent reading
  int p1[2], p2[2];
  pipe(p1);
  pipe(p2);
  exit(0);
}

step2

fork a child process

#include "kernel/types.h"
#include "user.h"

int main() {<!-- -->
  // Used to receive a byte
  char buf[1];

  //Two pipes, p1 is used for parent writing and child reading, p2 is used for child writing and parent reading
  int p1[2], p2[2];
  pipe(p1);
  pipe(p2);

  int pid = fork();

  if (pid > 0) {<!-- -->

  } else if (pid == 0) {<!-- -->

  } else {<!-- -->
    printf(2, "fork error!\\
");
  }
  exit(0);
}

step3

Transfer a byte to each other. Be careful to close it in time after writing, otherwise read will always be blocked.

#include "kernel/types.h"
#include "user.h"

int main() {<!-- -->
  // Used to receive a byte
  char buf[1];

  //Two pipes, p1 is used for parent writing and child reading, p2 is used for child writing and parent reading
  int p1[2], p2[2];
  pipe(p1);
  pipe(p2);

  int pid = fork();

  if (pid > 0) {<!-- -->
    // send bytes
    write(p1[1], "1", 1);
    close(p1[1]);
    //Receive bytes
    read(p2[0], buf, 1);
    fprintf(1, "%d: received pong\\
", getpid());
    exit(0);
  } else if (pid == 0) {<!-- -->
    read(p1[0], buf, 1);
    fprintf(1, "%d: received ping\\
", getpid());
    write(p2[1], "1", 1);
    close(p2[1]);
    exit(0);
  } else {<!-- -->
    fprintf(2, "fork error!\\
");
    exit(1);
  }
  exit(0);
}

step4

You can see it when running in xv6

The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly.

Execute in the corresponding folder command line

sudo ./grade-lab-util pingpong

can be seen

!The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly.

Written successfully!

四.primes

Use fork and pipe to form a pipeline and perform prime number screening from 2 to 35.

Specific solutions can be found in the official tutorial. In summary, the first number read from the left neighbor is printed out as its own base. After that, the number is read from the left neighbor in a loop to determine whether it can be divided by the base. If it can be said that it is not a prime number, if not, it is handed over to Right neighbor. Loop this process to filter out the prime numbers.

There are two key points: 1. How to communicate between left and right neighbors; 2. Close unnecessary file descriptors in a timely manner, otherwise xv6 cannot support so many file descriptors.

How to communicate between left and right neighbors: The so-called left and right neighbors are father and son processes. For each prime number, we have to create a process, that is, each process (except the last one) has to fork once. Before forking, we have to create a pipe for communication, but the child process does not know the file descriptor of the pipe. Therefore, we need to pass the read end of the pipe to the child process, and the child process needs to copy the behavior of the parent process. It is natural to think of using a recursive function. The parameter of the function is the read end file descriptor of the pipe.

Close the file descriptor in a timely manner: after fork, the parent process does not need to close the read end, and the child process does not need to close the write end. The child process does not need to close the previous read end of the parent process

Then note that each process only forks once. If the loop after forking is judged as not to fork.

In fact, the most important thing is to use fork and pipe to form a pipeline, that is, each thread must have a reading end and a writing end. It reads from the parent process and writes to the child process. Unnecessary file descriptors are closed, and the reading end comes from Due to the development of the parent process, the writing end comes from its own development, and then each child process has to repeat the behavior of the parent process, so a recursive function is used, and the reading end of the parameter passing is used.

code show as below:

#include "kernel/types.h"
#include "user.h"


//Operations to be performed by each process
void ProcessOperate(int listen_fd) {<!-- -->
  // Use the first read number as the base and print it out
  int base = -1;
  if (read(listen_fd, & amp;base, 4) == -1) {<!-- -->
    fprintf(2, "%d read listen_fd error!\\
", getpid());
    exit(-1);
  }
  fprintf(1, "prime %d\\
", base);
  
  int is_fork = 0; // Determine whether the current thread has been fork

  //Continue to read data and process it
  int num = -1;
  int pipes[2]; // used for parent-child process communication
  while (read(listen_fd, & amp;num, 4)) {<!-- -->
    if (num % base != 0) {<!-- -->
      if (!is_fork) {<!-- -->
        is_fork = 1;
        if (pipe(pipes) == -1) {<!-- -->
          fprintf(2, "%d process create pipe error!\\
", getpid());
          exit(-1);
        }
        int pid = fork();
        if (pid > 0) {<!-- -->
          close(pipes[0]);
        } else if (pid == 0) {<!-- -->
          close(pipes[1]);
          close(listen_fd);
          ProcessOperate(pipes[0]);
        } else {<!-- -->
          fprintf(2, "%d process fork error\\
", getpid());
          exit(-1);
        }
      }
      write(pipes[1], &num, 4);
    }
  }
  close(listen_fd);
  close(pipes[1]);
  wait(0);
  exit(0);
}

int main() {<!-- -->
  int pipes[2];
  if (pipe(pipes) == -1) {<!-- -->
    fprintf(2, "main process create pipe error!\\
");
    exit(-1);
  }

  //First write all data into the first pipe
  for (int i = 2; i <= 35; + + i) {<!-- -->
    if (write(pipes[1], & amp;i, 4) == -1) {<!-- -->
      fprintf(2, "main process write pipe error!\\
");
      exit(-1);
    }
  }

  //Close unnecessary writes
  close(pipes[1]);

  // Pass the reading end and filter prime numbers starting from the first process
  ProcessOperate(pipes[0]);

  exit(0);
}

Run from command line

sudo ./grade-lab-util primes

can be seen

The external link image transfer failed. The source site may have an anti-leeching mechanism. It is recommended to save the image and upload it directly.

success!

五.find

Write a simplified version of the UNIX find program: Find all files with a specific name in a directory tree.

In fact, as long as you understand how to write the ls program, you can basically write this program.

The specific idea is to loop through the files or directories in the directory we entered. When the file is traversed, the name of the file is compared with the name we are looking for; when the directory is traversed, the process is recursed. The function interfaces used are also used in ls, so if you understand the ls program, you will know how to use those interfaces.

There are a few points to note:

  • When comparing the name of the file with the name we are looking for, we need to extract the name of the simplified file. Of course we can use the fmtname function in ls, but note that there is a memset(buf + strlen§, ‘ ‘, DIRSIZ- in fmtname strlen§); We don’t need this because it is for filling in gaps to facilitate output. It would be wrong for us to add this name for comparison.
  • The beginning of each directory is two directories “.” and “…”. If not excluded, it will recurse infinitely, so be careful.
  • Another thing for me is that exit allows the program to exit directly, and I still use return in the function (I have been looking for this error for a long time, dizzy).

code show as below:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

// get name
char* fmtname(char *path)
{<!-- -->
  static char buf[512];
  char *p;
  //Extract the last/following name
  for(p=path + strlen(path); p >= path & amp; & amp; *p != '/'; p--)
    ;
  p + + ;

  memmove(buf, p, strlen(p));
  buf[strlen(p)] = 0;

  return buf;
}


void find(char *path, char *name) {<!-- -->
  char buf[512], *p;
  int fd;
  struct dirent de;
  struct stat st;

  // open Directory
  if ((fd = open(path, 0)) < 0) {<!-- -->
    fprintf(2, "ls: cannot open %s!\\
", path);
    exit(-1);
  }

  // copy path
  strcpy(buf, path);
  p = buf + strlen(buf);
  *p + + = '/';

  // Loop to determine the files or directories under the directory
  while (read(fd, & amp;de, sizeof(de)) == sizeof(de)) {<!-- -->
    if (de.inum == 0) {<!-- -->
      continue;
    }
    //Exclude . and .. at the beginning of the directory
    if (!strcmp(de.name, ".") || !strcmp(de.name, "..")) {<!-- -->
      continue;
    }
    //Add the file or directory name to the end of buf
    memmove(p, de.name, strlen(de.name));
    p[strlen(de.name)] = 0;
    if (stat(buf, & amp;st) < 0) {<!-- -->
      fprintf(1, "ls: cannot stat %s \\
", buf);
      continue;
    }
    switch(st.type) {<!-- -->
      case T_FILE:
        // If it is a direct comparison of files
        if (!strcmp(fmtname(buf), name)) {<!-- -->
          fprintf(1, "%s\\
", buf);
        }
        break;
      case T_DIR:
        // Directory recursion
        find(buf, name);
        break;
    }
  }

  close(fd);
  return;


}

int main(int argc, char* argv[]) {<!-- -->
  //Call function for recursive search
  if (argc < 3) {<!-- -->
    find(".", argv[1]);
    exit(0);
  }
  find(argv[1], argv[2]);
  exit(0);
}

6.xargs

Write a simplified version of the UNIX xargs program: it reads line by line from standard input and executes a command for each line, supplying the line as an argument to the command.

To make it clear, xargs is to execute a command. The parameters of the command come from its own parameters and from the standard input. Such a command and its own parameters are executed for each line in the standard input.

That’s actually very simple. Just read the data directly from the standard input, and then divide the lines according to \\
. Every time a line is found, fork it once and let the child process exec execute it. The parent process adjusts the position of the pointer and continues execution.

code show as below:

#include "kernel/types.h"
#include "kernel/param.h"
#include "user.h"

int main(int argc, char* argv[]) {<!-- -->
  if (argc > MAXARG) {<!-- -->
    fprintf(2, "argc > MAXARG\\
");
    exit(-1);
  }

  //Commands and parameters entered by xargs itself
  char* new_argv[MAXARG];
  for (int i = 0; i < argc - 1; + + i) {<!-- -->
    new_argv[i] = argv[i + 1];
  }

  char buf[32];
  char *p = buf;
  //Read input from the command line
  read(0, buf, 32);
  int pid;
  for (int i = 0; i < 32; + + i) {<!-- -->
    // If a newline character is detected, it means a new line is encountered, then fork and let the child process exec
    if (buf[i] == '\\
') {<!-- -->
      pid = fork();
      if (pid == 0) {<!-- -->
        // Set the end
        buf[i] = 0;
        // Set standard input as a parameter
        new_argv[argc - 1] = p;
        exec(new_argv[0], new_argv);
        fprintf(2, "exec error\\
");
      } else {<!-- -->
        //Update the pointer to point to the next line
        p = & amp;buf[i + 1];
        wait(0);
      }
    }
  }

  wait(0);
  exit(0);
}


Originally, I had another version, which was to use while to read byte by byte, but an error was reported. I don’t know why. It feels like there should be a problem with the judgment of newline characters, so it is safer to directly read all the standard input to ensure that it will end.

Summary

Because there were other things in between these experiments, the time span is still quite long, but I think the experiment of MIT6.S081 is really valuable. I may have used these instructions before, but I don’t know how these instructions are implemented. In the process of implementing these instructions, I became more familiar with the use of system calls such as fork, pipe, and exec. I had never used these system calls before.

Looking forward to the next experiment!