CSAPP之詳解ShellLab

實(shí)驗(yàn)之前

這個實(shí)驗(yàn)難度比較適中椅邓,當(dāng)然前提是你第八章認(rèn)真研究過了幾遍匆光,在做這個實(shí)驗(yàn)之前畔裕,請必須閱讀以便官網(wǎng)的writeup文檔衣撬,否則你可能不明白這個實(shí)驗(yàn)要實(shí)現(xiàn)干點(diǎn)什么,然后下載官網(wǎng)的實(shí)驗(yàn)材料到你的電腦上:
Write文檔:
Writeup幫助文檔鏈接

實(shí)驗(yàn)材料:
實(shí)驗(yàn)材料下載鏈接

實(shí)驗(yàn)說明

  1. 你只能修改tsh.c文件來完成其中的7個函數(shù):
    ?eval:解析和解釋命令行的主例程扮饶。 [大約70行]
    ?builtin_cmd:識別并解釋內(nèi)置命令:quit具练,fg,bg和job甜无。 [大約25
    行]
    ?do_bgfg:實(shí)現(xiàn)bg和fg內(nèi)置命令扛点。 [大約50行]
    ?waitfg:等待前臺作業(yè)完成。 [大約20行]
    ?sigchld處理程序:處理SIGCHILD信號岂丘。 [大約80行]
    ?sigint處理程序:處理SIGINT(ctrl-c)信號陵究。 [大約15行]
    ?sigtstp處理程序:處理SIGTSTP(ctrl-z)信號。 [大約15行]

  2. 每次修改tsh.c文件后元潘,你都需要鍵入單獨(dú)的一個make命令來完成一系列準(zhǔn)備工作畔乙,官方提供了16個測試來檢驗(yàn)?zāi)愕拇鸢妇停阈枰I入make testXX來輸出自己的答案翩概,然后輸入make rtestXX來比對標(biāo)準(zhǔn)答案,其中XX = 01返咱、02钥庇、03、......咖摹、16评姨。你的答案必須和標(biāo)準(zhǔn)答案一樣才算成功(進(jìn)程號可以是不同的,因?yàn)樗鼈兪请S機(jī)的),例如:

make test03
這里會輸出你的答案

make rtest03
這里是標(biāo)準(zhǔn)答案

  1. 在tsh.c文件中吐句,老師已經(jīng)幫我們實(shí)現(xiàn)了很多輔助函數(shù)胁后,在代碼中我會標(biāo)識它的作用(讀一讀是很有幫助的),并且已經(jīng)定義了一些全局變量嗦枢,大大降低了實(shí)驗(yàn)難度攀芯,我們需要充分理解這些變量和函數(shù)。如下文虏,這些是已經(jīng)準(zhǔn)備好的函數(shù)或者變量:
extern char **environ;      /* defined in libc 這個時環(huán)境變量侣诺,exec的參數(shù),老師已經(jīng)安排好了*/
char prompt[] = "tsh> ";    /* command line prompt (DO NOT CHANGE) */
int verbose = 0;            /* if true, print additional output */
int nextjid = 1;            /* next job ID to allocate */
char sbuf[MAXLINE];         /* for composing sprintf messages */
/*進(jìn)程結(jié)構(gòu)體氧秘,保管著所有tsh所有子進(jìn)程的信息年鸳,必要的時候,我們必須要修改它*/
struct job_t {              /* The job struct */
    pid_t pid;              /* job PID */
    int jid;                /* job ID [1, 2, ...] */
    int state;              /* UNDEF, BG, FG, or ST */
    char cmdline[MAXLINE];  /* command line */
};
struct job_t jobs[MAXJOBS]; /* The job list */

