shell 中信号的上锁解锁 sigprocmask

作者 QIFAN 日期 2017-03-30
shell 中信号的上锁解锁 sigprocmask

513 的第五次作业是自己写一个 shell ,来模拟真实 shell 中执行命令,中断命令的一些指令,大致的了解的输入命令行时终端里一些信号接收的玩意儿。本文主要讲一下 sigprocmask 这个方法。

先来一些大致背景:
shell 执行命令的主体逻辑并不复杂:

  • parse command
  • fork a child process
  • execute in child process
  • in parent process, add job to the job list, and if foreground, wait till finish; if background, return immediately

fork() 这个命令可以生成一个与当前程序一模一样的子程序,子程序和父程序各自向后执行,执行完了分别返回,也就是说调用一次 fork() ,会返回两次(孤陋寡闻的我觉得可神奇了)。我们用 fork() == 0 判断这是子程序。
shell 中,子程序调用 execve 来运行命令行, 在父程序中(就是 shell 里)把新生成的子程序添加到 shell 的一个全局变量 job list 中。

但是呢,由于很多进程是在同时进行的,极有可能会发生 race condition ,就是两个作用于同一个目标的两个指令在“赛跑”。比如在 sigchld_handler 中要执行 deletejob ,而在 eval 中要进行 addjob ,这是在两个不同的进程中的,我们希望的结果是先 add 再 delete ,但如果不进行信号阻塞,谁也不能保证哪个在前。所以这里用到了 sigprocmask 方法用于在某个进程执行是封锁信号,使得相应的信号不会被别的进程接收到从而在别的进程召唤出 handler 。

man sigprocmask
SYNOPSIS
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
DESCRIPTION
The sigprocmask() function examines and/or changes the current signal mask (those signals that are blocked from delivery). Signals are blocked if they are members of the current signal mask set.

第一个参数 how ,表示的是要做什么行动,这次作业涉及到这三个 SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK . SIG_BLOCK 就是锁定信号, SIG_UNBLOCK 就是解除信号,SIG_SETMASK 就是设置成给定的 set 。
第二个参数,是当前进程要新添加的信号
第三个参数是上一个状态的信号集

举个例子,假设共三个信号,SIGCHLD, SIGINT, SIGTSTP ,1 表示有,0 表示无。

sigprocmask(SIG_BLOCK, &mask, &prev_mask);
// child process
if ((pid = fork()) == 0) {
// child unblocks signals for receiving signals
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
// execute the command line
execve(token.argv[0], token.argv, environ);
}
// parent process
if (!addjob(job_list, pid, state, cmdline)) {
unix_error("add job error");
}
// unblock signals in parent
sigprocmask(SIG_SETMASK, &prev_mask, NULL);

假设当前信号集 curr_mask 为 001
这时候,要执行一个新的进程,改进程要求阻塞全部信号即 111 ,上述代码中就是 mask = 111 ,所以在执行了 sigprocmask(SIG_BLOCK, &mask, &prev_mask) 后, curr_mask = 001 | 111 = 111 。而 prev_mask = 001 ,也就是 curr_mask 一开始的值。进入子进程 (fork == 0) 后,执行 sigprocmask(SIG_SETMASK, &prev_mask, NULL) ,因为是 SIG_SETMASK ,也就是把当前的信号集直接设成 prev_mask ,即 curr_mask = prev_mask = 001 ,等于把信号阻塞状态复原了。然后在父程序结束后也要执行一次 sigprocmask(SIG_SETMASK, &prev_mask, NULL) 使得之后的信号不被阻塞。

给 513 TA 们笔芯。