主頁 > 作業系統 > CSAPP:lab7 shell

CSAPP:lab7 shell

2022-04-17 06:16:03 作業系統

實驗網站

課程網站: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 ?les 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

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/458336.html

標籤:其他

上一篇:2022-A rch安裝(詳細)

下一篇:Linux 單用戶模式修改密碼 Centos、Ubuntu

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more