簡體   English   中英

從另一個函數安裝時,C 信號處理程序不會卸載

[英]C Signal handler doesn't uninstall when installed from another function

我正在使用以下代碼片段體驗我認為是一些奇怪的行為。 當我打電話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;
}

謝謝!

聲明時不會清除struct sigaction actionStruct的內存。 您設置了一個值。 結構體的其余部分將包含來自前一個函數的堆棧中的值。 這可能就是為什么不同的函數有不同的行為。

您需要使用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 ,這是Term (終止進程),如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("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;
}

使用 eg 編譯並運行它

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

注意,4名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、信號編號和有效負載整數)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM