mit6.s081 lab1 Xv6 and Unix utilities

Keywords: Linux Windows Unix

chapter 1

system call

1.1 Process and memory

  • int fork()
    Create a process, the parent process returns the pid of the child process, and the child process returns 0
  • int wait(int *status)
    Status gets the status returned by the child process exit, and the return value of wait is the pid of the child process exit. If the process calling wait() has child processes, wait for an exiting child process. If there are no child processes, return - 1 directly.
  • int exec(char *file, char *argv[])
    Load a binary file and overwrite the process memory of the current program. The format of the binary file is ELF, file is the address of the binary file, and argv is the execution parameter of the program
    The execution process of the mini shell (located in user/sh.c):
    1.fork create sub process
    2. The subprocess exec executes binary files
    3. The parent process wait s for the child process to exit

1.2 I/O and File descriptors
fd 0 - standard input
fd 1 - standard output
fd 2 - standard error

  • read(fd ,buf, n)
    Read n bytes from the current offset of fd and copy it to buf to return the length of the read content. When reading is completed, the return value is 0
  • write(fd, buf, n)
    When writing b bytes from buf to fd, the processing of file offset is the same as that of read, and the length of the written content is returned
  • close(fd)
    Release fd to make it available again. The newly allocated fd is always the smallest fd not used in the process
  • fd = dup(1)
    Copy an fd and the return value is a new fd. Two FDS share a file. When fork and dup are called, two FDS share a file offset. Even if the same file is opened through open, the file offset will not be shared

1.3 Pipes
pipe creates a pipeline and returns two FDS, one for writing and the other for reading. When read is called to read the contents of the pipeline, if there is no data in the pipeline, it will wait for the data to be written to the pipeline or the write end fd to be closed. When the write end fd is closed, read returns 0

1.4 File system

  • chdir(path)
    Change current path
  • mkdir(path)
    create a new directory
  • mknod(path, marjor_dev_num, minor_dev_num)
    Create a device file and set the maximum device number and minimum device number
  • Each file corresponds to an inode, which is equivalent to the metadata of the file. The inode records the file type (file or directory), the length of the file, the location of the file on the disk and the number of links to the file. The inode information corresponding to a file can be obtained through fstat, and the corresponding links can be released through unlink. When the number of links in an inode is 0, the inode and the corresponding file will be deleted

cat program principle
1. Read content from fd 0
2. Write the read content to fd 1

Lab1

Start xv6
make qemu
sleep

Implement the sleep program in xv6. Sleep can be suspended according to the stopped ticks set by the user (the ticks are defined in the xv6 kernel, that is, the time of a time slice). It needs to be implemented in the file user/sleep.c

  • The system call of sleep is implemented in kermel/sysproc.c (sys_sleep)
  • Add sleep to the UPROGS of Makefile
#include "kernel/types.h" / / int is defined here
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[]) {
    if (argc != 2) { // Determine whether the input is correct
        printf("input error\n");
        exit(0);
    }
        
    int tick_count = atoi(argv[1]);
    sleep(tick_count);
    exit(0);
}
  • Exit qemu
    Open another terminal and enter a command
killall qemu-system-riscv64 
ping pong

A ping pong program is implemented using pipe. Two pipes with different directions are used for communication. At first, the parent sends a byte to the child through the pipe, and the child prints ": received ping", then sends a byte to the parent through the pipe, and then exits. After the parent reads the data in the pipeline, print ": received pong", and then exit. Implement the program in the file user/pingpong.c

  • Creating pipes with pipe
  • Using fork to create child processes
  • Use read to read the data in the pipeline
  • Get process pid using getpid
  • Add pingpong in UPROGS of Makefile
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[]) {
    int p1[2];
    int p2[2];
    pipe(p1);
    pipe(p2);
    if (fork() == 0) {
        char buf[8];
        read(p1[0], buf, sizeof(8));
        printf("%d: received ping\n", getpid());
        write(p2[1], "a", 1);
    }
    else {
        char buf[8];
        write(p1[1], "a", 1);
        read(p2[0], buf, sizeof(buf));
        printf("%d: received pong\n", getpid());
    }
    close(p1[0]);
    close(p1[1]);
    close(p2[0]);
    close(p2[1]);
    exit(0);
}
primes

The concurrent prime screening algorithm is implemented through fork and pipe to print the prime numbers in 2-35, which is implemented in the file user/primes.c
Concurrent primes
Concurrent prime sieve algorithm:

p = get a number from left neighbor
print p
loop:
    n = get a number from left neighbor
    if (p does not divide n)
        send n to right neighbor

  • Note the fd generated by the close pipe
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

