2018.12.10 把shelllab撿起來做了
阿新 • • 發佈:2018-12-11
好久沒寫了啊,總算把這個lab撿起來了。
具體內容不寫了,把網上另一個人寫的部落格轉載一下算作mark。
以下內容轉載自https://zhuanlan.zhihu.com/p/28695244
實驗思路
本次實驗就是自己實現一個shell,不過不是從頭自己寫,是完成幾個函式的實現。
- eval函式包含了shell的主要操作,讀取命令列,fork子程序,執行
- builtin_cmd函式包含了處理內建命令列函式的操作,包括quit, fg, bg, jobs
- do_bgfg函式用來處理fg和bg操作,主要是對程序變換狀態以及傳送SIGCONT訊號
- waitfg函式用來等待前臺程式結束,因為回收子程序交給了sigchld_handler來做,所以waitfg只要用sleep寫一個忙等待來等到前臺程序結束。
- 三個訊號的操作函式也是要重點實現的內容。
這次實驗比較難,有很多需要注意的細節,我開頭沒有理解透整個shell的操作,外加要求沒有完全看清,導致前期做的非常困難。
需要注意的細節有:
- 子程序的回收在sigchld_handler中來做,waitfg只要忙等待前臺程序結束就行。
- 後臺程序執行之後放著不用管就行,父程序不用等到後臺子程序執行結束,直接可以繼續後續操作(開頭寫錯了,導致語句輸出順序出現錯誤,以及一些後臺程序被執行完後才開始執行後續操作)
- 熟練使用waitpid中的引數,WNOHANG|WUNTRACED組合在一起用是立即返回的意思。
- 用WIFEXITED(status),WIFSIGNALED(status),WIFSTOPPED(status)等來補獲終止或者被停止的子程序的退出狀態(主要用於sigchld_handler中)
- 對於每個fork的子程序,執行setgpid(0, 0),這樣就會以子程序號單獨開一個程序組,也可以方便的使用kill(-pid, SIGNAL)來把訊號發到pid所在的整個程序組。
- 對於呼叫sigchld_handler回收子程序時,必須用掩碼阻塞SIGCHLD訊號,防止子程序還沒執行,就回收了。
- 對於每個子程序,加入joblist之後,在結束時在sigchld_handler中有三種操作,一種是正常結束,deletejob,一種是被訊號終止了,也要deletejob,還有一種是被訊號停止了,不用delete,只要修改job的狀態。
- 最重要的是,不要死磕一個trace,其實有的trace,需要你實現很多內容,可能實現了前一個,後面幾個都完成了,所以需要仔細分析所有的需求。
- 看一下給的其他程式,myspin,主要是一個延遲函式,所以在前臺執行,就要等它延遲結束,如果在後臺執行,就可以在exevce之後不管。mystop是用來發送停止訊號,myint是用來發送終止訊號。
eval
void eval(char *cmdline) { char *argv[MAXARGS]; char buf[MAXLINE]; int bg; pid_t pid; strcpy(buf, cmdline); bg = parseline(buf, argv); if(argv[0] == NULL) return; if(!builtin_cmd(argv)){ 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){ printf("setpgid error"); exit(0); } if(execve(argv[0], argv, environ) < 0){ printf("%s: Command not found.\n", argv[0]); exit(0); } } else{ if(bg) addjob(jobs, pid, BG, cmdline); else addjob(jobs, pid, FG, cmdline); sigprocmask(SIG_SETMASK, &prev_one, NULL);//unblock SIGCHLD if(bg){ printf("[%d] (%d) %s",pid2jid(pid), pid, cmdline); } else{ waitfg(pid); } } } return; }
eval函式,沒啥多說的,需要注意的上面都講過了,主要就是對每個操作是bg還是fg要分清楚操作,加掩碼阻塞SIGCHLD訊號的程式碼書本上有說明,然後就是父程序,對於前後臺程序,加入joblist要注意狀態BG和FG。然後後臺程序輸出一句話,前臺程序需要等待子程序執行結束。
builtin_cmd
int builtin_cmd(char **argv) { if(!strcmp(argv[0], "quit")) exit(0); if(!strcmp(argv[0], "jobs")){ listjobs(jobs); return 1; } if(!strcmp(argv[0], "bg")){ do_bgfg(argv); return 1; } if(!strcmp(argv[0], "fg")){ do_bgfg(argv); return 1; } return 0; /* not a builtin command */ }
判斷是否是內建命令列函式,如果是,就執行相應操作,如果不是,就返回到eval中,建立子程序執行操作。(listjobs我開頭沒有弄好子程序回收,導致前臺程式還留在 joblist中,然後輸出時會輸出前臺程序。要考慮到前臺程序執行結束之後,才能執行jobs命令,所以絕對不會出現狀態是前臺的程序)
do_bgfg
void do_bgfg(char **argv) { int id; struct job_t *job; if(argv[1] == NULL){ printf("%s command requires PID or %%jobid argument\n",argv[0]); return; } if(argv[1][0]=='%'){ if(argv[1][1] >='0'&& argv[1][1] <='9'){ id=atoi(argv[1]+1); job = getjobjid(jobs, id); if(job==NULL){ printf("%s: No such job\n",argv[1]); return; } } else { printf("%s: argument must be a PID or %%jobid\n",argv[0]); return; } } else{ if(argv[1][0] >='0'&& argv[1][0] <='9'){ id = atoi(argv[1]); job = getjobpid(jobs, id); if(job == NULL){ printf("(%s): No such process\n",argv[1]); return; } } else { printf("%s: argument must be a PID or %%jobid\n",argv[0]); return; } } kill(-(job->pid), SIGCONT); if(!strcmp(argv[0], "bg")){ job->state = BG; printf("[%d] (%d) %s",job->jid, job->pid, job->cmdline); } else { job