1. 程式人生 > 其它 >MIT6.S081-Lab Utilities

MIT6.S081-Lab Utilities

開始日期:22.2.24

作業系統:Ubuntu20.0.4

Link:Lab Utilities

目錄

Lab Utilities

環境配置

每次的環境配置都是一段折磨又快樂的時光,這個實驗的環境配置主要參考搭建MIT6.S081的實驗環境
沒有很複雜,再參考一下官網對Ubuntu的描述即可。

實驗內容

在實驗之前

  • 可以參考實驗內容翻譯來閱讀
  • 需要閱讀參考書的Chapter 1
  • 該課程在學完CS:APP3e之後來,會比較流暢,許多前置知識已經瞭解了
  • 可能需要事先了解的UINX(系統)函式

lab Boot xv6 (easy)

  • 參照著啟動xv6就可以了,實驗環境搭建完之後沒啥難度

  • 注意clone下來的xv6-labs-2021資料夾存放在使用者資料夾當中,一般就在桌面的左上角

  • To quit qemu type: Ctrl-a x.

    • 關閉qemu的方式是:先同時按ctrl+a,然後按x

sleep (easy)

  • 功能:使shell睡眠

  • 對照hints來寫即可,但可能一開始不熟悉這種方式,寫起來有點迷惑是很正常的,慢慢搞,搞得下來的

  • 本題很簡單,呼叫系統函式sleep()即可,但要注意:

    • 傳入的指標陣列*argv[]儲存的是char型別,需要用 atoi()char轉換為int
  • 參考程式碼:

    #include"kernel/types.h"
    #include"kernel/stat.h"
    #include"user/user.h"
    
    int main(int agrc, char* agrv[]){
    	/* argc is the number of command, argv[] store the context of parameters */
    	if(agrc == 1){
    		printf("Please enter parameter of int");
    	}
    	else {
    		int time = atoi(agrv[1]);
    		sleep(time);
    	}
    	exit(0);
    }
    

pingpong (easy)

  • 功能:父子程式通過管道傳遞資訊

  • 這裡開始涉及到pipe(管道)的知識了,筆者一開始也是比較迷糊,但實際上理解之後就容易了

    • 呼叫pipe()函式即可建立管道,但注意要提前定義一個存放兩個int元素的陣列
      用來存放fd(file descriptor, 檔案描述符)

    • 這個管道是以半雙工的方式進行交流的,以防止出意外,也就是說,在使用管道讀之前,得關閉寫端;在使用管道寫之前,得關閉讀端。

      The fact that read blocks until it is impossible for new data to arrive is one reason that it’s important for the child to close the write end of the pipe before executing wc above: if one of wc ’s file descriptors referred to the write end of the pipe, wc would never see end-of-file.
      BOOK-RISCV-REV2 Chapter 1

      • 這裡建立了兩個通道,一個是child的,一個是parent的

        • 對於child,讀寫之前需要關閉父管道的寫端,子管道的讀端
        • 對於parent,讀寫之前需要關閉父管道的讀端,子管道的寫端
      • 靈魂圖示

  • 參考程式碼

    /* Half-duplex Communication */
    #include"kernel/types.h"
    #include"kernel/stat.h"
    #include"user/user.h"
    
    int main(int argc, char* argv[]){
    	
    	int child_pfd[2];
    	int parent_pfd[2];
    	char buf[4]; /* "ping"or"pong" have 4 char */
    	
    	pipe(child_pfd);
    	pipe(parent_pfd);
    	
    	/* child */
    	if(fork() == 0){
    		int child_pid = getpid();
    		
    		/* close write of parent and read of child */
    		close(parent_pfd[1]);
    		close(child_pfd[0]);
    		
    		/*2: received "ping" then child print */
    		read(parent_pfd[0], buf, sizeof(buf));
    		printf("%d: received %s\n", child_pid, buf);
    		
    		/*3: write 4 bytes to parent */
    		write(child_pfd[1], "pong", sizeof(buf));
    		
    		exit(0);
    	}
    	
    	/* parent */
    	else{
    		int parent_pid = getpid();
    		
    		/* close read of parent and write of child */
    		close(parent_pfd[0]);
    		close(child_pfd[1]);
    		
    		/*1: sent 4 bytes to child */
    		write(parent_pfd[1], "ping", sizeof(buf));
    		
    		/*4: received "pong" then parent print */
    		read(child_pfd[0], buf, sizeof(buf));
    		printf("%d: received %s\n", parent_pid, buf);
    		
    		wait(0);
    		exit(0);
    	}
    }
    