void child_process(int p[]) {
    int child_p[2];
    close(p[1]);

    int prime;
    int len = read(p[0], &prime, sizeof(int));
    if (len == 0) {
        close(p[0]);
        exit(0);
    }
  
    printf("prime %d\n", prime);
    pipe(child_p);
    int num;
    if (fork() == 0) {
        close(p[0]);
        child_process(child_p);
    }
    else {
        close(child_p[0]);
        while (1) {
            len = read(p[0], &num, sizeof(int));
            if (len == 0) {
                close(p[0]);
                close(child_p[1]);
                wait(0);
                exit(0);
            }
            else {
                if (num % prime != 0) {
                    write(child_p[1], &num, sizeof(int));
                }
            }
        }
        
    }

}

int main(int argc, char *argv[]) {
    int p0[2];
    pipe(p0);
    if (fork() == 0) {
        child_process(p0);
    }
    else {
        close(p0[0]);
        for (int i = 2; i <= 35; i++) {
            write(p0[1], &i, sizeof(int));
        }
        close(p0[1]);
        wait(0);
    }
    exit(0);
}
find

Realize the file search in the directory tree. The prefix of the display path of the file is consistent with the path in the parameter, which is implemented in the file user/find.c

  • View the implementation of ls.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

char* fmtname(char *path) {
    static char buf[DIRSIZ+1];
    char *p;

    // Find first character after last slash.
    for(p=path+strlen(path); p >= path && *p != '/'; p--)
        ;
    p++;

    // Return blank-padded name.
    if(strlen(p) >= DIRSIZ)
        return p;
    memmove(buf, p, strlen(p));
    memset(buf+strlen(p), ' ', DIRSIZ-strlen(p));
    return buf;
}

void find(char *path, char *filename) {
    char buf[512], *p;
    int fd;
    struct dirent de;
    struct stat st;

    if ((fd = open(path, 0)) < 0) {
        printf("find: cannot open %s\n", path);
        return;
    }
    // Get stat according to fd
    if (fstat(fd, &st) < 0) {
        printf("find: cannot stat %s\n", path);
        close(fd);
        return;
    }

    switch (st.type)
    {
    case T_FILE:
        break;
    
    case T_DIR:
        if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
            printf("find: path too long\n");
            break;
        }
        strcpy(buf, path);
        p = buf+strlen(buf);
        *p++ = '/';
        while(read(fd, &de, sizeof(de)) == sizeof(de)){
            if(de.inum == 0)
                continue;

            // Do not process. And.. directories
            if (strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0)
                continue;

            memmove(p, de.name, DIRSIZ);
            p[DIRSIZ] = 0;
            // Get stat by file name
            if(stat(buf, &st) < 0){
                printf("ls: cannot stat %s\n", buf);
                continue;
            }
            if (st.type == T_DIR)
                find(buf, filename);
            else if (st.type == T_FILE) {
                if (strcmp(de.name, filename) == 0) {
                    printf("%s\n", buf);
                }  
            }
    
        }
        break;
    }
    close(fd);
}

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("input error\n");
        exit(0);
    }

    find(argv[1], argv[2]);
    exit(0);
}
xargs

Xargs is used to put standard input as a parameter after the command after xargs, such as

echo hello too | xargs echo bye It's actually executing echo bye hello too
 Output: bye hello too

Implemented in the user/xargs.c file
The implementation idea of xargs is:
(1) Obtain the information in the standard input, read each line of data in the standard input, and take each line of data as a parameter
(2) Use a char* exec_args[MAXARG] is passed in as an exec parameter. Put the passed in parameter into exec first_ In args, note that the first parameter in argv is xargs and the parameter to be executed is the second parameter
(3) Declare a buf to save the parameters in the standard input and read the standard input. If '\ n' is read, change the character to 0 (the end of the identification string) and put each line of data in exec_ In args
(4) Use the fork and exec functions to execute the real command. The running program is argv[1]. The first parameter of argv parameter in exec must also be the program name, and then it is the passed in parameter

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

int main(int argc, char *argv[]) {
    char buf[512];
    char *exec_argv[MAXARG] = {0};
    int argv_idx = 0;

    for (int i = 1; i < argc; i++) {
        exec_argv[argv_idx++] = argv[i];
    }
    
    int buf_idx = 0;
    int len = 0;

    while (1) {
        int begin_idx = buf_idx;
        while (1) {
            len = read(0, &buf[buf_idx], 1);

            if (len == 0 || buf[buf_idx] == '\n') {
                buf[buf_idx] = 0;
                break;
            }

            buf_idx++;
        }

        if (len == 0)
            break;

        if (buf_idx == begin_idx)
            continue;

        buf[buf_idx++] = 0;    
        
        exec_argv[argv_idx++] = buf + begin_idx;
    }
    

    if (fork() == 0) {
        exec(argv[1], exec_argv);
        exit(0);
    }
    else {
        wait(0);
    }
    exit(0);
} 
Operation results

Posted by james_creasy on Sat, 02 Oct 2021 13:15:28 -0700