CSAPP:lab7 shell

来源:https://www.cnblogs.com/world-explorer/archive/2022/04/16/16154725.html
-Advertisement-
Play Games

實驗網站 課程網站:CSAPP 源碼下載 源碼下載 實驗文檔下載 我的實驗環境:Ubuntu 20.04 lab7文檔解讀 ​ 查看 tsh.c (tiny shell) 文件,您會看到它包含一個簡單的 Unix shell 的功能骨架。為了幫助您入門,我們已經實現了不太有趣的功能。你的任務是完成下 ...


實驗網站

課程網站:CSAPP

源碼下載

源碼下載

實驗文檔下載

我的實驗環境:Ubuntu 20.04

lab7文檔解讀

​ 查看 tsh.c (tiny shell) 文件,您會看到它包含一個簡單的 Unix shell 的功能骨架。為了幫助您入門,我們已經實現了不太有趣的功能。你的任務是完成下麵列出的剩餘的空函數。作為對您的健全性檢查,我們在參考解決方案中列出了每個函數的大致代碼行數(其中包含大量註釋)。

  • eval: 解析和解釋命令行的主要常式。[70行]
  • builtin cmd:識別和解釋內置命令:quit、fg、bg和jobs。[25行]
  • do bgfg:實現bg和fg內置命令。[50行]
  • waitfg: 等待前臺作業完成。[20行]
  • sigchld handler: 捕獲SIGCHILD信號。[80行]
  • sigint handler: 捕獲SIGINT (ctrl-c) 信號。[15行]
  • sigtstp handler: 捕獲SIGTSTP (ctrl-z) 信號。[15行]

Unix Shell 概述

shell 是一個互動式命令行解釋器,它代表用戶運行程式。 shell 反覆列印提示,等待 stdin 上的命令行,然後按照命令行內容的指示執行一些操作。

命令行是由空格分隔的 ASCII 文本單詞序列。命令行中的第一個單詞要麼是內置命令的名稱,要麼是可執行文件的路徑名。剩下的詞是命令行參數。如果第一個單詞是內置命令,shell 會立即執行當前進程中的命令。否則,該詞被假定為可執行程式的路徑名。在這種情況下,shell 會派生一個子進程,然後在子進程的上下文中載入和運行程式。由於解釋單個命令行而創建的子進程統稱為作業。一般來說,一個作業可以由多個通過 Unix 管道連接的子進程組成。

如果命令行以 & 符號結尾,則作業在後臺運行,這意味著 shell 在列印提示符並等待下一個命令行之前不會等待作業終止。否則,作業在前臺運行,這意味著 shell 在等待下一個命令行之前等待作業終止。因此,在任何時間點,最多可以有一個作業在前臺運行。但是,可以在後臺運行任意數量的作業。例如,鍵入命令行

tsh> jobs

使shell執行內置的jobs命令。鍵入命令行T

tsh> /bin/ls -l -d

在前臺運行ls程式。按照慣例,shell確保程式開始執行其主常式時

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

argc和argv參數具有以下值:

argc == 3,

argv[0] == ‘‘/bin/ls’’,

argv[1]== ‘‘-l’’,

argv[2]== ‘‘-d’’.

或者,鍵入命令行

tsh> /bin/ls -l -d &

在後臺運行ls程式。

Unix shell 支持作業控制的概念,它允許用戶在後臺和前臺之間來回移動作業,並更改作業中進程的進程狀態(運行、停止或終止)。鍵入 ctrl-c 會導致將 SIGINT 信號傳遞給前臺作業中的每個進程。 SIGINT 的預設操作是終止進程。同樣,鍵入 ctrl-z 會導致將 SIGTSTP 信號傳遞給前臺作業中的每個進程。 SIGTSTP 的預設操作是將進程置於停止狀態,直到它被接收到 SIGCONT 信號喚醒為止。 Unix shell 還提供了各種支持作業控制的內置命令。例如:

• 作業:列出正在運行和已停止的後臺作業。

• bg :將已停止的後臺作業更改為正在運行的後臺作業。

• fg :將已停止或正在運行的後臺作業更改為在前臺運行。

• kill :終止作業。

tsh規範

