csapp:shelllab

一、实验要求
实现一个简单的shell,能够执行内置命令,执行前端任务、后端任务,以及任务控制。
需要实现如下几个功能:
•支持以下内置命令:
–quit命令终止shell。
–jobs命令列出所有后台作业。
–bg<job>命令通过发送SIGCONT信号重新启动<job>,然后在后台运行。<job>参数可以是PID或JID。
–fg<job>命令通过发送SIGCONT信号重新启动<job>,然后在前台运行它。<job>参数可以是PID或JID。
•键入ctrl-c(ctrl-z)应该会将SIGINT(sigtsp)信号发送到当前前台作业以及该作业的任何子进程(例如,它派生的任何子进程)。如果没有前台作业,则该信号应无效。
•如果命令行以&结束,则tsh应在后台运行作业。否则,它应该在前台运行作业。
 
二、预备知识
进程控制
创建子进程
pid_t fork()
  1. 调用一次返回两次,子进程返回0,父进程返回子进程pid;
  2. 并发执行,父进程和子进程并发执行,没有先后顺序;
  3. 相同但独立的地址空间,父进程和子进程的地址空间内容完全一致,但是相互独立。
  4. 共享文件,子进程继承父进程的打开文件表。
 
加载并运行程序
int execve(const char* filename,const char* argv[],const char *envp[ ]);
  1. 调用一次不返回;
  2. 地址空间替换为新程序的。
 
回收子进程
pid_t waitpid(pid_t pid, int *statusp, int option)
1.确定等待集的成员,等待集的成员由pid参数确定:
  • 如果pid>0,那么等待集就是进程ID为等于pid。
  • 如果pid=-1,那么等待集由父进程的所有子进程组成。
2.修改默认行为
可以通过将选项设置为各种组合来修改默认行为WNOHANG和WUNTRACED常数:
  • WNOHANG:如果等待集中的子进程尚未终止,则立即返回(返回值为0)。默认行为将挂起调用进程,直到子进程终止。如果希望在等待子项终止时继续执行有用的工作,则此选项非常有用。。
  • WUNTRACED:暂停调用进程的执行,直到等待集中的进程被终止或停止。返回导致返回的已终止或已停止子级的PID。默认行为只返回已终止的子进程。如果要同时检查已终止子级和已停止子级,则此选项非常有用。
  • WNOHANG | WUNTRACED:立即返回,返回值为0,如果等待集中的所有子项都没有停止或终止,或者返回的值等于某个已停止或终止的子级的PID。
3.检查子进程的退出状态
如果status参数为非空,则waitpid将对导致status参数返回的子进程的状态信息进行编码。
  • WIFEXITED(status) :如果子进程通过调用exit或return正常终止,则返回true。
  • WEXITSTATUS(status) :返回正常终止的子进程的退出状态。仅当WIFEXITED返回true时才定义此状态。
  • WIFSIGNALED(status) :如果子进程因未捕获到信号而终止,则返回true。
  • WTERMSIG(status) :返回导致子进程终止的信号数。仅当WIFSIGNALED(status)返回true时才定义此状态。
  • WIFSTOPPED(status) :如果导致返回的子级当前已停止,则返回true。
  • WSTOPSIG(status) :返回导致子进程停止的信号的数目。仅当WIFSTOPPED(status)返回true时才定义此状态。
 
信号控制
发送信号
int kill(pid_t pid, int sig)
  1. pid>0,发送信号sig给进程pid;
  2. pid=0,发送信号sig给调用进程所在进程组的每个进程,包括调用进程自己;
  3. pid<0,发送信号sig给进程组|pig|中的每个进程;
阻塞和解阻塞信号
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 
int sigemptyset(sigset_t *set); 
int sigfillset(sigset_t *set); 
int sigaddset(sigset_t *set, int signum); 
int sigdelset(sigset_t *set, int signum); Returns: 0 if OK, −1 on error 
int sigismember(const sigset_t *set, int signum); Returns: 1 if member, 0 if not, −1 on error
sigprocmask函数更改当前被阻止的信号集。具体行为取决于以下方式的值:
  • SIG_BLOCK :将set中的信号添加到blocked(blocked=blocked | set)。
  • SIG_UNBLOCK:从blocked中移除set中的信号。
  • SIG_SETMASK: blocked=set。
如果oldset为非空,则阻塞位向量的之前的值存储在oldset中。使用以下函数操作信号集(如set)。
sigemptyset将集合初始化为空集合。sigfillset函数添加要设置的每个信号。sigaddset函数将signum添加到set,sigdelset删除来自集合的signum,如果signum是集合的成员,则sigismember返回1,并且如果不是,则为0。
 
