简体   繁体   English

C 问题 Linux 进程,使用 kill() 和处理程序的子进程(错误)

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

Enter n , an integer, from the keyboard;从键盘输入n ,一个 integer; the process creates n child processes, then every one of them sends a signal to the parent process using kill() , counting with the handler function, h .该进程创建 n 个子进程,然后每个子进程使用kill()向父进程发送信号,并使用处理程序 function, h计数。

Why does it count more processes than there are?为什么它计算的进程比有的多?

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

As noted in a comment by Barmar , the fundamental problem is that you are receiving SIGCHLD signals as well as the signals generated by the child processes calling kill() .正如Barmar评论中指出的那样,根本问题是您正在接收SIGCHLD信号以及调用kill()的子进程生成的信号。

Tangentially, note that the children will all send the same signal to the parent — you don't call srand() in the children with different seeds, so they all get the same value from rand() .切线地,请注意孩子们都会向父母发送相同的信号——您不会在具有不同种子的孩子中调用srand() ,因此它们都从rand()获得相同的值。

Generally, you should prefer sigaction() over signal() .通常,您应该更喜欢sigaction()而不是signal() One reason to prefer sigaction() is that the handler is not automatically reset to the default, eliminating a timing problem.更喜欢sigaction()的一个原因是处理程序不会自动重置为默认值,从而消除了计时问题。

You get SIGCHLD signals from the dying children, as well as one of SIGHUP, SIGINT or SIGQUIT from the child calling kill() .您从垂死的孩子那里获得 SIGCHLD 信号,以及从调用kill()的孩子那里获得 SIGHUP、SIGINT 或 SIGQUIT 之一。 Your use of sigprocmask() prevents signals from being delivered except when sigsuspend() is called.您使用sigprocmask()会阻止传递信号,除非调用sigsuspend() You can get chained signals — one SIGINT and one SIGCHLD can be pending, and two separate calls to the signal handler occur, leading to the larger than expected signal count.您可以获得链接的信号——一个 SIGINT 和一个 SIGCHLD 可能处于挂起状态,并且会发生对信号处理程序的两次单独调用,从而导致信号计数大于预期。

The code shown below takes due note of How to avoid using printf() in a signal handler?下面显示的代码适当注意如何避免在信号处理程序中使用printf() and uses write() to report a limited amount of information.并使用write()报告有限数量的信息。 POSIX permits the use of write() in a signal handler; POSIX 允许在信号处理程序中使用write() the C standard doesn't (in part because it doesn't recognize write() as a standard function, but mainly because it is very stringent about what can happen in a signal handler ). C 标准没有(部分原因是它不将write()识别为标准 function,但主要是因为它对信号处理程序中可能发生的事情非常严格)。

The code tests sigfillset() and sigemptyset() because they are macros on macOS with a comma operator, the RHS of which is simply 0 .代码测试sigfillset()sigemptyset()因为它们是 macOS 上带有逗号运算符的宏,其 RHS 只是0 With my default compilation options, GCC complains about the unused value.使用我的默认编译选项,GCC 抱怨未使用的值。 So, the tests use the returned value, even though it's always zero.因此,测试使用返回的值,即使它始终为零。

Note that I ran the tests on a Mac running macOS rather than Linux.请注意,我在运行 macOS 而不是 Linux 的 Mac 上运行了测试。 However, the general behaviour of the two systems will probably be very similar.但是,这两个系统的一般行为可能非常相似。

Here is a minimal adaptation of your code, adding signal reporting to the signal handler and printing before and after sigsuspend() (source code sig17.c ):这是您的代码的最小修改,将信号报告添加到信号处理程序并在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);
}

On one of the many runs of this code (with 5 children specified), I got the output:在此代码的多次运行之一(指定了 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

As you can see, the generated signal is always 2 (SIGINT);如您所见,生成的信号始终为 2(SIGINT); signal 20 is SIGCHLD.信号 20 是 SIGCHLD。 In this example, the program caught 4 of the 5 SIGINT signals and 3 of the 5 SIGCHLD signals.在此示例中,程序捕获了 5 个 SIGINT 信号中的 4 个和 5 个 SIGCHLD 信号中的 3 个。 Note that sometimes two signal handlers were called because both a SIGINT and a SIGCHLD signal were pending.请注意,有时会调用两个信号处理程序,因为 SIGINT 和 SIGCHLD 信号都处于未决状态。

The sigprocmask() call ensures no signals are delivered asynchronously. sigprocmask()调用确保没有信号被异步传递。 If that call is removed, then the code detects 10 signals (source code sig19.c ):如果删除该调用,则代码会检测到 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);
}

Sample output:样品 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

Note that in this code, the signals appear at times other than when sigsuspend() is called.请注意,在此代码中,信号出现的时间不是调用sigsuspend()时。 If the SIGCHLD signal is not trapped, then the code produces a count of 5 reliably (source code sig23.c ).如果SIGCHLD信号未被捕获,则代码可靠地产生 5 个计数(源代码sig23.c )。 This also generates different signals (deterministically), and the children exit with different statuses.这也会产生不同的信号(确定性地),并且孩子以不同的状态退出。

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

Sample output:样品 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

You can go on ringing the changes with the code, tweaking the way that signals are handled.您可以 go 使用代码响铃更改,调整信号处理方式。 Nevertheless, the fundamental cause of the 'over-count' is that SIGCHLD signals are handled as well as those generated by the child processes calling kill() .然而,“计数过多”的根本原因是处理 SIGCHLD 信号以及调用kill()的子进程生成的信号。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM