簡體   English   中英

C 問題 Linux 進程,使用 kill() 和處理程序的子進程(錯誤)

[英]C problem Linux process, child processes using kill() and a handler (ERROR)

從鍵盤輸入n ,一個 integer; 該進程創建 n 個子進程,然后每個子進程使用kill()向父進程發送信號,並使用處理程序 function, h計數。

為什么它計算的進程比有的多?

#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

volatile int s = 0;
       
void h(int n) {
    signal(n, h); 
    ++s;
}
    
int main(int argc, char *argv[]) {
    sigset_t ms; int n;

    for(n = 1; n < NSIG; ++n) 
        signal(n, h);
    sigfillset(&ms); 
    sigprocmask(SIG_SETMASK, &ms, NULL);
    sigemptyset(&ms);

    //first part
    for(n = 1; n <= atoi(argv[1]); ++n)
        if(fork()) 
            sigsuspend(&ms);
        else {
            kill(getppid(), 1 + rand() % 3);
            exit(0);
        }

    //the kill part
    while(wait(NULL) != -1)
        ;
    printf("%d\n", s);
    return 0;
}

正如Barmar評論中指出的那樣,根本問題是您正在接收SIGCHLD信號以及調用kill()的子進程生成的信號。

切線地,請注意孩子們都會向父母發送相同的信號——您不會在具有不同種子的孩子中調用srand() ,因此它們都從rand()獲得相同的值。

通常,您應該更喜歡sigaction()而不是signal() 更喜歡sigaction()的一個原因是處理程序不會自動重置為默認值,從而消除了計時問題。

您從垂死的孩子那里獲得 SIGCHLD 信號,以及從調用kill()的孩子那里獲得 SIGHUP、SIGINT 或 SIGQUIT 之一。 您使用sigprocmask()會阻止傳遞信號,除非調用sigsuspend() 您可以獲得鏈接的信號——一個 SIGINT 和一個 SIGCHLD 可能處於掛起狀態,並且會發生對信號處理程序的兩次單獨調用,從而導致信號計數大於預期。

下面顯示的代碼適當注意如何避免在信號處理程序中使用printf() 並使用write()報告有限數量的信息。 POSIX 允許在信號處理程序中使用write() C 標准沒有(部分原因是它不將write()識別為標准 function,但主要是因為它對信號處理程序中可能發生的事情非常嚴格)。

代碼測試sigfillset()sigemptyset()因為它們是 macOS 上帶有逗號運算符的宏,其 RHS 只是0 使用我的默認編譯選項,GCC 抱怨未使用的值。 因此,測試使用返回的值,即使它始終為零。

請注意,我在運行 macOS 而不是 Linux 的 Mac 上運行了測試。 但是,這兩個系統的一般行為可能非常相似。

這是您的代碼的最小修改,將信號報告添加到信號處理程序並在sigsuspend()之前和之后打印(源代碼sig17.c ):

#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

static volatile sig_atomic_t s = 0;
static char message[] = "Signal XX received\n";

static void h(int n)
{
    signal(n, h);
    ++s;
    message[7] = (n / 10) + '0';
    message[8] = (n % 10) + '0';
    write(2, message, sizeof(message) - 1);
}

static void err_error(const char *fmt, ...);

int main(int argc, char *argv[])
{
    sigset_t ms;
    int n;

    if (argc != 2)
        err_error("Usage: %s num-children\n", argv[0]);

    for (n = 1; n < NSIG; ++n)
        signal(n, h);
    if (sigfillset(&ms) != 0)
        err_error("sigfillset() failed\n");
    sigprocmask(SIG_SETMASK, &ms, NULL);
    if (sigemptyset(&ms) != 0)
        err_error("sigemptyset() failed\n");

    // first part
    for (n = 1; n <= atoi(argv[1]); ++n)
    {
        int pid = fork();
        if (pid < 0)
            err_error("fork() failed\n");
        else if (pid != 0)
        {
            printf("%d: Started %d\n", n, pid);
            sigsuspend(&ms);
            printf("%d: Signalled!\n", n);
        }
        else
        {
            kill(getppid(), 1 + rand() % 3);
            exit(0);
        }
    }

    // the kill part
    int corpse, status;
    while ((corpse = wait(&status)) != -1)
        printf("Dead: %5d - 0x%.4X\n", corpse, status);
    printf("%d\n", s);
    return 0;
}

static void err_error(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    exit(1);
}

在此代碼的多次運行之一(指定了 5 個孩子)中,我得到了 output:

1: Started 26778
Signal 02 received
1: Signalled!
2: Started 26779
Signal 20 received
2: Signalled!
3: Started 26780
Signal 02 received
3: Signalled!
4: Started 26781
Signal 20 received
Signal 02 received
4: Signalled!
5: Started 26782
Signal 20 received
Signal 02 received
5: Signalled!
Dead: 26780 - 0x0000
Dead: 26779 - 0x0000
Dead: 26778 - 0x0000
Dead: 26781 - 0x0000
Dead: 26782 - 0x0000
7