您的tsh-shell應具有以下功能:

  • 提示應該是字元串“tsh>”。
  • 用戶鍵入的命令行應由名稱和零個或多個參數組成,所有參數均由一個或多個空格分隔。如果name是內置命令,那麼tsh應該立即處理它,並等待下一個命令行。否則,tsh應該假設name是可執行文件的路徑,它在初始子進程的上下文中載入和運行 (在這種情況下,術語job指的是這個初始子進程)。
  • tsh 不需要支持管道 (|) 或 I/O 重定向(< 和 >)。
  • 鍵入 ctrl-c (ctrl-z) 應該會導致將 SIGINT (SIGTSTP) 信號發送到當前前臺作業,以及該作業的任何後代(例如,它派生的任何子進程)。如果沒有前臺工作,那麼信號應該沒有效果。
  • 如果命令行以 & 符號結尾,那麼 tsh 應該在後臺運行該作業。否則,它應該在前臺運行作業。
  • 每個作業都可以通過進程 ID (PID) 或作業 ID (JID) 來標識,這是一個由 tsh 分配的正整數。 JID 應該在命令行上用首碼 '%' 表示。例如,“%5”表示 JID 5,“5”表示 PID 5。(我們為您提供了操作作業列表所需的所有常式。)
  • tsh應該支持以下內置命令:
    • quit命令終止shell。
    • job命令列出所有後臺運行的工作。
    • bg 命令重新啟動<工作>通過發送SIGCONT,然後在後臺運行。
    • 參數可以是一個PID或JID。
    • fg 命令重新啟動<工作>通過發送SIGCONT,然後在前臺運行它。
    • 參數可以是一個PID或JID。
    • tsh 應該收穫它所有的僵屍孩子。如果任何作業因為接收到它沒有捕獲的信號而終止,則 tsh 應該識別此事件並列印一條帶有作業 PID 和違規信號描述的消息。

Checking your work

我們提供了一些工具來幫助您檢查您的工作。 在運行任何可執行程式之前,請確保它具有執行許可權。 如果沒有,使用“chmod +x”給它執行許可權

**Reference solution. ** Linux 可執行文件 tshref 是 shell 的參考解決方案。 運行這個程式來解決你對 shell 應該如何工作的任何問題。 您的 shell 應該發出與參考解決方案相同的輸出(當然,PID 除外,它在運行之間會發生變化)。

Shell driver sdriver.pl 程式將 shell 作為子進程執行,按照跟蹤文件的指示向其發送命令和信號,並捕獲並顯示 shell 的輸出。

unix> ./sdriver.pl -h
Usage: sdriver.pl [-hv] -t <trace> -s <shellprog> -a <args>

選項:

-h 列印此消息

-v 更詳細

-t 跟蹤文件

-s 用於測試的 Shell 程式

-a Shell 參數

-g 為自動評分器生成輸出

我們還提供了16個跟蹤文件(trace{01-16}.txt),您將與shell驅動程式一起使用這些文件來測試shell的正確性。編號較低的跟蹤文件執行非常簡單的測試,編號較高的測試執行更複雜的測試。

您可以使用跟蹤文件 trace01.txt(例如)在您的 shell 上運行 shell 驅動程式,方法是鍵入:

unix> ./sdriver.pl -t trace01.txt -s ./tsh -a "-p"

(-a “-p” 參數告訴您的shell不要發出提示),或

unix> make test01

同樣,要將您的結果與參考 shell 進行比較,您可以通過鍵入以下內容在參考 shell 上運行跟蹤驅動程式:

unix> ./sdriver.pl -t trace01.txt -s ./tshref -a "-p"
or
unix> make rtest01

供您參考,tshref.out 提供了所有比賽的參考解決方案的輸出。這可能比在所有跟蹤文件上手動運行 shell 驅動程式更方便。

關於跟蹤文件的整潔的事情是,如果您以交互方式運行shell,它們會生成相同的輸出 (除了標識跟蹤的初始註釋)。例如:

bass> make test15
./sdriver.pl -t trace15.txt -s ./tsh -a "-p"
#
# trace15.txt - Putting it all together
#
tsh> ./bogus
./bogus: Command not found.
tsh> ./myspin 10
Job (9721) terminated by signal 2
tsh> ./myspin 3 &
[1] (9723) ./myspin 3 &
tsh> ./myspin 4 &
[2] (9725) ./myspin 4 &
tsh> jobs
[1] (9723) Running ./myspin 3 &
[2] (9725) Running ./myspin 4 &
tsh> fg %1
Job [1] (9723) stopped by signal 20
tsh> jobs
[1] (9723) Stopped ./myspin 3 &
[2] (9725) Running ./myspin 4 &
tsh> bg %3
5
%3: No such job
tsh> bg %1
[1] (9723) ./myspin 3 &
tsh> jobs
[1] (9723) Running ./myspin 3 &
[2] (9725) Running ./myspin 4 &
tsh> fg %1
tsh> quit
bass>

