CSAPP Lab:Shell Lab——理解進程控制的秘密


本次實驗目的是完成一個簡單的shell程序,解析命令行參數,理解並使用(fork,execve,waitpid)常見的多進程函數,了解linux進程組,以及前台進程和后台進程的相關概念,理解linux的信號機制(包括發送信號,接受信號,阻塞信號等)。實驗提示以及詳情請閱讀CMU的實驗指導:http://csapp.cs.cmu.edu/public/labs.html 。

我們要完成的shell並不是從0開始,實驗本身已經幫你完成了一部分內容,並且提供一些工具函數,我們要做的是實現一下這幾個核心函數:

eval: Main routine that parses and interprets the command line. [70 lines]

builtin_cmd: Recognizes and interprets the built-in commands: quit, fg, bg, and jobs. [25 lines]

dobgfg: Implements the bg and fg built-in commands. [50 lines]

waitfg: Waits for a foreground job to complete. [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]

eval

這是shell程序的核心函數,我們需要在這里完成以下幾個事情:

1.調用parseline,生成argv以及判斷是否是后台進程。

2.調用builtin_cmd判斷是否是內建命令,如果是則已經在該方法中執行,shell直接返回,否則創建進程執行。

3.fork之前要注意屏蔽SIGHLD信號,否則有可能在addjob之前就調用deletejob造成競爭。

4.需要在fork后解鎖SIGHLD的信號。

/* 
 * 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];
    char buf[MAXLINE];
    int isBg;
    pid_t pid;
    sigset_t mask;

    strcpy(buf,cmdline);
    isBg=parseline(buf,argv);
    if(argv[0]==NULL){
        return;
    }

    if(!builtin_cmd(argv)){
    //init mask
    sigemptyset(&mask);
    sigaddset(&mask,SIGCHLD);
    sigprocmask(SIG_BLOCK,&mask,NULL); //block SIGCHLD

        if((pid=fork())==0){
        sigprocmask(SIG_UNBLOCK,&mask,NULL); //unblock SIGCHLD
            if(execve(argv[0],argv,environ)<0){
                printf("%s:Command not found.\n",argv[0]);
                exit(0);
            }
        
        //set own pid as group pid
        setpgid(0,0);
        }

        if(!isBg){
            addjob(jobs,pid,FG,cmdline);
            waitfg(pid);

        }
    else{
            addjob(jobs,pid,BG,cmdline);
        printf("%d %s",pid,cmdline);
    }
        sigprocmask(SIG_UNBLOCK,&mask,NULL); //unblock SIGCHLD
    }
    

    return;
}

 

builtin_cmd

builtin_cmd做的事情比較簡單,判斷是否是內建命令,如果是,則直接執行並返回true,否則返回false。

/* 
 * builtin_cmd - If the user has typed a built-in command then execute
 *    it immediately.  
 */
int builtin_cmd(char **argv) 
{
    if(!strcmp(argv[0],"quit")||!strcmp(argv[0],"q")){
        exit(0);
    }
    if(!strcmp(argv[0],"jobs")){
        listjobs(jobs);
    return 1;
    }
    if(!strcmp(argv[0],"bg")||!strcmp(argv[0],"fg")){
    do_bgfg(argv);
    return 1;
    }
    return 0; 
}

dobgfg

這個方法還是比較復雜的,主要完成了bg和fg命令的操作,需要注意如下幾點:

1.需要區分輸入的是pid還是jid來調用不同函數。

2.通過發送SIGCONT來重啟進程,發送對象需要為進程組。

3.不要忘記將后台進程改為前台進程后需要等待前台進程完成(調用waitfg)。

/* 
 * do_bgfg - Execute the builtin bg and fg commands
 */
void do_bgfg(char **argv) 
{
    struct job_t *job;
    char *id=argv[1];
    pid_t pid;

    if(id==NULL){
        printf("%s command requireds pid or %%jobid argument\n",argv[0]);
        return;
    }

    //process by jobid
    if(id[0]=='%')
    {
        int jid = atoi(&id[1]);
        job=getjobjid(jobs,jid);
        if(job==NULL)
        {
            printf("%s:No such job\n",id);
            return;
        }
    }
    //process by pid
    else if(isdigit(id[0])){
    int pid = atoi(&id[1]);
    job = getjobpid(jobs,pid);
        if(job==NULL)
        {
            printf("%s:No such job\n",id);
            return;
        }    
    }
    else{
        printf("%s: argument must be a PID or %%jobid\n", argv[0]);
        return;
    }
    //send SIGCONT to restart
    kill(-(job->pid),SIGCONT);
    //set job status
    if(!strcmp(argv[0],"bg")){
        job->state = BG;
        printf("[%d] (%d) %s", job->jid, job->pid,job->cmdline);
    }else{
        job->state = FG;
        waitfg(job->pid);
    }


    return;
}

 

waitfg

沒什么好說的,根據實驗指導,這里直接使用忙等待來實現。

/* 
 * waitfg - Block until process pid is no longer the foreground process
 */
void waitfg(pid_t pid)
{
    while(pid == fgpid(jobs)){
        sleep(0);
    }
}

 

sigchld handler

回收僵屍進程的關鍵函數,需要注意如下幾點:

1.理解waitpid的用法,這里使用WNOHANG|WUNTRACED的組合會更合適,表示立即返回,如果等待集合中沒有進程被中止或停止返回0,否則返回進程的pid。

2.檢驗status的值來執行不同操作,status的含義有如下枚舉:

  • WIFEXITED(status):
    如果進程是正常返回即為true,什么是正常返回呢?就是通過調用exit()或者return返回的
  • WIFSIGNALED(status):
    如果進程因為捕獲一個信號而終止的,則返回true
  • WIFSTOPPED(status):
    如果返回的進程當前是被停止,則為true

  所以三種情況都是需要delete job的,當進程為停止狀態同時需要設置job的status。

void sigchld_handler(int sig) 
{
    pid_t pid;
    int status;
    while((pid = waitpid(-1,&status,WNOHANG|WUNTRACED))>0){
        if(WIFEXITED(status)){
            deletejob(jobs,pid);
        }
        if(WIFSIGNALED(status)){
            deletejob(jobs,pid);
        }
        if(WIFSTOPPED(status)){
            struct job_t *job = getjobpid(jobs,pid);
            if(job !=NULL ){
        job->state = ST;
        }
        }
    }
    if(errno != ECHILD)
        unix_error("waitpid error");

    return;
}

 

sigint handler

ctrl-c的響應函數,直接調用kill函數給相關進程。

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

 

sigtstp handler

ctrl-z的響應函數,直接調用kill函數給相關進程,需要注意kill前判斷狀態,不要重復發送信號。

/*
 * 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) 
{
    pid_t pid = fgpid(jobs);

    if(pid!=0 ){
        struct job_t *job = getjobpid(jobs,pid);
        if(job->state == ST){
            return;
        }else{
            Kill(-pid,SIGTSTP);
        }
    }
    return;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM