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!