【问题标题】:C problem Linux process, child processes using kill() and a handler (ERROR)C问题Linux进程,使用kill()和处理程序的子进程(错误)
【发布时间】:2022-01-17 18:00:11
【问题描述】:

从键盘输入n,一个整数;该进程创建n个子进程,然后每个子进程都使用kill()向父进程发送信号,并使用处理函数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;
}

【问题讨论】:

  • 您应该将s 声明为sigatomic_t
  • 但是你知道为什么会显示更多的过程吗?
  • 可能是因为当孩子退出时父母收到SIGCHLD 信号?
  • 请注意,所有子节点都会向父节点发送相同的信号——您不会在具有不同种子的子节点中调用srand(),因此它们都会从rand() 获得相同的值。
  • 您可以尝试 (1) 将 SIGCHLD 重置为其默认配置,并结合 (2) 确保没有孩子选择 SIGCHLD 作为他们要发送的随机信号。您可能会免费获得 (2),因为在我所知道的所有实现中,它的数量都大于 3,但明确表示不会有什么坏处。或者也许只为您最初期望的信号注册您的信号处理程序。

标签: c linux signals fork


【解决方案1】:

正如Barmarcomment 中所指出的,根本问题是您正在接收SIGCHLD 信号以及由调用kill() 的子进程生成的信号。

切线,请注意子代都会向父代发送相同的信号——您不会在具有不同种子的子代中调用srand(),因此它们都从rand() 获得相同的值。

通常,您应该更喜欢sigaction() 而不是signal()。首选sigaction() 的一个原因是处理程序不会自动重置为默认值,从而消除了计时问题。

您会从垂死的孩子那里获得 SIGCHLD 信号,以及来自调用 kill() 的孩子的 SIGHUP、SIGINT 或 SIGQUIT 之一。您对sigprocmask() 的使用会阻止传递信号,除非调用sigsuspend()。您可以获得链接的信号 - 一个 SIGINT 和一个 SIGCHLD 可能处于挂起状态,并且会发生对信号处理程序的两次单独调用,从而导致信号计数大于预期。

下面显示的代码适当注意How to avoid using printf() in a signal handler? 并使用write() 报告有限数量的信息。 POSIX 允许在信号处理程序中使用write(); C 标准没有(部分是因为它不承认write() 是标准函数,但主要是因为它对what can happen in a signal handler 非常严格)。

代码测试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 个孩子)中,我得到了输出:

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);
}

示例输出:

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);
}

示例输出:

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

您可以继续使用代码进行更改,调整处理信号的方式。然而,“计数过多”的根本原因是 SIGCHLD 信号的处理与调用 kill() 的子进程生成的信号一样。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-12
    • 1970-01-01
    • 1970-01-01
    • 2023-04-11
    • 1970-01-01
    相关资源
    最近更新 更多