caspp-shlab详解

本文详述了实现一个简单Shell的过程,包括处理SIGINT和SIGTSTP信号的函数,SIGCHLD信号处理,eval和waitfg函数,以及内置命令builtin_cmd和do_bgfg的实现。通过理解并实现这些功能,加深了对进程控制、信号处理及Shell工作原理的理解。
摘要由CSDN通过智能技术生成


这里是实验要用到的文件
这里是实验的 writeup

开始

这次的实验呢,要求我们写一个简单的 shell。像之前一样,在正式开始写代码之前,我们先来看一看下下来的 shlab-handout 里都有些什么:
首先是 “my” 开头的四个 .c 文件——这些是测试 shell 时要用到的;还有 16 个 “trace” 开头的文件、一个 .pl 文件以及 Makefile,是测试 shell 用的。我们的代码要写在 tsh.c 里面。当然了,tsh.c 也不是一片空白,作者已经给我们定义好了用来描述作业的结构(job),用来描述作业状态的宏(UNDEF、FG、BG、ST),放作业的全局数组(jobs),以及一大堆乱七八糟的帮助函数。我们需要实现的函数有 7 个——三个是信号处理函数,余下四个是处理用户输入的函数。
好了,知道了文件夹里都有什么,那我们就开敲吧。

sigint_handler 和 sigtstp_handler

我们先从比较简单的两个信号处理函数开始。
这两个函数的主要任务,是在收到 shell 传来的信号时,将这个信号“转发”给在 shell 中运行的进程。这个过程很好办——先用 fgpid(作者提供的帮助函数)获取前台进程(为啥只有前台进程嘞?因为 tstp 和 int 信号是只发给前台进程的)的 pid,之后走 kill 调用,向这个子进程发对应的信号。
好办是好办,但是其中还是有一些需要注意的点的。fgpid 是不是访问了全局数组呢?那么我们是否要在它执行的时候暂时阻塞所有信号呢?为了保证信号处理函数之外依赖于 errno 的代码正常运行,我们是不是应该暂存 errno 呢?
其实还有一个比较隐蔽的点哦——不过作者已经在 writeup 中提示给我们了——就是 kill 的第一个参数应该填 “-pid” (“负的 pid”),而不是 “pid”,从而保证“转发”的信号能够被传递给整个子进程组。为什么是“子进程”呢?如果 shell fork 出来的子进程,没有再 fork 它自己的子进程的话,填 “pid” 没有任何问题;但是,如果它 fork 了的话(shell 就有孙进程了),这时候子进程和孙进程的 pid 是不一样的。填正的 pid,只能保证子进程能被结束;但是孙进程么……就没那么幸运了——它会“丧父”(变成孤儿进程),直到操作系统“收养”它。所以为了避免这些麻烦事,填成负的 pid 就好了。
所以,我们写出来的代码是这样的:

void sigint_handler(int sig)
{
   
    int olderrno = errno;
    // 暂存 errno
    sigset_t mask_all, prev_all;
    sigfillset(&mask_all);
    sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
    // 阻塞所有信号
    pid_t pid = fgpid(jobs);
    if (pid == 0)
    {
   
        // 没有前台进程么?收拾收拾返回
        errno = olderrno;
        sigprocmask(SIG_SETMASK, &prev_all, NULL);
        return;
    }

    kill(-pid, SIGINT);
    sigprocmask(SIG_SETMASK, &prev_all, NULL);

    errno = olderrno;
    return;
}

void sigtstp_handler(int sig)
// 这个函数和上面那个差不多
{
   
    int olderrno = errno;

    sigset_t mask_all, prev_all;
    sigfillset(&mask_all);
    sigprocmask(SIG_BLOCK, &mask_all, &prev_all);

    pid_t pid = fgpid(jobs);
    if (pid == 0)
    {
   
        errno = olderrno;
        sigprocmask(SIG_SETMASK, &prev_all, NULL);
        return;
    }

    kill(-pid, SIGTSTP);
    sigprocmask(SIG_SETMASK, &prev_all, NULL);

    errno = olderrno;
}

sigchld_handler

上面我们实现了处理 SIGINT 和 SIGTSTP 信号的函数对不对。那么,当子进程收到信号之后停止了,又会发生什么呢?我们知道,这时候内核会发 SIGCHLD 信号给 shell,让它处理子进程的停止(或者是“终止”)事件。我们要写的 sigchld_handler 就是做这件事的。
先上代码:

void sigchld_handler(int sig)
{
   
    int olderrno = errno;
    // 存储 errno ——编写安全的信号处理函数要求之一
    int status;
    pid_t pid;
    sigset_t mask_all, prev_all;
    sigfillset(&mask_all)
  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值