由於我的第五個實驗的partB部分一直出問題。而且修了好久沒解決先不管了
這個實驗建議一定要認真讀完csapp全書的第八章。不然可能會毫無思路。千萬不要上來直接做。
0. 環境配置和實驗下載
利用docker配置Linux環境。無論你是mac還是windows都可以輕鬆搞定 https://www.cnblogs.com/JayL-zxl/p/14286789.html
1. 實驗目的
你的任務為補全tsh.c
裡面的一些空缺的函式。
• eval
: 解析命令列 [70 lines]
• builtin cmd
: 識別命令是否為內建命令: quit, fg, bg, and jobs. [25 lines]
• do bgfg
:實現bg
和fg
命令. [50 lines]
• waitfg
: 等待前臺程式完成 [20 lines]
• sigchld handler
: Catches SIGCHILD signals. 80 lines]
• sigint handler
: Catches SIGINT (ctrl-c) signals. [15 lines]
• sigtstp handler
: Catches SIGTSTP (ctrl-z) signals. [15 lines]
每一次你修改tsh.c
函式之後都要重新編譯它,然後執行你的shell
make
./tsh
在writeup
裡面給了非常多的提示。這裡簡單給大家列舉一下
- 要詳細閱讀書本的第八章內容
- 如果使用者輸入
ctrl-c
(ctrl-z
),那麼SIGINT
(SIGTSTP
)訊號應該被送給每一個在前臺程式組中的程式,如果沒有程式,那麼這兩個訊號應該不起作用。 - 如果一個命令以“&”結尾,那麼tsh應該將它們放在後臺執行,否則就放在前臺執行(並等待它的結束)
- tsh應該回收(reap)所有殭屍程式,如果一個工作是因為收到了一個它沒有捕獲的(沒有按照訊號處理函式)而終止的,那麼tsh應該輸出這個工作的PID和這個訊號的相關描述。
tsh
應該支援如下的內建命令
– The quit command terminates the shell.
– The jobs command lists all background jobs.
– The bg <job> command restarts <job> by sending it a SIGCONT signal, and then runs it in
the background. The <job> argument can be either a PID or a JID.
– The fg <job> command restarts <job> by sending it a SIGCONT signal, and then runs it in
the foreground. The <job> argument can be either a PID or a JID.
書上有展示過一個非常簡單的Shell程式這裡先複習一下。
當然上面的程式並沒有回收後臺執行的程式。而且它非常不完整,但足夠幫助我們理解整個shell的基本邏輯了
1.1 訊號簡介
當一個程式收到訊號之後就會呼叫訊號處理程式。這個也是我們任務之一。
訊號--->程式---->訊號處理程式---執行------結果
當然關於訊號有很多可以說的。比如訊號不會排隊啊。訊號處理程式的阻塞,以及併發情況如何處理啊。這些請大家仔細閱讀課本第八章。
2. 實驗開始
好了下面開始實驗了。首先根據writeup我們知道我們要依次根據test檔案進行測試。就是說我們的make test0x的執行結果要和make rtest0x的參考結果一樣就可以了。
這裡我們發現對於test02的測試就出現了問題。這裡去看一下trace02看看到底發生了什麼。
這裡有一條quit指令。不通過的原因就是我們的tsh程式還沒有實現對於這個命令的處理。所以它不會退出而會停在這裡。
下面正式開始我們的實驗啦
由於我們沒有辦法預估訊號到達的順序。因此對於併發程式設計的處理就變得非常的重要。
考慮下面這種情況
因此我們要考慮這種情況。在父程式addjob
之前先把SIGCHLD
訊號阻塞掉。
1. eval函式的實現
void eval(char *cmdline)
{
char *argv[MAXLINE]; /*Argument list execve()*/
char buf[MAXLINE]; /*Hold modified commend line*/
int bg; /*Should the job run in bg or fg?*/
pid_t pid;
int state;
sigset_t mask_all, mask_one, prev_one;
strcpy(buf,cmdline);
bg=parseline(cmdline,argv);
if(argv[0]==0){
return ;//ignore empty line
}
if(!builtin_cmd(argv)){
sigfillset(&mask_all);
sigemptyset(&mask_one);
sigaddset(&mask_one, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask_one, &prev_one); //Block SIGCHLD
if((pid==fork())==0){
sigprocmask(SIG_SETMASK,&prev_one,NULL);//UnBlock SIGCHLD
if (setpgid(0, 0) < 0)
{
perror("SETPGID ERROR");
exit(0);
}
if (execve(argv[0], argv, environ) < 0) //if execve error return 0
{
printf("%s: Command not found\n", argv[0]);
exit(0);
}
}
else{
state = bg ? BG : FG;
sigprocmask(SIG_BLOCK, &mask_all, NULL); //parent process
addjob(jobs, pid, state, cmdline);
sigprocmask(SIG_SETMASK, &prev_one, NULL); //unblock SIGCHLD
}
bg?printf("[%d] (%d) %s",pid2jid(pid), pid, cmdline):waitfg(pid);
}
return;
}
基本參考了書上p525
的shell
程式碼和p543
的程式碼。考慮了上述提到的併發訪問問題。
2. builtin_cmd
的實現
如果使用者輸入的是built-in command則立即執行否則返回0
一共需要支援四個內建命令
分別處理即可
int builtin_cmd(char **argv)
{
if(!strcmp(argv[0],"quit")){
exit(0);
}
if(!strcmp(argv[0],"&"))
return 1;
if(!strcmp(argv[0],"bg")||!strcmp(argv[0],"fg"){
do_bgfg(argv);
return 1;
}
if(!strcmp(argv[0],"jobs")){
listjobs(jobs);
return 1;
}
return 0; /* not a builtin command */
}
3. do_bgfg
的實現
Execute the builtin bg and fg commands
每個job都可以由程式ID(PID)或job ID(JID)標識,該ID是一個正整數tsh分配。 JID應該在命令列上以字首“%”表示。 例如,“%5”
表示JID 5,“ 5”表示PID5。(我們已為您提供了所需的所有例程處理工作清單。)
tsh> fg %1
Job [1] (9723) stopped by signal 20
kill(pid,signal)的規則
if (pid < 0) 則向|pid|的組中全部傳送訊號
if(pid > 0) 則就向單個程式傳送
if(pid=0) 訊號將送往所有與呼叫kill()的那個程式屬同一個使用組的程式。
void do_bgfg(char **argv)
{
struct job_t *job;
int id;
if(argv[1]==NULL){
printf("%s command requires PID or %%jobid argument\n", argv[0]);
return ;
}
if (sscanf(argv[1], "%%%d", &id) > 0)
{
job = getjobjid(jobs, id);
if (job == NULL)
{
printf("%%%d: No such job\n", id);
return ;
}
}
else if (sscanf(argv[1], "%d", &id) > 0)
{
job = getjobpid(jobs, id);
if (job == NULL)
{
printf("(%d): No such process\n", id);
return ;
}
}
else
{
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
return;
}
if(!strcmp(argv[0], "bg"))
{
kill(-(job->pid), SIGCONT);
job->state = BG;
printf("[%d] (%d) %s",job->jid, job->pid, job->cmdline);
}
else
{
kill(-(job->pid), SIGCONT);
job->state = FG;
waitfg(job->pid);
}
return;
}
3. waitfg
函式實現
Block until process pid is no longer the foreground process
void waitfg(pid_t pid)
{
sigset_t mask;
sigemptyset(&mask);
while (fgpid(jobs) > 0)
sigsuspend(&mask);
return;
}
這裡注意一下
4. sigchld_handler
的實現
The kernel sends a SIGCHLD to the shell whenever * a child job terminates (becomes a zombie), * stops because it received a SIGSTOP or SIGTSTP signal. The handler reaps all available zombie children, but doesn't wait for any other currently running children to terminate.
關於waitpid
引數的全介紹
void sigchld_handler(int sig)
{
int olderrno = errno;
pid_t pid;
int status;
sigset_t mask_all, prev;
sigfillset(&mask_all);
while((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
{
if (WIFEXITED(status)) //正常
{
sigprocmask(SIG_BLOCK, &mask_all, &prev);
deletejob(jobs, pid);
sigprocmask(SIG_SETMASK, &prev, NULL);
}
else if (WIFSIGNALED(status))
{
struct job_t* job = getjobpid(jobs, pid);
sigprocmask(SIG_BLOCK, &mask_all, &prev);
printf("Job [%d] (%d) terminated by signal %d\n", job->jid, job->pid, WTERMSIG(status));
deletejob(jobs, pid);
sigprocmask(SIG_SETMASK, &prev, NULL);
}
else /
{
struct job_t* job = getjobpid(jobs, pid);
sigprocmask(SIG_BLOCK, &mask_all, &prev);
printf("Job [%d] (%d) stopped by signal %d\n", job->jid, job->pid, WSTOPSIG(status));
job->state= ST;
sigprocmask(SIG_SETMASK, &prev, NULL);
}
}
errno = olderrno;
return;
}
5. 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 pid=fgpid(jobs);
int jid=pid2jid(pid);
sigset_t mask_all, prev;
sigfillset(&mask_all);
if(pid!=0){
sigprocmask(SIG_BLOCK, &mask_all, &prev);
printf("Job [%d] terminated by SIGINT.\n",jid);
deletejob(jobs,pid);
sigprocmask(SIG_SETMASK, &prev, NULL);
kill(-pid,sig);
}
return;
}
6. sigstp_handler
的實現
The kernel sends a SIGSTP to the shell whenver 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 pid=fgpid(jobs);
int jid=pid2jid(pid);
sigset_t mask_all, prev;
sigfillset(&mask_all);
if(pid!=0){
sigprocmask(SIG_BLOCK, &mask_all, &prev);
printf("Job [%d] stopped by SIGSTP.\n",jid);
(*getjobpid(jobs,pid)).state = ST;;
sigprocmask(SIG_SETMASK, &prev, NULL);
kill(-pid,sig);
}
return;
}