int parseline(const char *cmdline, char **argv);        /*解析命令行的函數(shù)丸相,和書上一樣*/ 
void sigquit_handler(int sig);                          /*quit信號處理*/
void clearjob(struct job_t *job);                       /*清理進(jìn)程鏈表搔确,退出的時候用*/
void initjobs(struct job_t *jobs);                      /*初始化進(jìn)程鏈表*/
int maxjid(struct job_t *jobs);                         /*找到最大的進(jìn)程組號*/
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline);    /*添加進(jìn)程,這個是需要我們手動添加的*/
int deletejob(struct job_t *jobs, pid_t pid);           /*刪除進(jìn)程已添,依然需要我們手動刪除*/
pid_t fgpid(struct job_t *jobs);                        /*如果有前臺工作進(jìn)程妥箕,返回1,否則返回0*/
struct job_t *getjobpid(struct job_t *jobs, pid_t pid); /*通過pid獲得對于的進(jìn)程結(jié)構(gòu)體指針*/ 
struct job_t *getjobjid(struct job_t *jobs, int jid);   /*通過jid獲得對于的進(jìn)程結(jié)構(gòu)體指針*/
int pid2jid(pid_t pid);                                 /*返回對于pid進(jìn)程的jid*/
void listjobs(struct job_t *jobs);                      /*打印進(jìn)程信息*/
void usage(void);                                       /*不用管他*/
void unix_error(char *msg);                             /*打印錯誤信息*/
void app_error(char *msg);
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler);
  1. 用戶鍵入的命令行應(yīng)由一個名稱和零個或多個參數(shù)組成更舞,所有參數(shù)以一個或多個空格分隔畦幢。如果name是內(nèi)置命令,則tsh應(yīng)該立即處理它并等待下一個命令行缆蝉。否則宇葱,tsh應(yīng)該假定名稱是可執(zhí)行文件,它會在初始子進(jìn)程的上下文中加載并運(yùn)行(在這種情況下刊头,工作一詞是指此初始子流程黍瞧。有幾個需要注意的地方:
    ?tsh不需要支持管道(|)或I / O重定向(<和>)。
    ?鍵入ctrl-c(ctrl-z)應(yīng)該會導(dǎo)致SIGINT(SIGTSTP)信號發(fā)送到當(dāng)前的前地面作業(yè)以及該作業(yè)的任何后代(例如原杂,它派生的任何子進(jìn)程)印颤。如果沒有前臺作業(yè),則該信號應(yīng)該沒有任何作用穿肄。
    ?如果命令行以&結(jié)束年局,則tsh應(yīng)該在后臺運(yùn)行作業(yè)。否則咸产,它應(yīng)該在前臺運(yùn)行作業(yè)矢否。
    ?每個作業(yè)都可以由進(jìn)程ID(PID)或作業(yè)ID(JID)標(biāo)識,該ID是一個正整數(shù)
    由tsh分配脑溢。 JID應(yīng)該在命令行上以前綴“%”表示僵朗。例如,“%5” 表示JID 5,“ 5”表示PID5验庙。(我們已為您提供了所需的所有例程處理工作清單顶吮。)
    ?tsh應(yīng)該支持以下內(nèi)置命令:
    – quit命令終止tsh程序。
    – jobs命令列出所有后臺作業(yè)粪薛。
    – bg <job>命令通過向其發(fā)送SIGCONT信號來重新啟動<job>云矫,然后在
    的背景。 <job>參數(shù)可以是PID或JID汗菜。
    – fg <job>命令通過向其發(fā)送SIGCONT信號來重新啟動<job>让禀,然后在
    前景。 <job>參數(shù)可以是PID或JID陨界。

  2. 正如前面所說的巡揍,我們的答案必須要和標(biāo)準(zhǔn)答案相同,所以在特定的位置我們需要輸出特定的語句菌瘪。例如腮敌,在后臺工作運(yùn)行時,我們要打印它的一系列參數(shù)等等等俏扩。

實(shí)驗(yàn)代碼

Fork函數(shù)

這個不是要求的糜工,但我們?nèi)匀话b好它:

/*自定義安全的Fork*/
pid_t Fork(void){
    pid_t pid;
    if ((pid = fork()) < 0)
        unix_error("fork error!");
    return pid;
}


eval函數(shù)

這個函數(shù)的重點(diǎn)就是要小心同步并發(fā)流中的競爭,此外录淡,setpgid也是很有必要的捌木,它將子進(jìn)程組與tsh進(jìn)程組分開,避免tsh收到莫名的信號而停止嫉戚。

