實驗網站 課程網站: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)
:如果子進程通過調用exit
或return
正常終止,則返回真,。此時可通過WEXITSTATUS(statusp)
獲得退出狀態。WIFSIGNALED(status)
:如果子進程是因為一個未捕獲的信號終止的,則返回真。此時可通過WTERMSIG(statusp)
獲得該信號的編號。WIFSTOPPED(statusp)
:如果引起函數返回的子進程是停止的,則返回真。此時可通過WSTOPSIG(statusp)
獲得引起子進程停止的信號編號。WIFCONTINUED(statusp)
:如果子進程收到SIGCONT
信號重新運行,則返回真。
-
如果當前進程沒有子進程,則
waitpid
返回-1,並設置errno
為ECHILD
,如果waitpid
函數被信號中斷,則返回-1,並設置errno
為EINTR
。否則返回被回收的子進程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
函數來修改信號的預設行為,但是無法修改SIGSTOP
和SIGKILL
信號的預設行為
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum
為信號編號,可以直接輸入信號名稱handler
為我們想要對信號signum
採取的行為- 當
handler
為SIG_IGN
,表示要進程忽略該信號- 當
handler
為SIG_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
以下是一個使用例子
以上執行內部函數時,就不會接收到SIGINT
信號,即不會被Ctrl+C
終止。
通過阻塞信號來消除函數衝突,或者保證程式運行邏輯正確。
顯示等待信號
當我們想要主進程顯示等待某個信號時,可以用以下代碼
這裡主進程會顯示等待子進程被回收,這裡使用了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
-
實現
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 */ }
-
make test03 make test04 make test05 //jobs指令執行失敗 //需完善void eval(char *cmdline)
-
完善
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; }
-
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; }
-
實現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