【问题标题】:C Signal handler doesn't uninstall when installed from another function从另一个函数安装时,C 信号处理程序不会卸载
【发布时间】:2017-12-24 19:58:51
【问题描述】:

我正在使用以下代码 sn-p 遇到我认为是一些奇怪的行为。当我调用addHandler() 来安装信号处理程序时,每次我在终端中按CTRL+C(发送SIGINT)时都会调用信号处理程序,但是如果我用@987654324 的内容替换对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;
}

谢谢!

【问题讨论】:

  • 你使用什么系统和libc?在我的系统上,每次在main()main() 中定义信号处理程序时,每次按下C-c 都会打印Signal handler beiing called!\n
  • FWIW,您应该避免从信号处理程序调用 printf,因为它是不可重入的。
  • 您没有初始化 actionStruct 中的其他字段,因此这可能是您看到的行为的原因。我建议先添加memset(&amp;actionStruct, 0, sizeof(actionStruct));

标签: c signals posix interrupt sigint


【解决方案1】:

你声明struct sigaction actionStruct的时候并没有清除它的内存。您设置一个值。结构的所有其余部分将包含来自先前函数的堆栈中的值。这可能就是为什么不同的函数会有不同的行为。

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

【讨论】:

    【解决方案2】:

    我在这里缺少一些基本的东西吗?

    是的。

    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)。您不应该将这些与&lt;stdio.h&gt; 函数混为一谈。

    (请注意,仅当(某些)信号处理程序无法安装并立即退出(失败退出状态)时,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 &lt; 3),这样只有捕获到的第三个​​ 信号才会导致程序退出。

    最后,请注意,像INT 这样的标准 POSIX 信号在技术上并不“可靠”:无法保证它们的交付。特别是,信号没有排队,所以如果你设法在第一个被传递之前发送了两个INT 信号,那么只会传递一个。操作系统/内核会尽最大努力确保传递信号,但开发人员应该了解技术限制。

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

    【讨论】:

      猜你喜欢
      • 2010-09-17
      • 2014-02-06
      • 2023-03-24
      • 2015-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-15
      • 2023-04-02
      相关资源
      最近更新 更多