C 信号处理程序从另一个函数安装时不会卸载



我正在经历我认为是以下代码片段的一些奇怪行为。当我调用addHandler()来安装信号处理程序时,每次我在终端中按CTRL+C(发送 SIGINT)时都会调用信号处理程序,但是如果我将调用替换为addHandler()addHandler()函数的内容(当前已注释掉),处理程序只被调用一次(据我所知, 这是预期的行为),后续的 SIGINT 实际上将终止进程,因为没有安装用户处理程序。我在这里错过了一些基本的东西吗?为什么通过另一个函数安装处理程序,似乎永久安装它?

我相信它比这更微妙...但这是代码:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sigHandler(int signal) {
printf("Signal handler beiing called!n");
}
void addHandler() {
struct sigaction actionStruct;
actionStruct.sa_handler = sigHandler;
sigaction(SIGINT, &actionStruct, NULL);
}
int main() {
addHandler();
/*struct sigaction actionStruct;
actionStruct.sa_handler = sigHandler;
sigaction(SIGINT, &actionStruct, NULL);*/
int i = 0;
while (1) {
printf("In while with i: %dn", i++);
sleep(1);
}
return 0;
}

谢谢!

声明struct sigaction actionStruct时不会清除内存。设置单个值。结构的所有其余部分都将包含上一个函数在堆栈上的值。这可能就是为什么不同函数具有不同行为的原因。

您需要用struct sigaction actionStruct = {};声明它或使用memset

我在这里错过了一些基本的东西吗?

是的。

  1. 您没有正确初始化struct sigaction actionStruct。本质上,您提供了随机.sa_flags,这导致了观察到的 OP 问题。

    建议初始化它的方法是使用memset()sigemptyset()

    memset(&actionStruct, 0, sizeof actionStruct);
    sigemptyset(&actionStruct.sa_mask);
    

    memset()将整个结构清除为零,包括任何填充。sigemptyset()清除信号处理程序本身运行时阻塞的信号集;即,它将其初始化为空集)。
     

  2. 您没有设置actionStruct.sa_flags成员。

    零是一个完全有效的值,但明确设置它对我们人类来说很重要,因为这样我们就可以读取意图

    例如,如果只希望处理程序运行一次,则可以设置actionStruct.sa_flags = SA_RESETHAND;。传递第一个信号后,SA_RESETHAND标志会导致处理程序重置为默认值。对于SIGINT,这是术语(终止进程),如 man 7 信号手册页中所述。
     

  3. printf()不是异步信号安全功能(如较新系统中的 MAN 7 信号安全手册页和旧系统中的 MAN 7 信号手册页所列)。

    根据确切的 C 库实现(有许多 POSIXy 系统),它可能会工作,可能会乱码输出,甚至可能导致进程崩溃。所以,不要那样做。
     

亲爱的读者,希望你真的对编写健壮的、便携的、POSIX 信号处理 C99 或更高版本的程序感兴趣,让我给你看一个例子。Breakme.c

#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <limits.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <errno.h>
/* Low-level, async-signal safe write routines.
*/
static int wrpp(const int descriptor, const char *ptr, const char *end)
{
while (ptr < end) {
ssize_t  n = write(descriptor, ptr, (size_t)(end - ptr));
if (n > 0)
ptr += n;
else
if (n != -1)
return EIO; /* Should not occur */
else
if (errno != EINTR)
return errno;
}
return 0;
}
static int wrs(const int descriptor, const char *s)
{
if (descriptor == -1)
return EBADF;
else
if (!s)
return EINVAL;
else {
/* Note: strlen() is not listed as an async-signal safe function. */
const char *end = s;
while (*end)
end++;
return wrpp(descriptor, s, end);
}
}
static int wrn(const int descriptor, const char *ptr, const size_t len)
{
if (descriptor == -1)
return EBADF;
else
return wrpp(descriptor, ptr, ptr + len);
}
static int wri(const int descriptor, const long value)
{
char           buffer[4 + (sizeof value) * (CHAR_BIT * 10) / 3];
char *const    end = buffer + sizeof buffer;
char          *ptr = buffer + sizeof buffer;
unsigned long  u = (value < 0) ? -value : value;
if (descriptor == -1)
return EBADF;
do {
*(--ptr) = '0' + (u % 10);
u /= 10uL;
} while (u);
if (value < 0)
*(--ptr) = '-';
return wrpp(descriptor, ptr, end);
}
/* 'Done' signal handler.
*/
static volatile sig_atomic_t  done = 0;
static void handle_done(int signum)
{
int saved_errno;
/* Note: Most commonly, we just use
done = 1;
here. In practice, we could also just use
done = signum;
because current POSIXy systems don't have a signal 0.
The following uses signum if it is nonzero,
and -1 for (signum == 0).
*/
done = (signum) ? signum : -1;
/* Before running functions that affect errno, save it. */
saved_errno = errno;
wrs(STDERR_FILENO, "handle_done(): Caught signal ");
wri(STDERR_FILENO, signum);
wrn(STDERR_FILENO, "n", 1);
/* Restore errno to its saved value. */
errno = saved_errno;
}
/* Helper function for installing the signal handler.
*/
static int install_done(int signum)
{
struct sigaction  act;
memset(&act, 0, sizeof act);
sigemptyset(&act.sa_mask);
act.sa_handler = handle_done;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}