Hints

  • 閱讀教科書中第 8 章(異常控制流)的每一個字。

  • 使用跟蹤文件來指導您的 shell 的開發。 從 trace01.txt 開始,確保您的 shell 產生與參考 shell 相同的輸出。 然後繼續跟蹤文件 trace02.txt,依此類推。

  • waitpid、kill、fork、execve、setpgid 和 sigprocmask 函數會派上用場。 waitpid 的 WUNTRACED 和 WNOHANG 選項也很有用。

  • 當您實現信號處理程式時,請確保將 SIGINT 和 SIGTSTP 信號發送到整個前臺進程組,在 kill 函數的參數中使用“-pid”而不是“pid”。 sdriver.pl 程式測試此錯誤。

  • 實驗室的一個棘手部分是決定waitfg 和sigchld 處理函數之間的工作分配。 我們推薦以下方法:

    • 在 waitfg 中,圍繞 sleep 函數使用busy loop。
    • 在 sigchld 處理程式中,只調用一次 waitpid。
  • 雖然其他解決方案是可能的,例如在 waitfg 和 sigchld 處理程式中調用 waitpid,但這些可能會非常令人困惑。 在處理程式中進行所有收穫更簡單。

  • 在 eval 中,父級必須在分叉子級之前使用 sigprocmask 阻止 SIGCHLD 信號,然後解除阻塞這些信號,在通過調用 addjob 將子級添加到作業列表後再次使用 sigprocmask。由於子進程繼承了父進程的阻塞向量,因此子進程必須確保在執行新程式之前解除阻塞 SIGCHLD 信號。

    父級需要以這種方式阻止 SIGCHLD 信號,以避免在父級調用 addjob 之前子級被 sigchld 處理程式收割(並因此從作業列表中刪除)的競爭條件。

  • 諸如 more、less、vi 和 emacs 之類的程式在終端設置上會做一些奇怪的事情。 不要從你的 shell 運行這些程式。 堅持使用簡單的基於文本的程式,例如 /bin/ls、/bin/ps 和 /bin/echo。

  • 當您從標準Unix shell運行您的shell時,您的shell正在前臺進程組中運行。如果您的shell隨後創建了一個子進程,則預設情況下,該子進程也將是前臺進程組的成員。由於鍵入ctrl-c會向前臺組中的每個進程發送一個SIGINT,因此鍵入ctrl-c會向您的shell以及您的shell創建的每個進程發送一個SIGINT,這顯然是不正確的。

    解決方法如下:在fork之後,但在execve之前,子進程應該調用setpgid(0,0),這會將子進程放入一個新的進程組中,其組ID與子進程的PID相同。這將確保前臺進程組中只有一個進程,即您的shell。當您鍵入ctrl-c時,shell應該捕獲生成的SIGINT,然後將其轉發到相應的前臺作業(或者更準確地說,是包含前臺作業的進程組)。

課本實驗相關內容複習

讀書筆記CSAPP:19[VB]ECF:信號和非本地跳轉

進程

我們可通過調用以下函數來等待子進程的終止或停止,父進程會得到被回收的子進程PID,且內核會刪除僵死進程

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statusp, int options); 
  • 等待集合pid

    • 如果pid>0,則等待集合就是一個單獨的子進程
      • 如果pid=-1,則等待集合就是該進程的所有子進程
      • 註意:當父進程創造了許多子進程,這裡通過pid=-1進行回收時,子程式的回收順序是不確定的,並不會按照父進程生成子進程的順序進行回收。可通過按順序保存子進程的PID,然後按順序指定pid參數來消除這種不確定性。
  • 等待行為options

    • 0預設選項,則會掛起當前進程,直到等待集合中的一個子進程終止,則函數返回該子進程的PID。此時,已終止的子進程已被回收。
      • WNOHANG如果等待子進程終止的同時還向做其他工作,該選項會立即返回,如果子進程終止,則返回該子進程的PID,否則返回0。
      • WUNTRACED當子進程被終止或暫停時,都會返回。
      • WCONTINUED掛起當前進程,知道等待集合中一個正在運行的子進程被終止,或停止的子進程收到SIGCONT信號重新開始運行。
      • 註意:這些選項可通過|合併。
  • 如果statusp非空,則waitpid函數會將子進程的狀態信息放在statusp中,可通過wait.h中定義的巨集進行解析

    • WIFEXITED(statusp)如果子進程通過調用exitreturn正常終止,則返回真,。此時可通過WEXITSTATUS(statusp)獲得退出狀態。
      • WIFSIGNALED(status)如果子進程是因為一個未捕獲的信號終止的,則返回真。此時可通過WTERMSIG(statusp)獲得該信號的編號。
      • WIFSTOPPED(statusp)如果引起函數返回的子進程是停止的,則返回真。此時可通過WSTOPSIG(statusp)獲得引起子進程停止的信號編號。
      • WIFCONTINUED(statusp)如果子進程收到SIGCONT信號重新運行,則返回真。
  • 如果當前進程沒有子進程,則waitpid返回-1,並設置errnoECHILD,如果waitpid函數被信號中斷,則返回-1,並設置errnoEINTR。否則返回被回收的子進程PID。

註意:waitpid通過設置options來決定是否回收停止的子進程。並且能通過statusp來判斷進程終止或停止的原因。

