C语言 过程信号掩码、阻塞信号集和阻塞信号之间的区别



了解信号,我想知道过程信号掩码、阻塞信号集、信号处理程序和阻塞信号之间的细微差别。

这些问题涉及(在 Debian 上):

  • 西格普罗克面具(2)
  • 西格塞托普斯(3) 相关函数

每个进程都有自己的信号掩码(一个包含被阻止的信号的长整型)。并且可以通过调用 sigprocmask(2) 来获得信号集,并使用 *set 变量的 NULL 参数,将导致旧的进程掩码被放入 *oldset 中,保持不变:

#include <string.h>
#include <signal.h>
void show_signals(const sigset_t exmask)
{
int exsignals[43];
exsignals[0] = SIGABRT;
exsignals[1] = SIGALRM;
exsignals[2] = SIGBUS;
exsignals[3] = SIGCHLD;
exsignals[4] = SIGCONT;
#ifdef SIGEMT
exsignals[5] = SIGEMT;
#else
exsignals[5] = -1;
#endif
exsignals[6] = SIGFPE;
#ifdef SIGFREEZE
exsignals[7] = SIGFREEZE;
#else
exsignals[7] = -1;
#endif
exsignals[8] = SIGHUP;
exsignals[9] = SIGILL;
#ifdef SIGINFO
exsignals[10] = SIGINFO;
#else
exsignals[10] = -1;
#endif
exsignals[11] = SIGINT;
exsignals[12] = SIGIO;
exsignals[13] = SIGIOT;
#ifdef SIGJVM1
exsignals[14] = SIGJVM1;
#else
exsignals[14] = -1;
#endif
#ifdef SIGJVM2
exsignals[15] = SIGJVM2;
#else
exsignals[15] = -1;
#endif
exsignals[16] = SIGKILL;
#ifdef SIGLOST
exsignals[17] = SIGLOST;
#else
exsignals[17] = -1;
#endif
#ifdef SIGLWP
exsignals[18] = SIGLWP;
#else
exsignals[18] = -1;
#endif
exsignals[19] = SIGPIPE;
exsignals[20] = SIGPOLL;
exsignals[21] = SIGPROF;
exsignals[22] = SIGPWR;
exsignals[23] = SIGQUIT;
exsignals[24] = SIGSEGV;
exsignals[25] = SIGSTKFLT;
exsignals[26] = SIGSTOP;
exsignals[27] = SIGSYS;
exsignals[28] = SIGTERM;
#ifdef SIGTHAW
exsignals[29] = SIGTHAW;
#else
exsignals[29] = -1;
#endif
#ifdef SIGTHR
exsignals[30] = SIGTHR;
#else
exsignals[30] = -1;
#endif
exsignals[31] = SIGTRAP;
exsignals[32] = SIGTSTP;
exsignals[33] = SIGTTIN;
exsignals[34] = SIGTTOU;
exsignals[35] = SIGURG;
exsignals[36] = SIGUSR1;
exsignals[37] = SIGUSR2;
exsignals[38] = SIGVTALRM;
#ifdef SIGWAITING
exsignals[39] = SIGWAITING;
#else
exsignals[39] = -1;
#endif
exsignals[40] = SIGWINCH;
exsignals[41] = SIGXCPU;
exsignals[42] = SIGXFSZ;
#ifdef SIGXRES
exsignals[43] = SIGXRES;
#else
exsignals[43] = -1;
#endif
int exsignals_n = 0;
for (;exsignals_n < 43; exsignals_n++) {
if (exsignals[exsignals_n] == -1) continue;
static char *exsignal_name;
exsignal_name = strsignal(exsignals[exsignals_n]);
switch(sigismember(&exmask, exsignals[exsignals_n]))
{
case 0: break;
case 1: printf("YES %sn", exsignal_name); break;
case -1: printf("could not obtain signaln"); break;
default: printf("UNEXPECTED for %s returnn", exsignal_name); break;
}
}
}
const sigset_t getmask(void)
{
static sigset_t retmask;
if ((sigprocmask(SIG_SETMASK, NULL, &retmask)) == -1)
printf("could not obtain process signal maskn");
return retmask;
}

在我的程序开始时,我意识到过程信号掩码,没有阻挡任何信号。然后,我将信号处理程序放入程序中。

static void sig_abrt(int signo)
{
printf("Caught SIGABRTn");
}
int main(void)
{
show_signals(getmask());
signal(SIGABRT, sig_abrt);
show_signals(getmask());
return 0;
}

所以现在有一个用于 SIGABRT 的信号处理程序,但是如果我再次调用 sigprocmask(2),如上所述,SIGABRT 将不会在进程中出现信号掩码。我尝试使用 sigismember(3) 进行检查,但是只有在我调用 sigaddset(3) 或其他修改信号掩码的函数后,才会修改进程信号掩码。