/*利用書上已有的框架和543提到的避免并發(fā)競爭發(fā)生*/
void eval(char *cmdline) 
{
    char* argv[MAXARGS];
    char* buf[MAXLINE];
    int bg;
    pid_t pid;
    sigset_t mask_all, mask_one, prev_one;
    strcpy(buf, cmdline);
    bg = parseline(buf, argv);
    if (argv[0] == NULL)
        return;
    
    sigfillset(&mask_all);
    sigemptyset(&mask_one);
    sigaddset(&mask_one, SIGCHLD);

    if (!builtin_cmd(argv)){
        sigprocmask(SIG_BLOCK, &mask_one, &prev_one);/*必須要鎖住刨裆,防止addjob和信號處理競爭*/
        if ((pid = Fork()) == 0){
            /*setpgid將子進(jìn)程組和tsh進(jìn)程組分開來,避免停止子進(jìn)程組把tsh一起停止掉彬檀,同時子進(jìn)程組id就等于pid帆啃,發(fā)送消息很方便*/
            /*請注意進(jìn)程組id并不等于題目中的jid*/
            if (setpgid(0, 0) < 0){
                perror("setpgid error!");
                exit(0);
            }
            sigprocmask(SIG_SETMASK, &prev_one, NULL);/*子進(jìn)程中不需要堵住它,但父進(jìn)程需要*/
            if (execve(argv[0], argv, environ) < 0){
                printf ("%s: Command not found\n", argv[0]);
                exit(0);
            }
        }
        if (!bg){
            sigprocmask(SIG_BLOCK, &mask_all, NULL);/*鎖住一切信號窍帝,避免addjob處理程序中斷*/
            addjob(jobs, pid, FG, cmdline);
            sigprocmask(SIG_SETMASK, &prev_one, NULL);
            waitfg(pid);
        }
        else{
            sigprocmask(SIG_BLOCK, &mask_all, NULL);
            addjob(jobs, pid, BG, cmdline);
            printf ("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
            sigprocmask(SIG_SETMASK, &prev_one, NULL);/*打印全局變量努潘,仍然需要加塞,防止途中被中斷坤学,可能造成還未讀(寫)內(nèi)存而內(nèi)存的值卻被修改的情況*/
        }
    }
    //printf ("eve return\n");
    return;



builtin_cmd函數(shù)

這個函數(shù)我們按要求解析4個內(nèi)置命令就好了

int builtin_cmd(char **argv) 
{
    //printf ("cmd\n");
    if (strcmp(argv[0], "quit") == 0)
        exit(0);    
    else if (strcmp(argv[0], "&") == 0)
        return 1;
    else if (strcmp(argv[0], "fg") == 0)
        do_bgfg(argv);
    else if (strcmp(argv[0], "bg") == 0)
        do_bgfg(argv);
    else if (strcmp(argv[0], "jobs") == 0)
        listjobs(jobs); /*這個是老師寫好的輔助函數(shù)疯坤,實(shí)現(xiàn)起來也很簡單*/
    else
        //printf ("cmd before return :%s \n",argv[0]);
        return 0;/*如果不是內(nèi)置命令,返回1*/
    return 1;     /* not a builtin command */
}


do_bgfg函數(shù)

這個函數(shù)發(fā)生繼續(xù)信號到指定進(jìn)程中拥峦,并指定是以fg模式還是bg模式運(yùn)行贴膘,這意味著原來的bg進(jìn)程可能會變成fg進(jìn)程卖子,所以要注意修改信息略号。

void do_bgfg(char **argv) 
{
    //printf ("bgfg\n");
    struct job_t* job = NULL;
    if (argv[1] == NULL){
        printf ("%s command requires PID or %jobid argument\n", argv[0]);
        return;
    }
    int idex;
    /*解析pid*/
    if (sscanf(argv[1], "%d", &idex) > 0){
        if ((job = getjobpid(jobs, idex)) == NULL){
            printf ("%s: No such process\n", argv[1]);
            return;
        }
    }
    /*解析jid*/
    else if (sscanf(argv[1], "%%%d", &idex) > 0){
        if ((job = getjobjid(jobs, idex)) == NULL){
            printf ("%s: No such process\n", argv[1]);
            return;
        }   
    }
    /*都失敗的話,打印錯誤消息*/
    else{
        printf ("%s: argument must be a PID or %%jobid\n", argv[0]);
        return;
    }

    /*發(fā)送信號,這里要求發(fā)送到進(jìn)程組玄柠,所以采用負(fù)數(shù)*/
    /*子進(jìn)程的進(jìn)程組id和pid是一致的突梦,請不要將jid和進(jìn)程組id搞混了*/
    kill(-(job->pid), SIGCONT);
    if (strcmp(argv[0], "bg") == 0){
        job->state = BG;/*設(shè)置狀態(tài)*/
        printf ("[%d] (%d) %s", job->jid, job->pid, job->cmdline);      
    }
    else{
        job->state = FG;/*設(shè)置狀態(tài)*/
        waitfg(job->pid);
    }
    //printf ("bgfg return");
    return;
}


waitfg函數(shù)

等待前臺任務(wù)結(jié)束,可以利用老師給定函數(shù)判定羽利,注意這里不能使用簡單的pause宫患,因?yàn)樾盘柨赡軙趫?zhí)行pause前到來(恰好),這樣就會形成競爭關(guān)系这弧,如果程序這個時候執(zhí)行了pause的話娃闲,就會等待著一個永遠(yuǎn)不會到來的信號(可能永遠(yuǎn)不會醒過來)。所以要使用sigsuspend函數(shù)匾浪,它等價于三條語句(請翻書)皇帮,第一條語句與pause是原子屬性的,它是不可中斷的蛋辈,在此之前我們堵塞SIGCHLD信號属拾,然后在執(zhí)行sigsuspend第一條語句時,我們短暫的解除堵塞冷溶,然后原子的立即執(zhí)行pause渐白,如果在執(zhí)行sigsuspend之前SIGCHLD信號發(fā)送過來,它會被堵塞逞频,而當(dāng)sigsuspend執(zhí)行時纯衍,它會等在pause執(zhí)行時被釋放,這會喚醒pause苗胀,同時陷入處理程序托酸,設(shè)置while循環(huán)條件,結(jié)束循環(huán)柒巫,如果沒有信號到來励堡,那么pause就會正確執(zhí)行。
當(dāng)然這里也可以簡單的用sleep語句堡掏,但是書上545頁也陳述了它的缺點(diǎn)应结,最好還是利用sigsuspend指令。