有個簡化的waitpid函數

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *statusp);

調用wait(&status)等價於調用waitpid(-1, &status, 0)

註意:當調用waitpid函數之前,就有子進程被終止或停止,一調用waitpid函數就會馬上將該子進程回收。

8.5.2發送信號

#include <unistd.h>
pid_t getpgrp(void); //返回所在的進程組
int setpgip(pid_t pid, pid_t pgid); //設置進程組
/* 
 * 如果pid大於零,就使用進程pid;如果pid等於0,就使用當前進程的PID。
 * 如果pgid大於0,就將對應的進程組ID設置為pgid;如果pgid等於0,就用pid指向的進程的PID作為進程組ID
 */ 
  • /bin/kill向進程發送任意信號
/bin/kill [-信號編號] id  

id>0時,表示將信號傳遞給PID為id的進程;當id<0時,表示將信號傳遞給進程組ID為|id|的所有進程。

  • 從鍵盤發送信號

通過鍵盤上輸入Ctrl+C會使得內核發送一個SIGINT信號到前臺進程組中的所有進程,終止前臺作業;通過輸入Ctrl+Z會發送一個SIGTSTP信號到前臺進程組的所有進程,停止前臺作業,直到該進程收到SIGCONT信號。

  • kill函數發送信號

可以在函數中調用kill函數來對目的進程發送信號

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig); 

pid>0時,會將信號sig發送給進程pid;當pid=0時,會將信號sig發送給當前進程所在進程組的所有進程;當pid<0時,會將信號sig發送給進程組ID為|pid|的所有進程

  • alarm函數發送SIGALARM信號
#include <unistd.h>
unsigned int alarm(unsigned int secs); 

alarm函數時,會取消待處理的鬧鐘,返回待處理鬧鐘剩下的時間,併在secs秒後發送一個SIGALARM信號給當前進程。

8.5.3接受信號

每種信號類型具有以下一種預定的預設行為:

  • 進程終止
  • 進程終止並dumps core
  • 進程掛起直到被SIGCONT信號重啟
  • 進程忽略信號

可以通過signal函數來修改信號的預設行為,但是無法修改SIGSTOPSIGKILL信號的預設行為

#include <signal.h>
typedef void (*sighandler_t)(int); 
sighandler_t signal(int signum, sighandler_t handler);
  • signum為信號編號,可以直接輸入信號名稱
  • handler為我們想要對信號signum採取的行為
  • handlerSIG_IGN,表示要進程忽略該信號
  • handlerSIG_DFL,表示要恢復該信號的預設行為
  • handler為用戶自定義的信號處理程式地址,則會調用該函數來處理該信號,該函數原型為void signal_handler(int sig);。調用信號處理程式稱為捕獲信號,置信信號處理程式稱為處理信號。當信號處理程式返回時,會將控制傳遞迴邏輯流中的下一條指令。註意:信號處理程式可以被別的信號處理程式中斷。
  • signal函數執行成功,則返回之前signal handler的值,否則返回SIG_ERR

8.5.4 阻塞信號和解除阻塞信號P532

Linux提供阻塞信號的隱式和顯示的機制:

  • 隱式阻塞機制:內核預設阻塞當前正在處理信號類型的待處理信號。
  • 顯示阻塞機制:應用程式通過sigprocmask函數來顯示阻塞和解阻塞選定的信號。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • 通過how來決定如何改變阻塞的信號集合blocked

    • how=SIG_BLOCK時,blocked = blocked | set
      • how=SIG_UNBLOCK時,blocked = blocked & ~set
      • how=SETMASK時,block = set
  • 如果oldset非空,則會將原始的blocked值保存在oldset中,用於恢複原始的阻塞信號集合

這裡還提供一些額外的函數來對set信號集合進行操作

#include <signal.h>
int sigemptyset(sigset_t *set); //初始化set為空集合
int sigfillset(sigset_t *set); //把每個信號都添加到set中
int sigaddset(sigset_t *set, int signum); //將signum信號添加到set中
int sigdelset(sigset_t *set, int signum); //將signum從set中刪除
int sigismember(const sigset_t *set, int signum); //如果signum是set中的成員,則返回1,否則返回0

以下是一個使用例子

img

以上執行內部函數時,就不會接收到SIGINT信號,即不會被Ctrl+C終止。

通過阻塞信號來消除函數衝突,或者保證程式運行邏輯正確。

顯示等待信號

當我們想要主進程顯示等待某個信號時,可以用以下代碼

img

這裡主進程會顯示等待子進程被回收,這裡使用了sigsuspend(&mask)函數,它等價於

sigprocmask(SIG_SETMASK, &mask, &prev);
pause();
sigprocmask(SIG_SETMASK, &prev, NULL); 