如果我使用 sigaddset(3) 阻塞 SIGABRT,信号处理程序在 SIGABRT 交付时sig_abrt收不到调用吗?这是否意味着信号模板会影响传递哪些信号?有什么区别?

另外,有没有办法在不使用 sigsetops(3) 和 sigprocmask(2) 函数的情况下阻止进程中的信号?

每个进程都有自己的信号掩码(包含被阻止的信号的长

)

嗯,没有。信号掩码实际上是特定于线程的。(在多线程程序中,必须使用pthread_sigmask()来操作当前线程的信号掩码;在单线程程序中,可以使用sigprocmask()

此外,它不是"很长"。它属于sigset_t类型,可能是数组、结构或联合类型。在任何情况下,都应该简单地将其视为一个无序位集,每个信号一个位。

所以现在有一个用于SIGABRT的信号处理程序,但SIGABRT不会在过程中信号模板中。

正确。无论您是否分配了信号处理程序,都不会影响信号掩码。

如果我使用 sigaddset(3) 阻塞 SIGABRT,信号处理程序在 SIGABRT 交付时sig_abrt收不到调用吗?这是否意味着信号模板会影响传递哪些信号?有什么区别?

如果所有线程都阻塞了 SIGABRT,则在信号被解锁(从信号掩码中删除)之前,它不会传递。如果使用sigwait()sigwaitinfo()sigtimedwait()消耗信号,则根本不会调用信号处理程序。

简短总结:

  • 信号可以定向到进程组(kill()pid == 0pid == -pgid)、特定进程(pid)或特定进程中的特定线程(pthread_kill()在同一进程中,tgkillLinux中的系统调用)。

  • 如果信号被定向到进程组,则该组中的每个进程都会接收信号的"副本"。

  • 信号
  • 掩码定义信号是被阻止还是立即传递。

  • 在每个过程中,每个信号

    • 可以有一个信号处理程序,或者

    • 被忽略(SIG_IGN"处理程序"),或

    • 具有默认处置(忽略 (Ign),使用(核心)或不使用(术语)核心转储终止进程;或者它可以停止(停止)或继续()目标线程或进程的执行)。有关详细信息,请参阅man 7 signal

  • 如果某些(但不是全部)线程阻塞了信号,并且该信号未针对特定线程,则内核会将信号定向到未阻塞信号的线程之一(随机)。

  • 有两种方法可以捕获信号:

    1. 使用信号处理程序。仅当信号被阻止时,信号才会传递到信号处理程序。如果信号被阻塞,则信号的传递将挂起,直到未被阻塞(或被下面的其他选项捕获)。

    2. sigwait()sigwaitinfo()sigtimedwait()。这些函数检查是否有任何信号挂起,如果是,则"捕获"它。他们检查的信号集由sigset_t类型的函数参数定义。

当内核向进程发送/转发信号时,它首先检查进程是否有未阻止该信号的线程。如果有这样的线程,它会通过该线程传递它。(如果信号具有信号处理程序,则会在该线程中调用该信号处理程序;否则,效果由信号处置决定。

如果信号被阻塞,内核会将其保留为等待进程。

如果进程使用指定信号集中的挂起信号调用sigwait()sigwaitinfo()sigtimedwait(),则接收有关该信号的信息,并捕获该信号。(它将不再处于挂起状态,并且不会导致调用信号处理程序;它被"消耗"。

如果进程更改其信号掩码,以便挂起的信号被解锁,则它由内核传递(就像在该时间点发送一样)。

另外,有没有办法在不使用 sigsetops(3) 和 sigprocmask(2) 函数的情况下阻止进程中的信号?

不。(您可以实现自己的sigsetops()和用于sigprocmask()的系统调用包装器,但仅此而已。


下面是一个示例程序example.c,可用于在单线程进程中探索信号处理程序、捕获信号和信号掩码:

#define  _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <errno.h>
/* Async-signal safe write-to-standard error function.
Keeps errno unchanged. Do not use stderr otherwise!
*/
static int wrerrpp(const char *ptr, const char *end)
{
const int  saved_errno = errno;
ssize_t    chars;
while (ptr < end) {
chars = write(STDERR_FILENO, ptr, (size_t)(end - ptr));
if (chars > 0)
ptr += chars;
else
if (chars != -1) {
errno = saved_errno;
return EIO;
} else
if (errno != EINTR) {
const int  retval = errno;
errno = saved_errno;
return retval;
}
}
errno = saved_errno;
return 0;
}
/* Write the supplied string to standard error.
Async-signal safe. Keeps errno unchanged.
Do not mix with stderr!
*/
static int wrerr(const char *ptr)
{
if (!ptr)
return 0;
else {
const char *end = ptr;
/* strlen() is not async-signal safe, so
find the end of the string the hard way. */
while (*end)
end++;
return wrerrpp(ptr, end);
}
}
/* Write the supplied long to standard error.
Async-signal safe. Keeps errno unchanged.
Do not mix with stderr!
*/
static int wrerrnum(const long  value)
{
unsigned long  u = (value < 0) ? (unsigned long)-value : (unsigned long)value;
char           buf[40];
char          *ptr = buf + sizeof buf;
char *const    end = buf + sizeof buf;
do {
*(--ptr) = '0' + (u % 10uL);
u /= 10uL;
} while (u > 0uL);
if (value < 0)
*(--ptr) = '-';
return wrerrpp(ptr, end);
}
/* Async-signal safe variant of strsignal().
Only covers a small subset of all signals.
Returns NULL if the signal name is not known. */
static const char *signal_name(const int signum)
{
switch (signum) {
case SIGHUP:    return "HUP";
case SIGINT:    return "INT";
case SIGQUIT:   return "QUIT";
case SIGKILL:   return "KILL";
case SIGSEGV:   return "SEGV";
case SIGTERM:   return "TERM";
case SIGUSR1:   return "USR1";
case SIGUSR2:   return "USR2";
case SIGCHLD:   return "CHLD";
case SIGCONT:   return "CONT";
case SIGSTOP:   return "STOP";
default:        return NULL;
}
}
/* Signal handler that reports its delivery immediately,
but does nothing else.
*/
static void report_signal(int signum, siginfo_t *info, void *ctx)
{
const char *sname = signal_name(signum);
wrerr("report_signal(): Received signal ");
if (sname)
wrerr(sname);
else
wrerrnum(signum);
if (info->si_pid) {
wrerr(" from process ");
wrerrnum(info->si_pid);
wrerr(".n");
} else
wrerr(" from kernel or terminal.n");
}
/* Install report_signal() handler.
*/
static int install_report_signal(const int signum)
{
struct sigaction  act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_sigaction = report_signal;
act.sa_flags = SA_SIGINFO;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}

int main(void)
{
sigset_t    mask;
siginfo_t   info;
const char *name;
int         signum;
if (install_report_signal(SIGINT) ||
install_report_signal(SIGCONT)) {
const char *errmsg = strerror(errno);
wrerr("Cannot install signal handlers: ");
wrerr(errmsg);
wrerr(".n");
return EXIT_FAILURE;
}
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGTERM);
sigprocmask(SIG_SETMASK, &mask, NULL);
printf("Process %ld is ready to receive signals! Runn", (long)getpid());
printf("tkill -USR1 %ldn", (long)getpid());
printf("tkill -USR2 %ldn", (long)getpid());
printf("tkill -HUP  %ldn", (long)getpid());
printf("tkill -TERM %ldn", (long)getpid());
printf("in another terminal; press Ctrl+C in this terminal; or press Ctrl+Z and runn");
printf("tfgn");
printf("in this terminal.n");
fflush(stdout);
/* Almost same as blocked mask, just without SIGUSR1 and SIGUSR2. */
sigemptyset(&mask);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGTERM);
do {
do {
signum = sigwaitinfo(&mask, &info);
} while (signum == -1 && errno == EINTR);
if (signum == -1) {
const char *errmsg = strerror(errno);
wrerr("sigwaitinfo(): ");
wrerr(errmsg);
wrerr(".n");
return EXIT_FAILURE;
}
name = signal_name(signum);
if (name)
printf("main(): Received signal %s from ", name);
else
printf("main(): Received signal %d from ", signum);
if (info.si_pid == 0)
printf("kernel or terminal.n");
else
printf("process %ld.n", (long)info.si_pid);
fflush(stdout);
} while (signum != SIGTERM);
return EXIT_SUCCESS;
}

例如编译它

gcc -Wall -O2 example.c -o example

我建议你准备两个终端。在一个终端中,使用

./example

并观察其输出。它将是这样的

进程 843 已准备好接收信号!跑
    杀死 -USR1 843
    杀死 -USR2 843
    杀 -HUP 843
    杀 - 第843条
在另一个终端中;在此终端中按 Ctrl+C;或按 Ctrl+Z 并运行
    盖瑞
在这个终端中。

无法捕获 KILL 和 STOP 信号。KILL 将始终终止进程,而 STOP 将始终停止("暂停")进程。

如果在该终端中按Ctrl+C,内核将向进程发送 INT 信号。(这将通过report_signal()信号处理程序提供。

如果在该终端中按Ctrl+Z,内核将向进程发送 STOP 信号。shell 检测到这一点,将./example推送到作业控制下,并允许您输入新的 shell 命令。fg命令将./example带回前台,shell 向其发送 CONT 信号,以便./example将继续执行。

USR1 和 USR2 信号被阻止,因此它们永远不会传递到report_signal()信号处理程序。

HUP和TERM信号也被阻塞,但它们被主线程通过sigwaitinfo()接收。

当程序收到 TERM 信号时,程序退出。

最新更新