primes (moderate)/(hard)

  • 給一個2 ~ 35的陣列,輸出其中的所有素數(限制:必須使用通道)

  • 這道題使用迭代,難度比較大

    • 在子程式,孫程式,曾孫程式...中迭代,不斷地filter(過濾),每次過濾的結果可能是素數,也可能不是,但到最後一次的結果一定是素數。
    • 每次的numbers(結果)會傳遞給子程式用來繼續過濾,但每次我們只打印一個prime
    • 注意每次完成讀操作之後,要關閉讀端
  • 參考程式碼

    #include"kernel/types.h"
    #include"kernel/stat.h"
    #include"user/user.h"
    
    #define LIMIT 35
    #define INT_SIZE 4
    #define READ 0
    #define WRITE 1
    #define NUMBERLIMIT 34  /* real limit of number is (35 - 2 + 1) = 34 */
    #define UNPRIME 0       /* 0 is not prime */
    
    void print_and_filter_primes(int* fd);
    
    int main(){
    	int first_fd[2];
    	int numbers[NUMBERLIMIT];
    	
        /* build first_fd*/
    	pipe(first_fd);
    	
    	/* first child */
    	if (fork() == 0)
            print_and_filter_primes(first_fd);
    	
    	
    	/* first parent */
    	else{
            close(first_fd[READ]);
      		/* feed numbers */
        	for (int i = 2; i <= LIMIT; ++i)
           		numbers[i - 2] = i;	     
            write(first_fd[WRITE], &numbers, (NUMBERLIMIT) * INT_SIZE);
            close(first_fd[WRITE]);
    
            wait(0);
            exit(0);
    	}
    	return 0;
    }
    
    void print_and_filter_primes(int* fd){
        close(fd[WRITE]);
    
        int prime = UNPRIME;
        int numbers[LIMIT];
        int next_fd[2];
        int temp;
        int count = 0;
    
        read(fd[READ], (int *)&prime, INT_SIZE);
    
        if (prime == UNPRIME)
            exit(0);
            
        /* the first number => 2 => is_prime */
        /* each time only print a prime */
        printf("prime %d\n", prime);
    
        /* filter numbers as next input */
        /* the next numbers may prime or un_prime */
        /* but the last number is prime */
        while (read(fd[READ], &temp, INT_SIZE) > 0)
            if (temp % prime != 0) 
                numbers[count++] = temp;
      
        /* aviod fd overflow*/
        close(fd[READ]);
    	
        /* build next_fd*/
    	pipe(next_fd);	
    	
        /* next child */
    	if (fork() == 0){
            print_and_filter_primes(fd);
    	}
    
        /* next parent */
        else{
            close(next_fd[READ]);
            write(next_fd[WRITE], &numbers, count * INT_SIZE);
            close(next_fd[WRITE]);
    
            wait(0);
            exit(0);
        }
    }
    

find (moderate)

  • 功能:從根目錄(.)中找出所有目標檔案(name

  • 這道題的難度主要在於對ls.c的理解,要比較透徹才行

  • 修改的基本都在switch

    • 對於file,我們需要呼叫函式compare()後將結果路徑打印出來

      • 之所以要額外寫個函式,是因為要一次性滿足遞迴(recursion)下降,以便最後時一次性遞迴上升(可以理解為:在stack中一次性全部pop,之後一次性全部push,而不是一下pop,一下push)
      • compare()還需要輔助函式get_path_lastname()找出最後的名字來對比,該函式的主要思路是從最後一個char開始找到第一個slash('/')
        • eg ./a/c/b 中,b是最後一個char,從左數第三個/是對於b的第一個slash('/')
    • 對於directory,我們需要遞迴find繼續進入,同時解決兩個問題

      • 第一,組裝新的路徑,主要用到了追加函式memmove()和指標p,注意:路徑本身是字串,c風格的字串最後一個字元是'\0';標準庫函式strlen(s)可以返回字串引數s的長度,但長度不包括末尾的'\0'
      • 第二,防止死迴圈,不進入當前目錄.和父目錄..;節約時間,不進入空檔案de.inum == 0
  • 參考程式碼

    #include "kernel/types.h"
    #include "kernel/stat.h"
    #include "user/user.h"
    #include "kernel/fs.h"
    
    void find(char *path, char *name);
    char* get_path_lastname(char *path);
    void compare(char *path_lastname, char *name);
    
    int main(int argc, char *argv[]) {
    /*  // Input Error! 
        if (argc < 3){
            printf("Input Error!");
            exit(0);
        }
     */
        find(argv[1], argv[2]);
        exit(0);    
    }
    
    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:
            	/* empty dir is file(T_FILE)  */
                /* must use recursion in order to push and pop*/
                compare(path, name);
                break;
            case T_DIR:
                if (strlen(path) + 1 + DIRSIZ + 1 > sizeof(buf)){
                    printf("ls: 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 || strcmp(".", de.name) == 0 || strcmp("..", de.name) == 0)
                        continue;
                    
                    memmove(p, de.name, strlen(de.name)); 
                    p[strlen(de.name)] = '\0';
                    find(buf, name);
                }
                break;
        }
        close(fd);
    }
    
    char* get_path_lastname(char *path) {
        static char buf[DIRSIZ+1];
        char *p;
    
        /* order: from the last char to first slash('/') */
        for(p = path + strlen(path); p >= path && *p != '/'; p--)
            ;
        
        /* since p-- point to last slash('/') */
        /* p++ point to last char */
        p++;  
    
        // Return file name to cmp 
        if(strlen(p) >= DIRSIZ)
            return p;
        memmove(buf, p, sizeof(p));
        p[strlen(buf)] = '\0';
        return buf;
    }
    
    void compare(char *path, char *name){
        if(strcmp(get_path_lastname(path), name) == 0)
            printf("%s\n", path);
        return;
    }
    
    /* 
    $ make qemu
    ...
    init: starting sh
    $ echo > b      // build a dir called 'b' that 'b' is sub-dir of '.'
    $ mkdir a       // build a dir called 'a' that 'a' is sub-dir of '.'
    $ echo > a/b    // build a dir called 'b' that 'b' is sub-dir of 'a'
    $ find . b      // find file that name is 'b' from system dir('.') 
    ./a/b
    $
     */
    
    /* 
    hierarchy of dir 
    // Directory is a file containing a sequence of dirent structures.
    #define DIRSIZ 14
    
    struct dirent {
      ushort inum; // the numbers of dir containing
      char name[DIRSIZ];
    };
    */
    
    /* 
    status of dir or file or device
    #define T_DIR     1   // Directory
    #define T_FILE    2   // File
    #define T_DEVICE  3   // Device
    
    struct stat {
      int dev;     // File system's disk device
      uint ino;    // Inode number
      short type;  // Type of file
      short nlink; // Number of links to file
      uint64 size; // Size of file in bytes
    };
     */
    