但是它是這三條代碼的原子版本,即第一行和第二行是一起調用的,則SIGCHLD信號不會出現在第一行和第二行之間,造成程式不會停止。

註意:第26行要先對SIGCHLD信號進行阻塞,防止過早發送給主進程,則pause函數就無法中斷,就會使得程式不會停止。

Reference output:
#
# trace14.txt - Simple error handling
#
tsh> ./bogus
./bogus: Command not found
tsh> ./myspin 4 &
[1] (42087) ./myspin 4 &
tsh> fg
fg command requires PID or %jobid argument
tsh> bg
bg command requires PID or %jobid argument
tsh> fg a
fg: argument must be a PID or %jobid
tsh> bg a
bg: argument must be a PID or %jobid
tsh> fg 9999999
(9999999): No such process
tsh> bg 9999999
(9999999): No such process
tsh> fg %2
%2: No such job
tsh> fg %1
Job [1] (42087) stopped by signal 20
tsh> bg %2
%2: No such job
tsh> bg %1
[1] (42087) ./myspin 4 &
tsh> jobs
[1] (42087) Running ./myspin 4 &
Student's output:
#
# trace14.txt - Simple error handling
#
tsh> ./bogus
./bogus: Command not found.
tsh> ./myspin 4 &
[1] (42141) ./myspin 4 &
tsh> fg
fg command requires PID or %jobid argument
tsh> bg
bg command requires PID or %jobid argument
tsh> fg a
fg: argument must be a PID or %jobid
tsh> bg a
bg: argument must be a PID or %jobid
tsh> fg 9999999
(9999999): No such process
tsh> bg 9999999
(9999999): No such process
tsh> fg %2
%2: No such job
tsh> fg %1
Job [1] (42141) stopped by signal 20
tsh> bg %2
%2: No such job
tsh> bg %1
[1] (42141) ./myspin 4 &
tsh> jobs
[1] (42141) Running ./myspin 4 &

Checking trace15.txt...
Reference output:
#
# trace15.txt - Putting it all together
#
tsh> ./bogus
./bogus: Command not found
tsh> ./myspin 10
Job [1] (42182) terminated by signal 2
tsh> ./myspin 3 &
[1] (42210) ./myspin 3 &
tsh> ./myspin 4 &
[2] (42213) ./myspin 4 &
tsh> jobs
[1] (42210) Running ./myspin 3 &
[2] (42213) Running ./myspin 4 &
tsh> fg %1
Job [1] (42210) stopped by signal 20
tsh> jobs
[1] (42210) Stopped ./myspin 3 &
[2] (42213) Running ./myspin 4 &
tsh> bg %3
%3: No such job
tsh> bg %1
[1] (42210) ./myspin 3 &
tsh> jobs
[1] (42210) Running ./myspin 3 &
[2] (42213) Running ./myspin 4 &
tsh> fg %1
tsh> quit
Student's output:
#
# trace15.txt - Putting it all together
#
tsh> ./bogus
./bogus: Command not found.
tsh> ./myspin 10
Job [1] (42255) terminated by signal 2
tsh> ./myspin 3 &
[1] (42269) ./myspin 3 &
tsh> ./myspin 4 &
[2] (42271) ./myspin 4 &
tsh> jobs
[1] (42269) Running ./myspin 3 &
[2] (42271) Running ./myspin 4 &
tsh> fg %1
Job [1] (42269) stopped by signal 20
tsh> jobs
[1] (42269) Stopped ./myspin 3 &
[2] (42271) Running ./myspin 4 &
tsh> bg %3
%3: No such job
tsh> bg %1
[1] (42269) ./myspin 3 &
tsh> jobs
[1] (42269) Running ./myspin 3 &
[2] (42271) Running ./myspin 4 &
tsh> fg %1
tsh> quit

主要任務

需要實現的命令

void eval(char *cmdline);
int builtin_cmd(char **argv);
void do_bgfg(char **argv);
void waitfg(pid_t pid);

void sigchld_handler(int sig);
void sigtstp_handler(int sig);
void sigint_handler(int sig);

按測試順序實現