int main(void)
{
int  i = 0;
if (install_done(SIGINT) ||
install_done(SIGHUP) ||
install_done(SIGTERM)) {
fprintf(stderr, "Cannot install signal handlers: %s.n", strerror(errno));
return EXIT_FAILURE;
}
printf("Runn");
printf("    kill -HUP %ldn", (long)getpid());
printf("    kill -INT %ldn", (long)getpid());
printf("    kill -TERM %ldn", (long)getpid());
printf("in another terminal window, or press Ctrl-C.n");
fflush(stdout);
while (!done) {
printf("(%d) ", i++);
fflush(stdout);
sleep(1);
}
printf("Terminated with done = %ld.n", (long)done);
return EXIT_SUCCESS;
}

编译并运行它,

例如
gcc -Wall -O2 breakme.c -o breakme
./breakme

请注意,四个wr*()函数是异步信号安全函数,它们输出字符串(wrs()),指定数量的字符(wrn())或有符号(长)整数(wri())到指定的低级描述符;这里为标准错误(STDERR_FILENO)。您不应该将它们与<stdio.h>函数混合在一起。

(请注意,breakme.c 仅在(某些)无法安装信号处理程序时才使用fprintf(stderr, ..),并立即退出(具有失败退出状态)。当然,我们可以使用三个wrs()调用来代替,首先将错误字符串抓取到像const char *msg = strerror(errno);这样的临时变量中,因为wr*()函数可能会修改errno,但我认为走那么远并不是真的明智。我相信程序尝试报告确切的问题,然后尽快退出就足够了。但是,在程序正常运行期间,我不会在fprintf(stderr,)使用,以免弄乱标准错误输出。

特别注意install_done()功能。如果成功将handle_done函数安装为指定的信号处理程序,则返回 0,否则返回 errno。

我建议您尝试该程序。例如,将done =行更改为done++;,将while (!done)更改为while (done < 3),以便只有捕获的第三个信号会导致程序退出。

最后,请注意,像INT这样的标准POSIX信号在技术上并不"可靠">:不能保证它们的交付。特别是,信号没有排队,因此如果您在第一个信号交付之前设法发送了两个INT信号,则只会传递一个信号。操作系统/内核确实尽最大努力确保信号的传递,但开发人员应该知道技术限制。

请注意,POSIX实时信号 -SIGRTMIN+0SIGRTMAX-0,包括;至少有8个或SIGRTMAX-SIGRTMIN+1 >= 8- 排队,尽管它们也不是完全可靠的。它们也支持通过sigqueue()函数传递一个 int 或 void 指针值的有效负载。您需要使用带有SA_SIGINFO的信号处理程序来捕获有效载荷,或者使用sigwaitinfo()/sigtimedwait()来捕获循环中被阻止的信号。 我相信修改上述程序以检测和显示有效载荷以及第二个程序(由用户同时单独运行)将信号和整数作为有效载荷发送到指定进程将很有趣,虽然非常简单;我建议将该程序编写为恰好采用三个参数(进程 ID、信号编号和有效负载整数)。

最新更新