xargs (moderate)

  • 功能:追加將要呼叫函式的引數,當遇到\n時就立刻執行

    • 參考連結:xargs()

    • eg1. xargs為將要呼叫的函式echo追加了引數hello too

      xargs echo bye
      hello too
      =>
      bye hello too
      
    • eg2. 先呼叫echo,它的輸出結果hello too作為xargs的輸入,成為將要呼叫的函式echo的追加引數

      echo hello too | xargs echo bye
      =>
      bye hello too
      
  • all_args_buffer儲存了所有引數

  • 我們傳遞給exec執行的是指標陣列的地址args_parray,裡面儲存了將要執行的引數

  • 當遇到\n執行完之後,要回歸原來的位置

    • eg. 當遇到\n執行完之後,要回歸原來的位置(echo

      echo "1\n2" | xargs -n 1 echo line
      =>
      line 1
      line 2
      
  • 參考程式碼

    #include"kernel/types.h"
    #include"kernel/stat.h"
    #include"user/user.h"
    #include "kernel/fs.h"
    #include "kernel/param.h"
    
    int main(int argc, char **argv) {
        int i, j;
        char *args_parray[MAXARG];
        char all_args_buffer[256];
        char *ptr_buffer = all_args_buffer;
        char *ptr_array =  ptr_buffer;
    
        for(i = 1, j = 0; i < argc; i++)
            args_parray[j++] = argv[i]; /* when loop break, j == argv - 1 */
    
        int length;
        int total_length = 0;
        while (read(0, ptr_buffer, MAXARG) > 0){ /* 0 is pipe of reading */
            total_length += length;
            if(total_length > 256){
                printf("the args is too long!");
                exit(0);
            }
    
            for(i = 0; i < length; ++i){
                if(ptr_buffer[i] == ' '){
                    ptr_buffer[i] = '\0'; /* c-style: the last of string has '\0' */
                    args_parray[j++] = ptr_array; /* add new args to args_parray */
                    ptr_array = &ptr_buffer[i + 1]; /* ptr_array point to next */
                }
                else if(ptr_buffer[i] == '\n'){
                    ptr_buffer[i] = '\0';
                    args_parray[j++] = ptr_array;
                    args_parray[j] = '\0'; /* as args of exec (c-style: string has '\0') */
                    ptr_array = &ptr_buffer[i + 1];
                    
                    if(fork() == 0){
                        exec(argv[1], args_parray);
                        exit(0);
                    }
    
                    j = argc - 1; /* comeback, eg: echo "1\n2" | xargs -n 1 echo line */
                    wait(0);
                }
            }
            ptr_buffer + length;
        }
        exit(0);
    }
    
  • 根據提示,我們還需要修改find(),以解決從.sh檔案中讀出的每條命令都加上$的問題。我們希望只有一個$,筆者目前沒有解決這個bug

總結

  • 完成日期22.3.8

  • 剛開學期間有許多雜事,返校需要自費核酸之類的,還有免修。勞累之後就得好好休息,就落下了

  • 難度目前沒有很大

  • 最近在聽《想和你漫步在午後》夏小調,《可風》黃建為

  • 自測試圖(筆者未解決關於time.txtbug