make test01//通過
make test02//需要實現quit
  1. 實現int builtin_cmd(char **argv)

    int builtin_cmd(char **argv)
    {   
        if(!strcmp(argv[0],"quit")){
            exit(0);
        }
        else if(!strcmp(argv[0],"jobs")){    /*需要防止衝突*/
            sigset_t mask, prev_mask;
            sigfillset(&mask);
            sigprocmask(SIG_BLOCK, &mask, &prev_mask);
            listjobs(jobs);
            sigprocmask(SIG_SETMASK, &prev_mask, NULL); 
            return 1;                   /*返回非0*/
            
        }
        else if(!strcmp(argv[0], "bg")){    /*需要防止衝突*/
           do_bgfg(argv);
            return 1;                   /*返回非0*/
            
        }
        else if(!strcmp(argv[0], "fg")){    /*需要防止衝突*/
           do_bgfg(argv);
            return 1;                   /*返回非0*/
            
        }
        else return 0;     /* not a builtin command */
    }
    
  2. make test03
    make test04
    make test05		//jobs指令執行失敗
        			//需完善void eval(char *cmdline)
    
  3. 完善void eval(char *cmdline)

    //一開始直接參考書上P525簡單shell,jobs指令失敗
    //
    void eval(char *cmdline) 
    {
       char *argv[MAXARGS]; /* Argument list execve() */
       char buf[MAXLINE];   /* Holds modified command line */
       int bg;              /* Should the job run in bg or fg? */
       pid_t pid;           /* Process id */
    
       strcpy(buf,cmdline);
       bg = parseline(buf, argv);      /*後臺執為true*/
       if (argv[0] == NULL)
           return;   /* Ignore empty lines */
       if (!builtin_cmd(argv)) {       /* 執行內置指令 */
           if ((pid = fork()) == 0) {      /* fork產生子進程執行指令 */
               if (execve(argv[0], argv, environ) < 0) {
                   printf("%s: Command not found.\n", argv[0]);
                   exit(0);
               }
           }
    
           /* Parent waits for foreground job to terminate */
    if (!bg) {
               int status;
               if (waitpid(pid, &status, 0) < 0)
                   unix_error("waitfg: waitpid error");
           }
           else
               printf("%d %s", pid, cmdline);
       }
       return;
    }
    

    檢查知上面的eval缺少添加jobs,進一步添加信號阻塞和addjobs

    /* 
     * eval - Evaluate the command line that the user has just typed in
     * 
     * If the user has requested a built-in command (quit, jobs, bg or fg)
     * then execute it immediately. Otherwise, fork a child process and
     * run the job in the context of the child. If the job is running in
     * the foreground, wait for it to terminate and then return.  Note:
     * each child process must have a unique process group ID so that our
     * background children don't receive SIGINT (SIGTSTP) from the kernel
     * when we type ctrl-c (ctrl-z) at the keyboard.  
    */
    void eval(char *cmdline) 
    {
        char *argv[MAXARGS]; /* Argument list execve() */
        char buf[MAXLINE];   /* Holds modified command line */
        int bg;              /* Should the job run in bg or fg? */
        pid_t pid;           /* Process id */
        sigset_t mask_all, mask_one, prev_one;
        strcpy(buf,cmdline);
        bg = parseline(buf, argv);      /*結尾&,後臺執,為true*/
        if (argv[0] == NULL) return;   /* Ignore empty lines */
        /*sigemptyset(sigset set)   初始化集合為空
          sigfillset(sigset set)    把每個信號都添加到set中
          sigaddset(sigset set,int signum)     函數把signum添加到集合set中
        */    
        Sigfillset(&mask_all);	
        Sigemptyset(&mask_one);
        Sigaddset(&mask_one, SIGCHLD);	                //mask_one:SIGCHLD
        Sigprocmask(SIG_BLOCK, &mask_one, &prev_one);   //阻塞SIGCHLD
        
        if (!builtin_cmd(argv)) {   /* 執行內置指令,不執行則往下執行 */
            if ((pid = Fork()) == 0) {      /* fork產生子進程執行指令 */
            Sigprocmask(SIG_SETMASK, &prev_one, NULL);      //解除阻塞
                setpgid(0, 0);              //確保前臺進程組中只有一個進程,即shell
                if (execve(argv[0], argv, environ) < 0) {
                    // ref:./bogus:Commandnotfound      (無.)
                    printf("%s: Command not found\n", argv[0]);
                    exit(0);
                }
            }
    
            /* Parent waits for foreground job to terminate */
    	if (!bg) {          //fg執行
                Sigprocmask(SIG_BLOCK, &mask_all, NULL);
                addjob(jobs,pid,FG,cmdline);
                waitfg(pid);  //掛起父進程等待前臺執行
                // if (waitpid(pid, &status, 0) < 0)
                //     unix_error("waitfg: waitpid error");
                Sigprocmask(SIG_SETMASK, &prev_one, NULL);
            }
            else{       //bg執行
                Sigprocmask(SIG_BLOCK, &mask_all, NULL);
                addjob(jobs,pid,BG,cmdline);
                Sigprocmask(SIG_SETMASK, &prev_one, NULL);
                struct job_t* bg_job = getjobpid(jobs, pid);
                printf("[%d] (%d) %s",bg_job->jid,bg_job->pid, cmdline);
            }
        }
        return;
    }
    
  4. make test05
    make test06//需要增加異常信號處理
    
    void sigchld_handler(int sig) //P543
    {   
        int olderrno=errno;
        sigset_t mask_all,prev_all;
        pid_t pid;
        struct job_t *job;
        int status;         //存儲回收子進程的退出狀態
    
        sigfillset(&mask_all);
        /*子進程都沒有停止或終止,返回0;停止或終止返回該子進程pid*/
        while ((pid=waitpid(-1,&status,WNOHANG|WUNTRACED))>0){  //回收僵屍子進程  
            
            job=getjobpid(jobs,pid);
            int pid=job->pid;
            int jid=job->jid;
            if(!WIFSTOPPED(status)) deletejob(jobs,pid);
            sigprocmask(SIG_BLOCK,&mask_all,&prev_all);
            
            //Job [1] (26263) terminated by signal 2
            if(WIFSIGNALED(status)){           //子進程停止因為未捕獲的信號而終止,則WTERMSIG(status)返回引起子進程終止的編號
                printf("Job [%d] (%d) terminated by signal %d\n", jid, pid,WTERMSIG(status));
            }else if(WIFSTOPPED(status)){      //子進程停止,WSTOPSIG(status)返回引起子進程停止的進程編號
                job -> state = ST;
                printf("Job [%d] (%d) stopped by signal %d\n",jid ,pid, WSTOPSIG(status));
            }
            
        }
        
        if (errno != ECHILD) {
            unix_error("waitpid error");
        }
        return;
    }
    
    /* 
     * sigint_handler - The kernel sends a SIGINT to the shell whenver the
     *    user types ctrl-c at the keyboard.  Catch it and send it along
     *    to the foreground job.  
     */
    void sigint_handler(int sig) 
    {   int olderrno = errno;
        sig_t mask_all,prev_all;
    
        sigfillset(&mask_all);
        sigprocmask(SIG_BLOCK,&mask_all,&prev_all);
        int fg_pid=fgpid(jobs);           //返回前臺進程
        sigprocmask(SIG_SETMASK,&prev_all,NULL);
        if(fg_pid){
            kill(-fg_pid,sig);          //向進程組發送發送SIGINT
        }
    
        errno=olderrno;
        return;
    }
    
    /*
     * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
     *     the user types ctrl-z at the keyboard. Catch it and suspend the
     *     foreground job by sending it a SIGTSTP.  
     */
    void sigtstp_handler(int sig) 
    {
        int olderrno = errno;
        sig_t mask_all,prev_all;
    
        Sigfillset(&mask_all);
        Sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
        int fg_pid = fgpid(jobs);
        Sigprocmask(SIG_SETMASK, &prev_all, NULL);
        if (fg_pid) {
            Kill(-fg_pid, sig);
        }
    
        errno=olderrno;
        return;
    }
    
  5. 實現do_fgbg

    ```c
    /* 
     * do_bgfg - Execute the builtin bg and fg commands
     */
    void do_bgfg(char **argv) 
    {   
        int bg=strcmp(argv[0],"bg");
        sigset_t mask_all,mask_one,prev_one;
        sigfillset(&mask_all);
        Sigemptyset(&mask_one);
        Sigaddset(&mask_one,SIGCHLD);
    
        struct job_t *Job;
        if (!argv[1]) {             //缺少參數PID或JID
            //fg command requires PID or %jobid argument
            printf("%s command requires PID or %%jobid argument\n", (!bg) ? "bg":"fg");
            return;
        }
        else if((argv[1][0]<'0'||argv[1][0]>'9')&&argv[1][0]!='%')  //指令:fg %2  fg 2
        {   
            // fg: argument must be a PID or %jobid
            printf("%s: argument must be a PID or %%jobid\n", (!bg) ? "bg":"fg");
            return;
        }
        else if (argv[1][0] == '%') {
            int jid = 0;
            for (int i = 1; argv[1][i]; i++) {
                jid = jid * 10 + (argv[1][i] - '0');
            }
            Job = getjobjid(jobs, jid);
            if (!Job) {
                printf("%%%d: No such job\n",jid);
                return;
            }
        }
        else {
            pid_t pid = 0;
            for (int i = 0; argv[1][i]; i++) {
                pid = pid * 10 + (argv[1][i] - '0');
            }
            Job = getjobpid(jobs, pid);
            if (!Job) {
                printf("(%d): No such process\n",pid);
                return; 
            }
        }
        Sigprocmask(SIG_BLOCK, &mask_one, &prev_one);
        Kill( -Job -> pid, SIGCONT);
        int pid=Job->pid;
        int jid=Job->jid;
        if (bg) {
            Sigprocmask(SIG_BLOCK, &mask_all, NULL);
            Job -> state = FG;
            waitfg(pid);
            Sigprocmask(SIG_SETMASK, &prev_one, NULL);        
        }
        else {
            Sigprocmask(SIG_BLOCK, &mask_all, NULL);
            Job -> state = BG;
            Sigprocmask(SIG_SETMASK, &prev_one, NULL);
            printf("[%d] (%d) %s",jid ,pid,Job->cmdline);
        }
        return;
    }
    ```
    

