具有非确定性输出的C fork and pipe程序

时间:2012-12-16 22:07:22

标签: c signals posix fork pipe

让我们考虑以下代码(请不要写,有命名问题,结构问题等,我也知道这一点)。它写的是为其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;
}

1 个答案:

答案 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'的行整齐对齐。请注意,在子分叉之前设置信号处理程序。在多CP​​U机器上,无法保证进程的执行顺序,因此将信号设置保留到分叉后最多是不明智的,并且最坏的情况下可能导致意外死亡。

通过读入main()中的父代码来替换信号处理程序中的读数;这是处理输入的更令人满意的方式。你应该在信号处理程序中尽可能少地做。 C标准不能可靠地支持更多:

  

ISO / IEC 9899:2011§7.14.1signal功能

     

¶5如果信号的出现不是调用abortraise函数的结果,   如果信号处理程序引用具有静态或线程的任何对象,则行为未定义   存储持续时间不是无锁原子对象,而是通过为其分配值   声明为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())。不准确的观察!

通过进行更多打印操作,您可以更轻松地进行调试。您可以使用循环来杀死和收集您的孩子(尽管可以像您一样编写循环;但是不要在一行上放置三个函数调用)。