MIT6.S081 - Lab1: Xv6 and Unix utilities

来源:https://www.cnblogs.com/jll133688/p/18138393
-Advertisement-
Play Games

Part1:sleep 實驗要求與提示 可以參考 user/echo.c, user/grep.c 和 user/rm.c 文件 如果用戶忘記傳遞參數,sleep 應該列印一條錯誤消息 命令行參數傳遞時為字元串,可以使用 atoi 函數將字元串轉為數字 使用系統調用 sleep,有關實現 sleep ...


Part1:sleep

實驗要求與提示

  1. 可以參考 user/echo.c, user/grep.cuser/rm.c 文件
  2. 如果用戶忘記傳遞參數,sleep 應該列印一條錯誤消息
  3. 命令行參數傳遞時為字元串,可以使用 atoi 函數將字元串轉為數字
  4. 使用系統調用 sleep,有關實現 sleep 系統調用的內核代碼參考 kernel/sysproc.c(查找 sys_sleep),關於可以從用戶程式調用的 sleep 的 C 定義,參閱 user/user.h,以及 user/usys.S 表示從用戶跳轉到內核休眠的彙編代碼
  5. 確保 main 調用 exit() 以退出程式
  6. Makefile 中將 sleep 程式條件到 UPROGS 中,這樣可以使得 make qemu 能夠編譯程式,併在 xv6 shell 中運行

遇到的問題

問題一

  • 問題:運行 ./grade-lab-util sleep 顯示錯誤 /usr/bin/env: ‘python’: No such file or directory ,可能是沒裝 python2 或者裝的是 python3
  • 解決:grade-lab-util 文件第一行的 !/usr/bin/env python 改為 !/usr/bin/env python3

問題二

  • 問題:make qemu 後無法退出
  • 解決:輸入 ctrl+a 後抬起按鍵,然後再輸入 x

最終代碼

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

int main(int argc, char *argv[])
{
    if(argc < 2){  // 判斷用戶是否輸入了參數
        fprintf(2, "Usage: sleep has not input parameters!\n");  //將錯誤信息寫入到標準錯誤2
        exit(1);  // 非正常運行導致退出程式
    }
    sleep(atoi(argv[1]));   // 使用sleep系統調用,使用atoi將輸入的字元串轉為數字
    exit(0);    // 正常退出,註意這裡沒用return
}

註意要將 sleep 添加到 Makefile 的 UPROGS 中

  • 可以使用 ./grade-lab-util sleep 來進行打分,使用 make grade 可以給整個實驗打分

實驗思考

  1. 實現 sleep 比較容易,但是要掌握 sleepexitatoi 等的使用
  2. exit 和 return 的不同點:
  • exit(0):正常運行程式並退出程式

  • exit(1):非正常運行導致退出程式

  • return():返回函數,若在主函數中,則會退出函數並返回一值

    • return 返回函數值,是關鍵字;exit 是一個函數
    • return 是語言級別的,由 C 語言提供,它表示了調用堆棧的返回;而 exit 是系統調用級別的,是由操作系統提供的(或者函數庫中給出的),它表示了一個進程的結束
    • return函數的退出(返回);exit進程的退出
    • return 用於結束一個函數的執行,將函數的執行信息傳出個其他調用函數使用;exit 函數是退出應用程式,刪除進程使用的記憶體空間,並將應用程式的一個狀態返回給 OS,這個狀態標識了應用程式的一些運行信息,這個信息和操作系統有關,一般是 0 為正常退出,非 0 為非正常退出
    • 非主函數中調用 returnexit 效果很明顯,但是在 main 函數中調用 returnexit 的現象就很模糊,多數情況下現象都是一致的

Part2:pingpong

實驗要求與提示

  1. 調用一對管道(每個方向一個管道)在兩個進程間"ping-pong"傳遞一個位元組。父進程向子進程發送一個位元組,子進程輸出 <pid>: received ping,其中 <pid> 是它的進程 ID,然後子進程將位元組寫入管道,隨後退出,父進程從子進程讀取位元組,列印 <pid>: received pong,隨後退出
  2. 使用 pipe 創建一個管道;使用 fork 創建子進程;使用 read 從管道中讀數據,使用 write 將數據寫入到管道;使用 getpid 查找進程的 ID
  3. xv6 上的用戶程式中可供使用的庫函數可以在 user/user.h 中查看,它們的源代碼(除了用於系統調用)在 user/ulib.cuser/printf.cuser/umalloc.c