Evaluation

分數將根據以下分佈計算出最多90分:

80 Correctness: 16 trace files at 5 points each.
10 Style points. We expect you to have good comments (5 pts) and to check the return value of EVERY system call (5 pts).

您的解決方案 shell 將在 Linux 機器上測試正確性,使用包含在您的實驗室目錄中的相同 shell 驅動程式和跟蹤文件。 您的 shell 應該在這些跟蹤上產生與參考 shell 相同的輸出,只有兩個例外:

  • PID 可以(並且將會)不同。
  • trace11.txt、trace12.txt 和trace13.txt 中的/bin/ps 命令的輸出將因運行而異。但是,/bin/ps 命令輸出中任何 mysplit 進程的運行狀態應該相同。

我們為您提供了一個名為grade shlab的測試。pl.以下是正確案例的示例:

unix> ./grade-shlab.pl -f tsh.c
CS:APP Shell Lab: Grading Sheet for tsh.c
Part 0: Compiling your shell
gcc -Wall -O2 tsh.c -o tsh
gcc -Wall -O2 myspin.c -o myspin
gcc -Wall -O2 mysplit.c -o mysplit
gcc -Wall -O2 mystop.c -o mystop
gcc -Wall -O2 myint.c -o myint
7
Part 1: Correctness Tests
Checking trace01.txt...
Checking trace02.txt...
Checking trace03.txt...
Checking trace04.txt...
Checking trace05.txt...
Checking trace06.txt...
Checking trace07.txt...
Checking trace08.txt...
Checking trace09.txt...
Checking trace10.txt...
Checking trace11.txt...
Checking trace12.txt...
Checking trace13.txt...
Checking trace14.txt...
Checking trace15.txt...
Checking trace16.txt...
Preliminary correctness score: 80

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