void waitfg(pid_t pid)
{
    sigset_t mask, prev;
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &mask, &prev);
    while (fgpid(jobs) != 0){
        sigsuspend(&mask);
        //printf ("wait here\n");
    }
    sigprocmask(SIG_SETMASK, &prev, NULL);
    return;
}


sigchld_handler函數(shù)

這個函數(shù)要處理三種情況泉唁,一是處理正常中止的情況鹅龄,二是收到信號中止的情況,三是被信號所暫時停止的情況亭畜,前兩種情況都要顯示的將進(jìn)程從進(jìn)程表中刪除扮休,第三種情況卻不用,但是卻要更改狀態(tài)拴鸵。為了防止del程序被中斷玷坠,我們?nèi)匀恍枰尤?/p>

void sigchld_handler(int sig) 
{
    int olderrno = errno;
    int status;
    pid_t pid;
    struct job_t *job;
    sigset_t mask, prev;
    sigfillset(&mask);
    while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0){
        sigprocmask(SIG_BLOCK, &mask, &prev);
        if (WIFEXITED(status)){
            deletejob(jobs, pid);
        }
        else if (WIFSIGNALED(status)){
            printf ("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
            deletejob(jobs, pid);
        }
        else if (WIFSTOPPED(status)){
            printf ("Job [%d] (%d) stoped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));
            job = getjobpid(jobs,pid);
            job->state = ST;
        }
        sigprocmask(SIG_SETMASK, &prev, NULL);          
    }
    errno = olderrno;
    //printf ("chldreturn\n");
    return;
}


sigint_handler函數(shù)和sigtstp_handler函數(shù)

最后兩個函數(shù)比較簡單蜗搔,我們只要負(fù)責(zé)將信號發(fā)送給前臺作業(yè)即可。因?yàn)檫@兩個信號都是有默認(rèn)行為的八堡,不需要我們瞎操心樟凄。

