让我们考虑以下代码(请不要写,有命名问题,结构问题等,我也知道这一点)。它写的是为其3个孩子写出随机生成的x,y,z和r(和pid)数字,但经常发生它只打印两个/一个“得到这个......”行,我不知道为什么你能解释一下我,问题是什么,或者纠正我的代码吗?
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h> //fork
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h> //lock
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include "sys/ipc.h"
#include "sys/sem.h"
int child;
int cs[3];
int fd[2];
int t;
int parent;
int child;
void okay(int sign)
{
t = 0;
}
void rr(int sign)
{
char b[50];
while(read(fd[0], &b, 50)<0) sleep(0.5);
printf("Got this: %s \n", b);
}
void ch(int argc, char** argv)
{
printf("Mypid: %i\n", getpid());
close(fd[0]);
while(t==1) sleep(1);
srand((unsigned)time(NULL)); // init
int x,y,z,r,pid;
x = rand() % 101; y = rand() % 101; z = rand() % 101; r = rand() % 101;
pid = getpid();
char b[50];
sprintf(b, "%i %i %i %i %i", pid, x, y, z, r);
while(write(fd[1], b, 50)<0) sleep(0.2);
kill(parent, SIGUSR2);
close(fd[1]);
}
int main(int argc, char** argv)
{
if(argc < 4)
{
printf("Too few args!\n");
return 0;
}
pipe(fd);
t = 1;
parent = getpid();
signal(SIGUSR1, okay);
child = fork();
if(child < 0) perror("FORK");
if(child > 0)
{
cs[0] = child;
child = fork();
if(child < 0) perror("FORK");
if(child > 0)
{
cs[1] = child;
child = fork();
if(child < 0) perror("FORK");
if(child > 0)
{
cs[2] = child; // MAIN
printf("%i %i %i\n", cs[0], cs[1], cs[2]);
close(fd[1]);
signal(SIGUSR2, rr);
kill(cs[0], SIGUSR1); kill(cs[1], SIGUSR1); kill(cs[2], SIGUSR1);
int status;
waitpid(cs[0], &status, 0);
waitpid(cs[1], &status, 0);
waitpid(cs[2], &status, 0);
close(fd[0]);
}else
{ // ch 3
ch(argc, argv);
}
}else
{ // ch 2
ch(argc, argv);
}
}else
{ // ch 1
ch(argc, argv);
}
return 0;
}
答案 0 :(得分:1)
即使使用各种修改版本的代码,我也能够获得描述的行为。例如,我从代码的诊断版本获得的一条跟踪是:
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()
并设置sa.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作为消息的一部分打印出来,那么在处理这样的多个进程时会很有帮助。我使用'Adult'而不是'Parent',因此输出与标记为'Child'的行整齐对齐。请注意,在子分叉之前设置信号处理程序。在多CPU机器上,无法保证进程的执行顺序,因此将信号设置保留到分叉后最多是不明智的,并且最坏的情况下可能导致意外死亡。
通过读入main()
中的父代码来替换信号处理程序中的读数;这是处理输入的更令人满意的方式。你应该在信号处理程序中尽可能少地做。 C标准不能可靠地支持更多:
ISO / IEC 9899:2011§7.14.1
signal
功能¶5如果信号的出现不是调用
abort
或raise
函数的结果, 如果信号处理程序引用具有静态或线程的任何对象,则行为未定义 存储持续时间不是无锁原子对象,而是通过为其分配值 声明为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
中设置为main()
)。
通过进行更多打印操作,您可以更轻松地进行调试。您可以使用循环来杀死和收集您的孩子(尽管可以像您一样编写循环;但是不要在一行上放置三个函数调用)。