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;
}

原文地址:https://www.cnblogs.com/jarviswhj/p/9750131.html

时间: 2024-10-16 16:13:04

CSAPP Lab:Shell Lab——理解进程控制的秘密的相关文章

CSAPP2e:Shell lab 解答

期中之后的第一个lab 就是实现一个简单的Shell 程序,程序的大部分已经写好,只需要实现 eval 函数和处理信号的sigchld_handle, sigint_handle, sigtstp_handle这三个函数. 这个lab 主要要求处理好各个信号,因为上课的时候一直听得很糊涂,就拖着没有写,直到这两天deadline逼近才动手.同样是时间紧迫,debug的时候出了很多问题,在网上搜了很多解答,但是因为题目版本不一样,并不完全适用,比如之前的不需要重定向.因此把自己写的代码也贴出来,最

进程控制(Note for apue and csapp)

1. Introduction We now turn to the process control provided by the UNIX System. This includes the creation of new processes, program execution, and process termination. We also look at the various IDs that are the property of the process - real, effe

深入理解计算机操作系统——第8章:进程控制

8.4 进程控制 8.4.1 获取进程ID (1)pid_t getpid(void)和pid_t gettpid(void)函数,其中返回的是pid_t整数值 (2)pid_t在types.h文件中被定义 8.4.2 创建和终止进程

shell脚本之流程控制

shell脚本之流程控制 shell脚本之流程控制 条件语句 条件判断 循环语句for,while,until for循环 while循环 until循环 循环控制语句continue 循环控制语句break 循环控制shift命令 创建无限循环 while的特殊用法 for的特殊用法 select循环与菜单 select与case 信号捕捉trap 条件语句 选择执行: 注意:if语句可嵌套 单分支 if 判断条件;then 条件为真的分支代码 fi 双分支 if 判断条件; then 条件为

六、Linux进程控制

1. Linux进程概述 进程是一个程序一次执行的过程,它和程序有本质区别. 程序是静态的,它是一些保存在磁盘上的指令的有序集合:而进程是一个动态的概念,它是一个运行着的程序,包含了进程的动态创建.调度和消亡的过程,是Linux的基本调度单位. 那么从系统的角度看如何描述并表示它的变化呢?在这里,是通过进程控制块(PCB)来描述的.进程控制块包含了进程的描述信息.控制信息以及资源信息,它是进程的一个静态描述. 内核使用进程来控制对CPU和其他系统资源的访问,并且使用进程来决定在CPU上运行哪个程

Linux进程控制(二)

1. 进程的创建 Linux下有四类创建子进程的函数:system(),fork(),exec*(),popen() 1.1. system函数 原型: #include <stdlib.h> int system(const char *string); system函数通过调用shell程序/bin/sh –c来执行string所指定的命令,该函数在内部是通过调用execve("/bin/sh",..)函数来实现的.通过system创建子进程后,原进程和子进程各自运行,

linux 命令及进程控制

main.c  main.o/main.obj  main/main.exe          编译                连接 程序运行;      两步: gcc/g++  -c  main.c/main.cpp  -> main.o              gcc/g++ -o main  main.o -> main(可执行文件)     一步:  gcc -o main mian.c  -> main    工程文件:       rm  *.o     gcc  -

Linux进程控制——exec函数族

原文:http://www.cnblogs.com/hnrainll/archive/2011/07/23/2114854.html 1.简介 在Linux中,并不存在exec()函数,exec指的是一组函数,一共有6个,分别是: #include <unistd.h> extern char **environ; int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char

第八章:进程控制

8.1:引言 本章介绍Unix的进程控制,包括创建新进程.执行程序和进程终止.还将说明进程属性的各种ID--实际.有效和保存的用户和组ID,以及它们如何受到进程控制原语的影响.还包括解释器文件和system函数,最后讲述大多数Unix系统所提供的进程会计机制. 8.2:进程标识符 每个进程都有一个非负整型表示的唯一进程ID.虽然是唯一的,但是进程ID却可以重用,当一个进程终止后,其进程ID就可以再次使用了.Unix使用延迟重用算法,避免新进程的ID等于最近终止的进程的ID. 除了进程ID,每个进