void sigint_handler(int sig) 
{
    pid_t pid;
    if ((pid = fgpid(jobs)) > 0)
        kill(-pid, sig);
    return;
}
void sigtstp_handler(int sig) 
{
    pid_t pid;
    if ((pid = fgpid(jobs)) > 0)
        kill(-pid, sig);
    return;
}




總結(jié)

雖然只需要我們實(shí)現(xiàn)7個函數(shù),但是閱讀老師給定幫助函數(shù)是非常有幫助的兄渺。做這個實(shí)驗(yàn)可能更多的要處理好并發(fā)造成的問題吧缝龄,并發(fā)編程是非常容易出錯的,我們必須小心小心再小心挂谍。
完整的tsh.c文件下載:
https://github.com/happysnaker/CSAPPLabs/blob/CSAPP/tsh.c

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叔壤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子口叙,更是在濱河造成了極大的恐慌百新,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庐扫,死亡現(xiàn)場離奇詭異饭望,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)形庭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門铅辞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人萨醒,你說我怎么就攤上這事斟珊。” “怎么了富纸?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵囤踩,是天一觀的道長。 經(jīng)常有香客問我晓褪,道長堵漱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任涣仿,我火速辦了婚禮勤庐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘好港。我一直安慰自己愉镰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布钧汹。 她就那樣靜靜地躺著丈探,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拔莱。 梳的紋絲不亂的頭發(fā)上碗降,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天隘竭,我揣著相機(jī)與錄音,去河邊找鬼遗锣。 笑死,一個胖子當(dāng)著我的面吹牛嗤形,可吹牛的內(nèi)容都是我干的精偿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼赋兵,長吁一口氣:“原來是場噩夢啊……” “哼笔咽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起霹期,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤叶组,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后历造,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體甩十,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年吭产,在試婚紗的時候發(fā)現(xiàn)自己被綠了侣监。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡臣淤,死狀恐怖橄霉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情邑蒋,我是刑警寧澤姓蜂,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站医吊,受9級特大地震影響钱慢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卿堂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一滩字、第九天 我趴在偏房一處隱蔽的房頂上張望烘豌。 院中可真熱鬧拙已,春花似錦互拾、人聲如沸缕陕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽壁肋。三九已至割岛,卻和暖如春揍诽,著一層夾襖步出監(jiān)牢的瞬間诀蓉,已是汗流浹背栗竖。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渠啤,地道東北人狐肢。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像沥曹,于是被迫代替她去往敵國和親份名。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 原文鏈接 目標(biāo) 補(bǔ)全tsh.c中剩余的代碼: void eval(char *cmdline):解析并執(zhí)行命令妓美。 ...
    Coc0閱讀 967評論 0 0
  • 一步一步教你寫SHELL 這個LAB 是上完CMU CSAPP的14-15 LECTURE之后僵腺,就可以做了。csa...
    西部小籠包閱讀 412評論 0 1
  • 一步一步教你寫SHELL 這個LAB 是上完CMU CSAPP的14-15 LECTURE之后壶栋,就可以做了辰如。csa...
    西部小籠包閱讀 9,313評論 1 5
  • shell lab 在嘗試完成這個 shell lab 之前,先看看官方給了什么代碼吧贵试,一個是書上有的 shlle...
    oo上海閱讀 3,344評論 1 1
  • 實(shí)驗(yàn)介紹 完成一個簡單的shell程序琉兜,總體的框架和輔助代碼都已經(jīng)提供好了,我們需要完成的函數(shù)主要以下幾個: ev...
    leon4ever閱讀 8,376評論 1 4