遇到的問題

問題一

  • 問題:VScode 中怎麼調試用戶程式

  • 配置:首先應該將 launch.json 中的 "stopAtEntry": 改為 true

  • 調試步驟:

    1. 點調試按鍵開啟調試,此時會停在 kernerl/main.c 的入口處
    2. 在調試控制台輸入 -exec file ./user/_filenamefilename 為需要調試的文件名稱
    3. 在終端輸入 filename ,點擊繼續開始調試的按鍵,然後就可以進入文件調試了
    4. 如果需要對該文件進行多次調試,直接在終端重新輸入 filename 就行

    註意:第一次調試某文件時,不要先設置斷點,有的地方設置斷點可能會導致進入不了該文件,等第一次調試之後再將斷點打在能變為紅色的地方

問題二

  • 問題:python 用多了,C 語言中關於字元串、指針的用法就有點模糊了,程式錯誤都是因為這裡
  • 解決:韋東山有個視頻是關於指針的,然後再找一個數組的視頻或文檔看一看

最終代碼

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

int main(int argc, char *argv[]){
    int p1[2];
    int p2[2];
    int pid;
    char recv1[64];
    char recv2[64];
    pipe(p1);
    pipe(p2);
    pid = fork();
    if(pid == 0){     // 子進程
        close(p1[1]);   // 關閉寫通道
        read(p1[0], recv1, sizeof("ping"));  // 等待父進程將數據寫入通道
        printf("%d: received %s\n", getpid(), recv1);
        close(p1[0]);
        close(p2[0]);
        write(p2[1], "pong", sizeof("pong"));
        close(p2[1]);
        exit(0);
    }else{            // 父進程
        close(p1[0]);   // 關閉寫通道
        write(p1[1], "ping", sizeof("ping"));  // 寫入通道
        close(p1[1]);
        close(p2[1]);
        read(p2[0], recv2, sizeof("pong"));
        printf("%d: received %s\n", getpid(), recv2);
        close(p2[1]);   
    }
    exit(0);
}

實驗思考

  1. 關於通道讀寫的過程一定要知道在什麼情況下會發生什麼,在不使用讀端或者寫端的時候一定要關閉,不然可能會造成自己被自己阻塞的現象
  2. 這個實驗實現起來比較簡單,但是能深挖的邏輯關係有很多,之後需要再進行複習,理清之間的關係

Part3: primes

實驗要求與提示

  1. 使用 pipe 和 fork 來設置管道,首先將數字 2 到 35 輸入管道。對於每個素數將安排創建一個進程,該進程通過一個管道從其左側鄰居讀取數據,並通過另一個管道向其右側鄰居寫入數據。由於 xv6 的文件描述符和進程數量有限,第一個進程可以在 35 時停止
  2. 要小心關閉進程不需要的文件描述符,否則程式將在第一個進程達到 35 之前耗盡 xv6 的資源
  3. 一旦第一個進程達到 35,它應該等到整個管道終止,包括所有的子進程、孫子進程等等。因此,主質數進程應該只在所有輸出都列印出來之後退出,並且在所有其他質數進程都退出之後退出
  4. 當管道的寫端關閉時,read 返回零
  5. 最簡單的方法是直接將 32 位(4 位元組)整數寫入管道,而不是使用格式化的 ASCII I/O
  6. 僅在需要時在管道中創建進程

最終代碼

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

#define WRITE 1
#define READ 0
void primeprocess(int p[]){
    int first_num;
    close(p[WRITE]);
    if(read(p[READ], &first_num, sizeof(first_num)) == 0){  // 遞歸終止條件,讀不到數據
        close(p[READ]);
        exit(0);   
    }
    printf("prime %d\n", first_num);  // 第一個進入管道的肯定是素數
    int p_child[2];
    pipe(p_child);                  // 創建下一個pipe
    int pid = fork();
    if(pid == 0){                   // 子進程
        primeprocess(p_child);      // 遞歸函數
    }else{                          // 父進程
        int num; 
        close(p_child[READ]);      
        while(read(p[READ], &num, sizeof(num)) != 0){
            if(num % first_num != 0){
                write(p_child[WRITE], &num, sizeof(num));
            }
        }
        close(p[READ]);
        close(p_child[WRITE]);
        wait(0);   // 需要等待子進程退出才能退出
    }
    exit(0);   // 子進程結束
}
int main(int argc, char *argv[]){
    int p[2];
    pipe(p);
    int pid = fork();
    if(pid == 0){    // 子進程
        primeprocess(p);
    }else{
        close(p[READ]);
        for(int i = 2; i < 36; i++){
            write(p[WRITE], &i, sizeof(i));  // 註意這裡是將i的地址給write函數
        }
        close(p[WRITE]);
        wait(0);
    }
    exit(0);
}