三、实验代码
eval()
void eval(char *cmdline)
{
    char *argv[MAXARGS];
    // char cmdlinestr[MAXLINE];
    pid_t pid;
    int is_bg;
    sigset_t mask_all, mask, pre;
    is_bg = parseline(cmdline, argv);
    if (argv[0] == NULL)
    { //空的命令行
        return;
    }
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGTSTP);
    sigfillset(&mask_all);
    //判断是否为内置命令
    if (!builtin_cmd(argv))
    { //不是内置命令
        //阻塞sigchild信号,防止sigchild信号处理函数提前处理,避免竞态
        sigprocmask(SIG_BLOCK, &mask, &pre);
        //fork子进程执行
        if ((pid = fork()) == 0)
        {
            //子进程继承父进程的阻塞信号集合,解锁子进程的sigchild信号
            sigprocmask(SIG_SETMASK, &pre, NULL);
            //设置进程组,创建新的进程组,用于SIG——INT
            setpgid(0, 0);
            //execve执行错误返回-1
            if (execve(argv[0], argv, environ) < 0)
            {
                unix_error(argv[0]);
            }
        }
        //
        sigprocmask(SIG_BLOCK, &mask_all, NULL);
        //判断是否为前端命令
        int state = is_bg ? BG : FG;
        addjob(jobs, pid, state, cmdline);
        //解锁sigchild信号
        sigprocmask(SIG_SETMASK, &pre, NULL);
        //判断是否为前端命令
        if (!is_bg)
        {
            //等待前端命令执行完,或者终端发信号终止前端命令
            waitfg(pid);
        }
        else
        {
            //后端命令,直接打印命令信息
            printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
        }
    }
    return;
}
sigchld_handler
void sigchld_handler(int sig)
{
    int olderrno = errno;
    pid_t pid;
    int status;
    sigset_t mask, pre;
    sigfillset(&mask);
    //使用WNOHANG来避免shell程序因等待子进程终止而被挂起
    while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
    {
        sigprocmask(SIG_BLOCK, &mask, &pre); //阻塞所有信号
        int jid = pid2jid(pid);
        //正常退出
        if (WIFEXITED(status))
        {
            // printf("Job [%d] (%d) exited\n", jid, pid);
            deletejob(jobs, pid);
        }
        //进程被不能捕获的信号终止(sigkill,sifINT)
        else if (WIFSIGNALED(status))
        {
            int sig_num = WTERMSIG(status);
            printf("Job [%d] (%d) terminated by signal %d\n", jid, pid, sig_num);
            deletejob(jobs, pid);
        }
        //进程被停止sigTSTP
        else if (WIFSTOPPED(status))
        {
            int sig_num = WSTOPSIG(status);
            printf("Job [%d] (%d) stopped by signal %d\n", jid, pid, sig_num);
            struct job_t *job = getjobpid(jobs, pid);
            job->state = ST;
        }
        sigprocmask(SIG_SETMASK, &pre, NULL); //解锁所有信号
    }
    errno = olderrno;
    return;
}
sigint_handler
void sigint_handler(int sig)
{
    int olderrno = errno;
    //获取前端的进程pid
    pid_t pid = fgpid(jobs);
    if (pid != 0)
    {
        //给进程组为[pid]的发送sigkill信号
        kill(-pid, SIGINT);
    }
    errno = olderrno;
    return;
}
sigtstp_handler
void sigtstp_handler(int sig)
{
    int olderrno = errno;
    //获取前端的进程pid
    pid_t pid = fgpid(jobs);
    if (pid != 0)
    {
        //给进程组为[pid]的发送SIGTSTP信号
        kill(-pid, SIGTSTP);
    }
    errno = olderrno;
    return;
}
do_fpbg
void do_bgfg(char **argv)
{
    int jid;
    pid_t pid;
    if (argv[1] == NULL)
    {
        printf("%s command requires PID or %%jobid argument\n", argv[0]);
        return;
    }
    char *id = argv[1];
    //检查参数合法性
    if (id[0] != '%' && (id[0] < '0' || id[0] > '9'))
    {
        printf("%s argument must be a PID or %%jobid\n", argv[0]);
        return;
    }
    //获取jid,pid信息
    if (id[0] == '%')
    {
        jid = atoi(id + 1);
        struct job_t *job = getjobjid(jobs, jid);
        if (job == NULL)
        {
            printf("%%%d: No such job\n", jid);
            return;
        }
        else
        {
            pid = job->pid;
        }
    }
    else
    {
        pid = atoi(id);
        jid = pid2jid(pid);
        if (jid == 0)
        {
            printf("(%d): No such process\n", pid);
            return;
        }
    }
    struct job_t *job = getjobjid(jobs, jid);
    kill(-pid, SIGCONT);
    //把stop的后端命令命令变成running后端
    if (!strcmp(argv[0], "bg"))
    {
        job->state = BG;
        printf("[%d] (%d) %s", jid, pid,job->cmdline);
    }
    else
    {
        job->state = FG;
        waitfg(pid);
    }
    return;
}
 
posted @ 2020-06-03 20:18  skyliulu  阅读(438)  评论(0)    收藏  举报