-Advertisement-
Play Games
更多相關文章
  • 修飾類只有public和default(也就是是不寫訪問修飾符) 修飾成員變數或成員方法時有四種 public 可以被任意包中的類訪問 protected 可以被同一包下的其他類訪問(包括同一包下的子類),其他包的子類可以訪問 default(也就是什麼都不寫) 可以被同一包下的其他類訪問(包括同一 ...
  • 來源:juejin.im/post/5d026212f265da1b8608828b IDEA是Java開發利器,springboot是Java生態中最流行的微服務框架,docker是時下最火的容器技術,那麼它們結合在一起會產生什麼化學反應呢? 一、開發前準備 1.Docker安裝 可以參考: ht ...
  • 之前用.net5.0寫webapi,自動集成swagger,非常方便。asp.net mvc沒有自動集成swagger,但是手動配置swagger也很簡單。 1、nuget引用Swashbuckle庫,安裝完後,App_Start下自動多了一個SwaggerConfig.cs; 2、打開Swagge ...
  • 1. DevOps的一些介紹 DevOps(Development和Operations的組合詞)是一組過程、方法與系統的統稱,用於促進開發(應用程式/軟體工程)、技術運營和質量保障(QA)部門之間的溝通、協作與整合。 它是一種重視“軟體開發人員(Dev)”和“IT運維技術人員(Ops)”之間溝通合 ...
  • 工具 這裡用到兩個工具分別為Procdump+Windbg Procdump:ProcDump是一個命令行實用工具,主要目的是監視應用程式,以便在管理員或開發人員可用於確定峰值的原因期間監視 CPU 峰值和生成故障轉儲。 ProcDump 還包括使用視窗掛起 (使用相同的視窗掛起定義,Windows ...
  • 在實際的軟體開發過程中,我們通常會採用一種前後端分離的開發模式,在這種模式下一般會由前後端兩類開發人員協同開發,在這種情況下後端開發人員則需要提供API文檔去與前端人員進行對接,這樣才能保障後續的工作能夠順利開展。 並且當前項目在與外部系統進行業務往來或者數據交互的時候,我們通常會作為“介面方”對外 ...
  • 1. 前言 通過之前的學習,我們已經掌握了crank的配置以及對應http基準工具bombardier、wrk、wrk2的用法,本篇文章介紹一下如何將其用於實戰,在實際的項目中我們如何使用crank來完成壓測任務。 2. 項目背景 目前有一個項目,我們希望通過壓測來瞭解其QPS、吞吐量、以及臨界值, ...
  • RTF文檔即富文本格式(Rich Text Format)的文檔。我們在處理文件時,遇到需要對文檔格式進行轉換時,可以將RTF轉為其他格式,如轉為DOCX/DOC、PDF或者HTML,以滿足程式設計需要。網上有開發者提供了可實現RTF轉為HTML格式的方法,但是方法可能不一定適用於所有程式,比如可能 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...