如您所見,生成的信號始終為 2(SIGINT); 信號 20 是 SIGCHLD。 在此示例中,程序捕獲了 5 個 SIGINT 信號中的 4 個和 5 個 SIGCHLD 信號中的 3 個。 請注意,有時會調用兩個信號處理程序,因為 SIGINT 和 SIGCHLD 信號都處於未決狀態。

sigprocmask()調用確保沒有信號被異步傳遞。 如果刪除該調用,則代碼會檢測到 10 個信號(源代碼sig19.c ):

#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

static volatile sig_atomic_t s = 0;
static char message[] = "Signal XX received\n";

static void h(int n)
{
    signal(n, h);
    ++s;
    message[7] = (n / 10) + '0';
    message[8] = (n % 10) + '0';
    write(2, message, sizeof(message) - 1);
}

static void err_error(const char *fmt, ...);

int main(int argc, char *argv[])
{
    sigset_t ms;
    int n;

    if (argc != 2)
        err_error("Usage: %s num-children\n", argv[0]);

    for (n = 1; n < NSIG; ++n)
        signal(n, h);

    if (sigemptyset(&ms) != 0)
        err_error("sigemptyset() failed\n");

    for (n = 1; n <= atoi(argv[1]); ++n)
    {
        int pid = fork();
        if (pid < 0)
            err_error("fork() failed\n");
        else if (pid != 0)
        {
            printf("%d: Started %d\n", n, pid);
            sigsuspend(&ms);
            printf("%d: Signalled!\n", n);
        }
        else
        {
            kill(getppid(), 1 + rand() % 3);
            exit(0);
        }
    }

    int corpse, status;
    while ((corpse = wait(&status)) != -1)
        printf("Dead: %5d - 0x%.4X\n", corpse, status);
    printf("%d\n", s);
    return 0;
}

static void err_error(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    exit(1);
}

樣品 output:

1: Started 26857
Signal 02 received
1: Signalled!
Signal 20 received
2: Started 26858
Signal 02 received
2: Signalled!
Signal 20 received
3: Started 26859
Signal 02 received
3: Signalled!
Signal 20 received
4: Started 26860
Signal 02 received
4: Signalled!
Signal 20 received
5: Started 26861
Signal 02 received
5: Signalled!
Dead: 26860 - 0x0000
Dead: 26859 - 0x0000
Dead: 26858 - 0x0000
Dead: 26857 - 0x0000
Signal 20 received
Dead: 26861 - 0x0000
10

請注意,在此代碼中,信號出現的時間不是調用sigsuspend()時。 如果SIGCHLD信號未被捕獲,則代碼可靠地產生 5 個計數(源代碼sig23.c )。 這也會產生不同的信號(確定性地),並且孩子以不同的狀態退出。

#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

static volatile sig_atomic_t s = 0;
static char message[] = "Signal XX received\n";

static void h(int n)
{
    signal(n, h);
    ++s;
    message[7] = (n / 10) + '0';
    message[8] = (n % 10) + '0';
    write(2, message, sizeof(message) - 1);
}

static void err_error(const char *fmt, ...);

int main(int argc, char *argv[])
{
    sigset_t ms;
    int n;

    if (argc != 2)
        err_error("Usage: %s num-children\n", argv[0]);

    for (n = 1; n < NSIG; ++n)
    {
        if (n != SIGCHLD && n != SIGKILL && n != SIGSTOP)
            signal(n, h);
    }

    if (sigemptyset(&ms) != 0)
        err_error("sigemptyset() failed\n");

    for (n = 1; n <= atoi(argv[1]); ++n)
    {
        int pid = fork();
        if (pid < 0)
            err_error("fork() failed\n");
        else if (pid != 0)
        {
            printf("%d: Started %d\n", n, pid);
            sigsuspend(&ms);
            printf("%d: Signalled!\n", n);
        }
        else
        {
            int sig = n % 3 + 1;
            kill(getppid(), sig);
            exit(sig);
        }
    }

    int corpse, status;
    while ((corpse = wait(&status)) != -1)
        printf("Dead: %5d - 0x%.4X\n", corpse, status);
    printf("%d\n", s);
    return 0;
}

static void err_error(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    exit(1);
}

樣品 output:

1: Started 27162
Signal 02 received
1: Signalled!
2: Started 27163
Signal 03 received
2: Signalled!
3: Started 27164
Signal 01 received
3: Signalled!
4: Started 27165
Signal 02 received
4: Signalled!
5: Started 27166
Signal 03 received
5: Signalled!
Dead: 27165 - 0x0200
Dead: 27164 - 0x0100
Dead: 27163 - 0x0300
Dead: 27162 - 0x0200
Dead: 27166 - 0x0300
5

您可以 go 使用代碼響鈴更改,調整信號處理方式。 然而,“計數過多”的根本原因是處理 SIGCHLD 信號以及調用kill()的子進程生成的信號。

暫無
暫無

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

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