簡體   English   中英

C - SIGINT 處理程序不能與多個線程一起工作,每個線程都有一個 popen 進程

[英]C - SIGINT handler not working with multiple threads, each of which has a popen process

我有一個小的 C 程序,它執行以下操作:

  1. 打開多個線程,每個線程使用popen()生成一個ssh進程。
  2. 對於每個線程,處理popen()返回的FILE中的 output。 (想想打開ssh命令和拖尾)。

我試圖做到這一點,如果用戶按下Ctrl+C (一次),所有打開的ssh進程(由線程產生)將被殺死,進程將相應地退出。

我已經嘗試過類似於此處最佳答案的解決方案: POSIX pthread programming

但是,如果我將線程 function 更改為執行我正在執行的操作(調用popen() ),則此示例有效,它需要我Ctrl+C幾次(大概是打開的線程數) 我猜這與popen()打開的ssh進程沒有忽略SIGINT信號,允許它傳遞到我定義的處理程序這一事實有關。 這個對嗎?

這是我當前的代碼:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void handler(int signo) {
    printf("I'm in the handler with SIGNAL %d!", signo);
    exit(0);
}

void *thread(void *argument) {
    // Hardcoding to "myuser@myhost.com" here for sake of example, but you
    // could image the host is different per thread
    FILE *file = popen("ssh myuser@myhost.com \"tail -f /path/to/some/log.log\"", "r");
    if (file == null) {
        printf("Error opening file\n");
        exit(1);
    }

    char line[2048];
    while (fgets(line, sizeof(line), fp) != NULL) {
        printf("%s\n", line);
    }
    return NULL;
}

int main(void) {
    // Block the SIGINT signal. The threads will inherit the signal mask.
    // This will avoid them catching SIGINT instead of this thread.
    sigset_t sigset, oldset;
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGINT);
    pthread_sigmask(SIG_BLOCK, &sigset, &oldset);

    // Spawn the two threads.
    pthread_t thread1, thread2;
    pthread_create(&thread1, NULL, thread, &(unsigned int){1});
    pthread_create(&thread2, NULL, thread, &(unsigned int){2});

    // Install the signal handler for SIGINT.
    struct sigaction s;
    s.sa_handler = handler;
    sigemptyset(&s.sa_mask);
    s.sa_flags = 0;
    sigaction(SIGINT, &s, NULL);

    // Restore the old signal mask only for this thread.
    pthread_sigmask(SIG_SETMASK, &oldset, NULL);

    // Wait for SIGINT to arrive.
    pause();

    // Cancel both threads.
    pthread_cancel(thread1);
    pthread_cancel(thread2);

    // Join both threads.
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    // Done.
    puts("Terminated.");
    return EXIT_SUCCESS;
}

我對正在發生的事情的假設是否正確? 如果沒有,有什么想法我做錯了嗎?

當您啟動您的 ssh 程序時,您將使用屏蔽的 SIGINT 啟動它們,因此 pipe 結束時的那些程序將忽略 SIGINT。 您可以通過將 popen 命令更改為“sleep 30; ls”之類的命令來測試這一點,啟動您的程序,按 ctrl-C,然后鍵入“ps s”。 您可能會看到幾行格式為“sh -c sleep 30; ls”和“sleep 30”的行及其信號配置。 如果你等到它們自然死亡,然后再次運行程序,但這次用“Ctrl-\”(SIGTERM)點擊它,你會注意到它會立即清理。

您可以將 popen 行更改為:

sigset_t nset;
pthread_sigmask(SIG_SETMASK, &oldset, &nset);
FILE *file = popen("ssh myuser@myhost.com \"tail -f /path/to/some/log.log\"", "r");
pthread_sigmask(SIG_SETMASK, &nset, NULL);

並將 oldset 的定義移動到通用。 大多數情況下,您會得到想要的效果,但是在這兩個對 pthread_sigmask() 的調用之間存在競爭條件,其中 SIGINT 可以在此上下文中調用處理程序而不是預期的處理程序。

要修復競爭條件,您需要編寫自己的 popen()-like function,然后 fork(2)s 在 exec(2)-ing 之前將子進程中的信號配置設置為您希望的方式shell。

不幸的是,信號和線程確實不能很好地混合。

暫無
暫無

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

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