[英]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.