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

时间:2017-12-24 19:58:51

标签: c signals posix interrupt sigint

我遇到了以下代码片段,我认为这是一种奇怪的行为。当我调用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;
}

谢谢!

2 个答案:

答案 0 :(得分:1)

声明时,您不会清除var mainTable = $('#table1').dataTable({ "bStateSave": true, "stateSave": true, "bPaginate": false, "bLengthChange": false, "bFilter": false, "bInfo": false, "bAutoWidth": false }); /*SELECT OPTION */ mainTable.on('click', 'tbody tr', function () { $(this).toggleClass('selected'); }); $('#copyToTable2,#copyToTable3').on('click', function () { let $elem = $(this); var table = $("#table" + $elem.attr('id').replace(/[a-zA-Z]/ig, '')); var tbl_id = table.attr('id'); var $row = mainTable.find(".selected"); if (!$row.length) { console.log('You must select some rows to copy first'); return; } else { var r = confirm("Copy to table " + tbl_id + "?"); var table_to_copy = table.dataTable(); if (r == true) { copyRows(mainTable, table_to_copy); console.log("Copied!"); setTimeout('console.clear()', 2000); } } }); /* FROM HERE SAVE ROW ================*/ function copyRows(fromTable, toTable) { var $row = fromTable.find(".selected"), storageName = 'dataSet_' + toTable.attr('id'), //added this line dataSet = localStorage.getItem(storageName) ? JSON.parse(localStorage.getItem(storageName)) : []; //added this line $.each($row, function (k, v) { if (this !== null) { addRow = fromTable.fnGetData(this); toTable.fnAddData(addRow); dataSet.push(addRow); //added this line } }); localStorage.setItem(storageName, JSON.stringify(dataSet)); //added this line } /* =============== TABLE 2 ================== */ $('#table2').dataTable({ "data": localStorage.getItem('dataSet_table2') ? JSON.parse(localStorage.getItem('dataSet_table2')) : [], //changed here "columns": [{ "title": "First Name" }, { "title": "Last Name" }, { "title": "Action" }], "bStateSave": true, "stateSave": true, "bPaginate": false, "bLengthChange": false, "bFilter": false, "bInfo": false, "bAutoWidth": false }); $('#table3').dataTable({ "data": localStorage.getItem('dataSet_table3') ? JSON.parse(localStorage.getItem('dataSet_table3')) : [], //changed here "columns": [{ "title": "First Name" }, { "title": "Last Name" }, { "title": "Action" }], "bStateSave": true, "stateSave": true, "bPaginate": false, "bLengthChange": false, "bFilter": false, "bInfo": false, "bAutoWidth": false }); 的内存。您设置单个值。结构的所有其余部分将包含来自先前函数的堆栈上的值。这可能就是为什么你有不同功能的不同行为。

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

答案 1 :(得分: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手册页中)。< / p>

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

  4. 希望您,亲爱的读者,真的有兴趣编写健壮,便携,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),以便仅捕获第三个​​信号退出的计划。

    最后,请注意标准POSIX信号如INT在技术上并非“可靠”:无法保证其交付。特别是,信号不会排队,因此如果您在第一个信号发送之前设法发送了两个INT信号,则只会传送一个。 OS /内核确实尽力确保信号的传递,但开发人员应该知道技术限制。

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