實驗思考

  1. 這道題關鍵在於理解問題所表達的意思,用遞歸的方法主要是因為父進程需等待子進程退出,不過遞歸的思路比較簡單
  2. 註意 write(p[WRITE], &i, sizeof(i)) 中是傳遞的 i 的地址

Part4: find

實驗要求與提示

  1. 查看 user/ls.c 瞭解如何讀取目錄
  2. 使用遞歸查找子目錄,但除去"."和".."
  3. 對文件系統的更改在 qemu 運行期間持續存在;要獲得一個乾凈的文件系統,請運行 make clean,然後運行 qemu
  4. 需要使用 C 字元串,註意比較字元串不能像 python 一樣直接 ==,而是應該用 strcmp()

遇到的問題

問題一

  • 問題:不太熟悉 find 函數的使用,不知道它後面都帶能帶哪些函數
  • 解決:這個實驗僅僅是實現了 find 函數的部分功能,它的語法為 find [路徑] [匹配條件] [動作] ,之後可以再嘗試實現它裡面更多的功能

最終代碼

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

char* fmtname(char *path)
{
  char *p;
  // 查找末尾斜杠後的第一個字元
  for(p=path+strlen(path); p >= path && *p != '/'; p--);
  p++;
  return p;
}

void find(char *path, char *target)
{
  char buf[512], *p;
  int fd;
  struct dirent de;  // 記錄文件首碼
  struct stat st;    // inode

  if((fd = open(path, O_RDONLY)) < 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:   // 文件
      if(strcmp(fmtname(path), target) == 0)
        printf("%s\n", path);
      break;

    case T_DIR:    // 目錄
      if(strlen(path) + 1 + DIRSIZ + 1 > sizeof(buf)){
        printf("ls: path too long\n");
        break;
      }
      strcpy(buf, path);    // 複製path到buf里
      p = buf+strlen(buf);  // 將p指向buf的末尾 
      *p++ = '/';           // 將buf的末尾添加/,從a/b變為a/b/
      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, DIRSIZ);  // 合併文件為a/b/de.name
        p[DIRSIZ] = 0;   // 結束字元串
        if(stat(buf, &st) < 0){
          printf("find: cannot stat %s\n", buf);
          continue;
        }
        find(buf, target);   // 遞歸,從開始路徑一直往深處查找文件
      }
      break;
    }
  close(fd);
}

int main(int argc, char *argv[])
{
  if(argc != 3){
    printf("Usage: find <dirName> <fileName>\n");
    exit(1);
  }
  find(argv[1], argv[2]);
  exit(0);
}

實驗思考

  1. read(fd, &de, sizeof(de)) 是讀取文件的方法,其中 struct dirent de 用來記錄文件首碼,它的結構體如下:
struct dirent {
  ushort inum;
  char name[DIRSIZ];
};
  1. 這道題在 user/ls.c 的基礎上進行修改,但要註意在文件判斷時,要排除 "."".." 的情況,它們不能進入遞歸

Part5: xargs

實驗要求與提示

  1. 使用 forkexec 對每一行輸入調用命令。在父進程中使用 wait 來等待子進程完成命令
  2. 要讀取單獨的輸入行,每次讀取一個字元,直到出現換行符 '\n'
  3. kernel/param.h 聲明 MAXARG,如果需要聲明 argv 數組,這可能很有用。

