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

问题描述 投票:2回答:2

我正在通过以下代码片段体验我认为是一些奇怪的行为。当我调用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: %d\n", i++);
    sleep(1);
  }

  return 0;
}

谢谢!

c signals posix interrupt sigint
2个回答
1
投票

当你声明它时,你不清楚struct sigaction actionStruct的记忆。您设置单个值。结构的所有其余部分将包含来自先前函数的堆栈上的值。这可能是您从不同功能中获得不同行为的原因。

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


1
投票

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

是。

  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,这是Term(终止进程),如man 7 signal手册页中所述。
  3. printf()不是异步信号安全函数(在较新系统的man 7 signal-safety手册页中,在旧系统的man 7 signal手册页中列出)。 根据确切的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("Run\n");
    printf("    kill -HUP %ld\n", (long)getpid());
    printf("    kill -INT %ld\n", (long)getpid());
    printf("    kill -TERM %ld\n", (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信号,那么只有一个信号会被传送。 OS /内核确实尽力确保信号的传递,但开发人员应该知道技术限制。

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

© www.soinside.com 2019 - 2024. All rights reserved.