重写的答案
即使使用各种修改版本的代码,我也能够获得所描述的行为。例如,我从带有诊断功能的代码版本中得到的一个跟踪是:
14607 at work
Children: 14608 14609 14610
Children signalled
Child 14609: signal 30 - setting t to 0
Child 14608: signal 30 - setting t to 0
Child 14610: signal 30 - setting t to 0
Child 14609: at work
Child 14610: at work
Child 14608: at work
Child 14609: sending 14609 65 24 97 0
Child 14609: exiting
Child 14610: sending 14610 87 17 23 57
Adult 14607: signal 31 - reading input
Child 14610: exiting
Child 14608: sending 14608 5 89 95 8
Child 14608: exiting
Adult 14607: got <<14609 65 24 97 0>>
Adult 14607: signal 31 - reading input
Adult 14607: got <<14610 87 17 23 57>>
Child 1 ended
Child 2 ended
Child 3 ended
14607 exiting
您可以看到父级从 14609 和 14610 获取数据,但不是从 14608 获取数据。我将其归因于信号的使用。它们对于 IPC 来说是一个非常糟糕的机制。而且,在这种情况下,他们似乎对时间不可靠。这是使用sigaction()
andsa.sa_mask
值设置为阻止所有信号 ( sigfillset(&sa.sa_mask)
) 的代码。
但是,实际上没有任何需要使用从孩子返回给父母的信号。我已将信号处理程序保留在适当的位置,以便父级通知子级进行编织,但将其简化为简单地将volatile sig_atomic_t
变量的值(t
按名称,仍然)从 1 更改为 0。表达式是“使用”信号编号参数(sign
在代码中调用);当我在 Mac OS X 10.7.5 上使用 GCC 4.7.1 进行编译时,它避免了警告:
gcc -O3 -g -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
pipes-13905948.c -o pipes-13905948
种子srand()
将时间与进程的 PID 混合以从每个子进程中提供不同的值(单独使用 PID 也可以做到这一点)。我将原始文件中的 16 个标题(包括两次重复)删除为 7 个。我已将其删除rr()
,因为父级不再响应来自子级的信号。我已经重组了代码main()
所以它不会跳出页面的 RHS。该代码包括有关正在发生的事情的大量诊断。如果大多数消息都打印了 PID 作为消息的一部分,则在处理这样的多个进程时会很有帮助。我使用“成人”而不是“父母”,因此输出与标记为“儿童”的行整齐对齐。请注意,信号处理程序是在孩子分叉之前设置的。在多 CPU 机器上,无法保证进程将执行的顺序,因此将信号设置留到分叉之后是不明智的,最坏的情况是可能导致意外死亡。
信号处理程序中的读取被替换为读取中的父代码main()
;这是处理输入的更令人满意的方式。您的目标应该是尽可能少地使用信号处理程序。C 标准不能可靠地支持更多:
ISO/IEC 9899:2011 §7.14.1signal
功能
¶5 如果信号的出现不是调用abort
orraise
函数的结果,则如果信号处理程序引用具有静态或线程存储持续时间的任何对象,该对象不是无锁原子对象,则行为未定义,而不是通过赋值到声明为 的对象volatile sig_atomic_t
,或者信号处理程序调用标准库中的任何函数,而不是abort
函数、_Exit
函数、
quick_exit
函数或signal
函数,其第一个参数等于与导致调用的信号对应的信号编号处理程序。
POSIX 更为宽松,但您仍然需要非常小心在信号处理程序中执行的操作,并且您应该尽可能少地在信号处理程序中执行操作。
这些更改导致此代码:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
static int fd[2];
static volatile sig_atomic_t t = 1;
static int parent;
static void okay(int sign)
{
t = (sign == 0);
}
static void ch(void)
{
int pid = getpid();
printf("Child %i: at work\n", pid);
close(fd[0]);
while (t == 1)
{
printf("Child %d: pausing on t\n", pid);
pause();
}
srand((unsigned)time(NULL) ^ pid);
int x = rand() % 101;
int y = rand() % 101;
int z = rand() % 101;
int r = rand() % 101;
char b[50];
sprintf(b, "%i %i %i %i %i", pid, x, y, z, r);
printf("Child %d: sending %s\n", pid, b);
while (write(fd[1], b, strlen(b)) < 0)
printf("Child %d: write failed\n", pid);
close(fd[1]);
printf("Child %d: exiting\n", pid);
exit(0);
}
int main(void)
{
int cs[3];
pipe(fd);
parent = getpid();
printf("%d at work\n", parent);
struct sigaction sa;
sa.sa_flags = 0;
sigfillset(&sa.sa_mask);
sa.sa_handler = okay;
sigaction(SIGUSR1, &sa, 0);
if ((cs[0] = fork()) < 0)
perror("fork 1");
else if (cs[0] == 0)
ch();
else if ((cs[1] = fork()) < 0)
perror("fork 2");
else if (cs[1] == 0)
ch();
else if ((cs[2] = fork()) < 0)
perror("fork 3");
else if (cs[2] == 0)
ch();
else
{
printf("Children: %i %i %i\n", cs[0], cs[1], cs[2]);
close(fd[1]);
kill(cs[0], SIGUSR1);
kill(cs[1], SIGUSR1);
kill(cs[2], SIGUSR1);
printf("Children signalled\n");
char buffer[64];
int nbytes;
while ((nbytes = read(fd[0], buffer, sizeof(buffer)-1)) > 0)
{
buffer[nbytes] = '\0';
printf("Adult %d: read <<%s>>\n", parent, buffer);
}
int status;
waitpid(cs[0], &status, 0);
printf("Child 1 ended\n");
waitpid(cs[1], &status, 0);
printf("Child 2 ended\n");
waitpid(cs[2], &status, 0);
printf("Child 3 ended\n");
close(fd[0]);
}
printf("%d exiting\n", (int)getpid());
return 0;
}
代码在错误处理上仍然不稳定;有很多未经检查的系统调用和未报告的结果(如子状态)。我不相信在失败时重试写入,但代码从未被执行过。
这是代码修订版的痕迹。
15745 at work
Children: 15746 15747 15748
Children signalled
Child 15746: at work
Child 15746: sending 15746 63 4 70 89
Child 15748: at work
Child 15746: exiting
Child 15747: at work
Adult 15745: read <<15746 63 4 70 89>>
Child 15748: sending 15748 44 0 99 37
Child 15748: exiting
Child 15747: sending 15747 3 69 68 97
Adult 15745: read <<15748 44 0 99 37>>
Child 15747: exiting
Adult 15745: read <<15747 3 69 68 97>>
Child 1 ended
Child 2 ended
Child 3 ended
15745 exiting
有几次,我得到了如下输入:
Adult 15734: read <<15736 83 95 64 2915737 42 63 66 89>>
这将进程 15736 和 15737 的输出组合成单个读取结果。我对此不满意;AFAIK,读取应该将单独子项的原子写入作为单独的消息。在没有进一步研究的情况下,我将把它归结为 Mac OS X 的一个怪癖。
原始答案
由于您使用的是signal()
而不是sigaction()
,因此您的信号处理程序可能会在调用信号处理程序之前重置为 SIGDFL。您可以okay()
通过添加以下内容来解决此问题:
void okay(int sign)
{
signal(sign, okay);
t = 0;
}
您可以通过检查处理程序中的返回值来监控这是否是一个问题signal()
。
您的其余代码当前未使用t
(尽管它设置为1
in main()
)。(观察不准!)
您可以通过更多的打印操作使您的调试更容易。您可以使用循环来杀死和收集您的孩子(尽管可以像您所做的那样写出循环;但是不要将三个函数调用放在一行上)。