概念
信号提供了一种处理异步事件
的方法.例如,终端用户键入 CTRL + C,会通过信号机制停止一个程序,或通过管道终止管道中的下一个程序。
信号的定义在 <signal.h> 中,被定义为正整数常量。
信号可以由以下几种方式产生:‘
- 终端
- 硬件异常
- 某种软件状态产生
kill函数
#include <signal.h>
int kill(pid_t pid, int sig);
pid: 进程ID, sig: 信号
kill的pid参数有以下4中不同的情况:
- pid > 0: 将信号发送给进程 ID 为 pid 的进程
- pid == 0: 将进程发送给与发送进程属于同一近程租的所有进程,前提是发送进程具有权限向这些进程发送信号。注:
所有进程不包括实现定义的系统进程集
- pid < 0: 将该信号发送给其近程租ID 等于PID 绝对值,而且发送进程具有权限发送的所有进程
- pid == -1: 发送给有权限的所有进程
如果 sig == 0,no signal is sent,仅仅判断 pid 是否还存在
当收到一个信号时,我们可以做以下几件事:
- 执行系统规定的默认操作
- 忽略
- 捕获,调用我们定义好的信号处理程序
- 阻塞:推迟信号直到解阻塞
信号处理函数
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
Returns: previous signal handler if ok, SIG_ERR otherwise
#include <signal.h>
typedef void (*sig_t)(int);
sig_t signal(int sig, sig_t func);
Returns: previous signal handler if ok, SIG_ERR otherwise
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
Returns: previous signal handler if ok, SIG_ERR otherwise
参数 fun 可能是:
- SIG_IGN:忽略信号
- SIG_DFL:使用默认的信号处理函数
- 信号处理函数指针
在有些系统的实现中,在一个信号处理程序被调用后,将会将信号重定向到默认的处理函数中去。
sigpending
#include <signal.h>
int sigpending(sigset_t *set);
sigpending 函数返回一个信号集,对于调用进程而言,其中的各信号是阻塞的不能递送的,而它也一定是当前未决的。
sigaction
#include <signal.h>
int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *resrict oact)
sigaction 函数的作用是检查和(或)修改与指定信号相关联的处理动作。
下面是一个信号处理程序的例子:
- 阻塞 SIG_QUIT,如果我们输入 ^\ 不会发生任何事
- 我们可以查看是否有 SIG_QUIT 产生,
- 通过 sigpending(2) 交付 信号,一但我们不再阻塞信号,我们将会立刻进入信号处理函数
- 我们可以通过 sig_int 切换信号处理函数,单是在unblock之前还是会回到原来的信号处理函数
/* This program illustrates blocking a signal and the
* use of signal masks.
*
* SIG_QUIT is blocked, so if we hit ^\, nothing
* happens. We can check whether any such signals
* were delivered via sigpending(2). Once we unblock
* the signal, we will immediately enter the signal
* handler.
*
* Note that if we change the signal handler to
* SIG_IGN _after_ the signal has been generated, but
* _before_ we unblock, it will still be ignored.
*/
#include <err.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#ifndef SLEEP
#define SLEEP 5
#endif
int s = 0;
static void
sig_quit(int signo) {
(void)signo;
(void)printf("In sig_quit, s=%d. Now sleeping...\n", ++s);
/* This call to sleep(3) can itself be
* interrupted if we receive a signal other
* than SIGQUIT while executing this signal
* handler. If that happens, then we jump
* into the other signal handler; when that
* handler completes, we are returned back
* here. */
(void)sleep(SLEEP);
(void)printf("sig_quit, s=%d: exiting\n", s);
}
static void
sig_int(int signo) {
(void)signo;
(void)printf("Now in sig_int, s=%d. Returning immediately.\n", ++s);
}
int
main(int argc, char **argv) {
sigset_t newmask, oldmask, pendmask;
int ismember = 0;
(void)argv;
(void)printf("\n=> Establishing initial signal hander via signal(3).\n");
if (signal(SIGQUIT, sig_quit) == SIG_ERR) {
err(EXIT_FAILURE, "unable to set SIGQUIT signal handler");
/* NOTREACHED */
}
if (signal(SIGINT, sig_int) == SIG_ERR) {
err(EXIT_FAILURE, "unable to set SIGINT signal handler");
/* NOTREACHED */
}
if (sigemptyset(&newmask) < 0) {
err(EXIT_FAILURE, "sigemtpyset");
/* NOTREACHED */
}
if (sigaddset(&newmask, SIGQUIT) < 0) {
err(EXIT_FAILURE, "sigaddset");
/* NOTREACHED */
}
(void)printf("\n=> Blocking delivery of SIGQUIT...\n");
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {
err(EXIT_FAILURE, "unable to block SIGQUIT");
/* NOTREACHED */
}
(void)printf("\n=> Now going to sleep for %d seconds...\n", SLEEP);
(void)sleep(SLEEP);
if (argc > 1) {
if (signal(SIGQUIT, SIG_IGN) == SIG_ERR) {
err(EXIT_FAILURE, "unable to ignore SIGQUIT");
/* NOTREACHED */
}
}
printf("\n=> Checking if any signals are pending...\n");
if (sigpending(&pendmask) < 0) {
err(EXIT_FAILURE, "sigpending");
/* NOTREACHED */
}
if ((ismember = sigismember(&pendmask, SIGQUIT)) < 0) {
err(EXIT_FAILURE, "sigismember");
/* NOTREACHED */
}
if (ismember) {
(void)printf("Pending SIGQUIT found.\n");
}
(void)printf("\n=> Unblocking SIGQUIT...\n");
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) {
err(EXIT_FAILURE, "unable to unblock SIGQUIT");
/* NOTREACHED */
}
/* Note that if we did receive ^\ while we
* were blocked, we are jumping into sig_quit
* right here, _before_ we print this. */
(void)printf("SIGQUIT unblocked - sleeping some more...\n");
/* ^\ will now be delivered again */
(void)sleep(SLEEP);
(void)printf("Now exiting.\n");
exit(EXIT_SUCCESS);
}
信号概念的更多细节
- 当执行一个信号处理程序时,触发它的信号将被阻塞。但是!其他的信号可能会发生,并将信号处理流程切换到新发生的信号中
- 被阻止的信号被标记为未决; 您可以检查未决信号集 (Blocked signals are marked as pending; you can inspect the set of pending signals)
- 触发您进入信号处理程序的信号在退出该信号处理程序后会自动解除阻止,这意味着它将被传递,并且您将立即重新进入同一处理程序(The signal that triggered you entering a signal handler is automatically unblocked upon exit from that signal handler, meaning it will be delivered and you will re-enter the same handler right away)
- 在被阻塞时到达的多个信号可能会被合并(Multiple signals of the same type arriving in sequence while being blocked may be
merged) - 在fork(2)之后,子进程中的所有信号配置和信号掩码都与父进程中的相同(After a fork(2), all signal dispositions and signal masks remain the same in the child as in the parent)
- exec() 之后,所有的信号处理函数都将被恢复默认操作,但是之前忽略的信号依然忽略(The execve(2) system call reinstates the default action for all signals which were caught, but ignored signals continue to be ignored; the signal mask also remains the same)
- 信号是异步事件的通知; 传递类似于硬件中断(保存当前上下文,构建新上下文)( Signals are notifications of asynchronous events; delivery is similar to a hardware interrupt (current context is saved, a new context is built))
- 信号可以被执行默认操作,被捕获((signal(3) / sigaction(2))),被阻塞(sigprocmask(2))
- 到达的信号可能会被融合并传递,不同的信号可能会中断信号处理程序(Arriving signals may be merged, then immediately delivered; different signals may interrupt the current signal handler.)
- 还有一些其他的选择,见 sigaction(2)(A number of additional options are available; see sigaction(2))
思考:如果你的信号处理程序能够被中断,那么在信号处理函数里的系统调用是安全的吗??