-1

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

1 回答 1

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()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 如果信号的出现不是调用abortorraise函数的结果,则如果信号处理程序引用具有静态或线程存储持续时间的任何对象,该对象不是无锁原子对象,则行为未定义,而不是通过赋值到声明为 的对象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(尽管它设置为1in main())。观察不准!

您可以通过更多的打印操作使您的调试更容易。您可以使用循环来杀死和收集您的孩子(尽管可以像您所做的那样写出循环;但是不要将三个函数调用放在一行上)。

于 2012-12-16T22:21:09.253 回答