最終代碼

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
#include "kernel/fcntl.h"
#include "kernel/param.h"
#define MAXBUF 1024
int main(int argc, char *argv[]){
    char *xargs_argv[MAXARG];   // 字元串數組
    char buf[MAXBUF];           // 字元數組
    int i;
    if(argc < 2){
        printf("Usage: xargs <command>\n");
        exit(1);
    }

    for(i = 0; i < argc; i++){
        xargs_argv[i - 1] = argv[i];  // argv裡面為管道|後面的輸入,字元串數組
    }
    while(1){
        int index = 0;   // buf寫入位元組順序
        int buf_index = 0;  // buf遇到' '或'\n'的首地址
        int xargs_index = argc - 1;
        int re;    // read返回值
        char ch;   // 讀到的一個位元組
        while(1){
            re = read(0, &ch, sizeof(ch));   // 讀取shell標準輸入的一個位元組
            if(re == 0){
                exit(0);      // 表示沒有讀到位元組,結束程式(這裡是程式正常結束的唯一齣口)
            }
            if(ch == ' ' || ch == '\n'){
                buf[index++] = '\0';
                xargs_argv[xargs_index++] = &buf[buf_index];   //將buf當前的字元串傳給xargs_argv
                buf_index = index;       // 更新buf當前命令首地址
                if(ch == '\n')break;     // 跳出迴圈,執行一行命令
            }else{
                buf[index++] = ch;
            }
        }
        xargs_argv[xargs_index] = (char *)0;   // 結束一行命令
        int pid = fork();
        if(pid == 0){    // 子程式
            exec(xargs_argv[0], xargs_argv);
        }else{
            wait((int *) 0);  //等待子程式執行完畢
        }
    }
    exit(0);   
}

實驗思考

  1. 這道題主要是要理解 xargs 的用法以及靈活使用指針和數組,其中字元串數組和字元數組的用法要區分清楚
  2. 可以用 '\0' 來標記字元串的結束
  3. argv 的字元串只包括了管道最後一個輸入,這裡是整個代碼的關鍵

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在處理大型Excel工作簿時,有時候我們需要在工作表中凍結窗格,這樣可以在滾動查看數據的同時保持某些行或列固定不動。凍結窗格可以幫助我們更容易地導航和理解複雜的數據集。相反,當你不需要凍結窗格時,你可能需要解凍它們以獲得完整的視野。 下麵將介紹如何使用免費.NET庫通過C#實現凍結Excel視窗以鎖 ...
  • 在 Avalonia 中,樣式是定義控制項外觀的一種方式,而控制項主題則是一組樣式和資源,用於定義應用程式的整體外觀和感覺。本文將深入探討這些概念,並提供示例代碼以幫助您更好地理解它們。 樣式是什麼? 樣式是一組屬性,用於定義控制項的外觀。它們可以包括背景色、邊框、字體樣式等。在 Avalonia 中,樣 ...
  • 引言 上一篇我們創建了一個Sample.Api項目和Sample.Repository,並且帶大家熟悉了一下Moq的概念,這一章我們來實戰一下在xUnit項目使用依賴註入。 Xunit.DependencyInjection Xunit.DependencyInjection 是一個用於 xUnit ...
  • 老周在幾個世紀前曾寫過樹莓派相關的 iOT 水文,之所以沒寫 Nano Framework 相關的內容,是因為那時候這貨還不成熟,可玩性不高。不過,這貨現在已經相對完善,老周都把它用在項目上了——第一個是自製的智能插座,這個某寶上50多塊可以買到,搜“esp32 插座”就能找到。一種是 86 型盒子 ...
  • JWT(JSON Web Token)是一種用於在網路應用之間傳遞信息的開放標準(RFC 7519)。它使用 JSON 對象在安全可靠的方式下傳遞信息,通常用於身份驗證和信息交換。 在Web API中,JWT通常用於對用戶進行身份驗證和授權。當用戶登錄成功後,伺服器會生成一個Token並返回給客戶端 ...
  • 目錄 目錄目錄基礎指令Linux命令基本格式文件操作文件格式文件許可權創建文件查看文件刪除文件移動文件複製文件編輯文件查找文件查找命令路徑vim文本編輯器一般指令模式(command mode)編輯模式(insert mode)指令列命令模式command-line mode目錄操作列印路徑查看目錄切 ...
  • 1、安裝Docker Centos7.6-centos7.9 # 配置主機名: hostnamectl set-hostname master1 && bash #關閉防火牆 systemctl stop firewalld && systemctl disable firewalld #關閉ipt ...
  • Linux 的重要性不用我多說了吧,大多數互聯網公司,伺服器都是採用的Linux操作系統 Linux是一個主要通過命令行來進行管理的操作系統。 只有熟練掌握Linux核心命令,在使用起來我們才會得心應手 這裡給大家整理了Linux一些核心命令,掌握這些核心命令,工作中應該游刃有餘了 一、腦圖 二、詳 ...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...