[6.s081]Lab Utilities

Overview

This lab aims for teaching us how to write user program in xv6, also get familiar with pipes, common system calls. My github repo: https://github.com/JoeBlack220/6.S081/

Project Setup

It's easy to boot up xv6 just by using make qemu. But the gdb guide doesn't help a lot on how to connect a gdb to qemu. We can follow these steps:

  • Create a .gdbinit file under ~:
    • echo "add-auto-load-safe-path YOUR_PATH/xv6-labs-2020/.gdbinit " >> ~/.gdbinit
  • Open qemu under gdb mode:
    • make qemu-gdb
  • Attach gdb to qemu in a second terminal:
    • gdb-multiarch
  • Load the code in gdb:
    • file user/_ls
  • Set a break point:
    • b ls.c:15 You could also set breank points, print variables just as using a normal gdb.

sleep

A trivial one to illustrate how to use system calls to make a user program:

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

int
main(int argc, char *argv[])
{
  int n;

  if(argc < 2) {
    fprintf(2, "Usage: sleep for x seconds...\n");
    exit(1);
  }

  n = atoi(argv[1]);

  sleep(n);
  exit(0);
}

Don't forget to call exit(0); at the end of the main function.

pingpong

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

int
main(int argc, char *argv[])
{
  // Ingnore any input arguments

  int parent[2];
  int child[2];

  // Create a pipe in p
  pipe(parent);
  pipe(child);

  char buf[1];
  // child process, write one byte to child[1]
  if(fork() == 0) {
        close(parent[1]);
        close(child[0]);
    read(parent[0], buf, 1);
        close(parent[0]);
    if(buf[0] == 'p') {
      printf("%d: received ping\n", getpid());
    }
    write(child[1], "c", 1);
        close(child[1]);
  } else { // parent process, write one byte to parent[1]
        close(parent[0]);
        close(child[1]);
    write(parent[1], "p", 1);
        close(parent[1]);
    read(child[0], buf, 1);
        close(child[0]);
    if(buf[0] == 'c') {
      printf("%d: received pong\n", getpid());
    } 
  }

  exit(0);
}

Could be optimized to only use one pipe, which will make use of wait() to make sure the child process has read and write to the pipe.

primes

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

int
main(int argc, char *argv[])
{

  int p[2];
  pipe(p);
  int buf[1];

  for(int i = 2; i <= 35; ++i) {
    write(p[1], &i, 1);
  }
  close(p[1]);

  int read_fd = p[0];
  while(read(read_fd, buf, 1) > 0) {
    int cur_prime = buf[0];
    printf("prime %d\n", cur_prime);
    pipe(p);          // create new file descriptor for the next child
    int fork_pid = fork();
    if(fork_pid != 0) {
      close(p[0]);
      while(read(read_fd, buf, 1) > 0) {
        if(buf[0] % cur_prime != 0) {
          write(p[1], &buf[0], 1);
        } 
      }
      close(read_fd);
      close(p[1]);
      wait((int *)0);
    } else {
        close(p[1]);
        read_fd = p[0];
    }
  }
  exit(0);
} 

Just be careful to close a fd in time in case the fd number exceeds the system limits.

find

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

void
find_helper(char buf[512], char* cur_name, char* name) 
{
    char *p;
    int fd;
    struct dirent de;
    struct stat st;

    if((fd = open(buf, 0)) < 0) {
        fprintf(2, "find: cannot open %s\n", buf);
        return;
    }

    if(fstat(fd, &st) < 0) {
        fprintf(2, "find: cannot stat %s\n", buf);
        close(fd);
        return;
    }

    switch(st.type) {
        case T_FILE:
            if(strcmp(cur_name, name) == 0) {
                printf("%s\n", buf);
            }
            break;
        case T_DIR:
            if(strlen(buf) + 1 + DIRSIZ + 1 > 512) {
                printf("find: path too long\n");
                break;
            }
            p = buf+strlen(buf);
            *p++ = '/';
            while(read(fd, &de, sizeof(de)) == sizeof(de)) {
                if(de.inum == 0)
                    continue;
                if(strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0) {
                    continue;
                }
                memmove(p, de.name, DIRSIZ);
                p[DIRSIZ] = 0;
                find_helper(buf, de.name, name);
            }
            break;
    }
    close(fd);
    return;

}


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

    if((fd = open(path, 0)) < 0) {
        fprintf(2, "find: cannot open %s\n", path);
        return;
    }

    if(fstat(fd, &st) < 0) {
        fprintf(2, "find: cannot stat %s\n", path);
        close(fd);
        return;
    }

    switch(st.type) {
        case T_FILE:
            fprintf(2,"find: given path %s is not a directory\n", path);
            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); // move p to the end of buf
            *p++ = '/';
            while(read(fd, &de, sizeof(de)) == sizeof(de)) {
                if(de.inum == 0) { 
                    continue;
                }
                if(strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0) {
                    continue;
                }
                memmove(p, de.name, DIRSIZ);
                p[DIRSIZ] = 0;
                find_helper(buf, de.name, name);
            }
            break;
    }
    close(fd);
    return;
}

int
main(int argc, char *argv[])
{
    if(argc < 3) {
        fprintf(2, "usage: find [path] [filename]\n");
        exit(1);
    }

    find(argv[1], argv[2]);

    exit(0);
}

The implementation here is a little long and can be optimized. find_helper() and find() may be merged into one function.

xargs

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"
#define MAXLEN 100
int
main(int argc, char *argv[]) 
{
    if(argc <= 1) {
        fprintf(2, "usage: xargs command (arg...)\n");
        exit(1);
    }
    char *command = argv[1];
    char buf;
    char new_argv[MAXARG][MAXLEN]; // assuming the maximun single parameter length is 512
    char *p_new_argv[MAXARG];

    while(1) {
        memset(new_argv, 0, MAXARG * MAXLEN); // reset the parameter

        for(int i = 1; i < argc; ++i) {
            strcpy(new_argv[i-1], argv[i]);
        }

        int cur_argc = argc - 1;
        int offset = 0;
        int is_read = 0;

        while((is_read = read(0, &buf, 1)) > 0) {
            if(buf == ' ') {
                cur_argc++;
                offset = 0; 
                continue;
            }
            if(buf == '\n') {
                break;
            }
            if(offset==MAXLEN) {
                fprintf(2, "xargs: parameter too long\n");
                exit(1);
            }
            if(cur_argc == MAXARG) {
                fprintf(2, "xargs: too many arguments\n");
                exit(1);
            }
            new_argv[cur_argc][offset++] = buf;
        }

        if(is_read <= 0) {
            break;
        } 
        for(int i = 0; i <= cur_argc; ++i) {
            p_new_argv[i] = new_argv[i];
        }
        if(fork() == 0) {
            exec(command, p_new_argv);
            exit(1);
        } 
        wait((int*) 0);
    }
    exit(0);
}

Read the input from stdout and split the parameter by space and newline characther. It seems that there is memory limit on stack since if I allocate the parameter array to be too big it will throw error and I don't dig too much into it. Maybe when I get to know the system better the causes will emerge.

Conclusion

This lab gets us familiar of the common system call, the usage of pipe and how process interact with each other, and also the basic of C syntax.