C 带fork()的二叉进程树

C 带fork()的二叉进程树,c,fork,binary-tree,C,Fork,Binary Tree,我的OS类的第一个项目是使用fork()创建一个进程树,该进程树的深度由用户在命令行中指定。每个叶级节点都需要对数据进行排序,并使用命名管道(FIFO)将其传递回其父节点 我可以用fork()创建一个N深度树,每个进程有两个子进程。我不明白的是,如何将FIFO一直传递给树下的每个孩子,然后让这个过程对FIFO中的某些数据执行排序,然后再将其返回到树的顶部 以下是迄今为止我用于构建树的伪代码: void CreateTree(int level) { if level = 0 return

我的OS类的第一个项目是使用
fork()
创建一个进程树,该进程树的深度由用户在命令行中指定。每个叶级节点都需要对数据进行排序,并使用命名管道(FIFO)将其传递回其父节点

我可以用
fork()
创建一个N深度树,每个进程有两个子进程。我不明白的是,如何将FIFO一直传递给树下的每个孩子,然后让这个过程对FIFO中的某些数据执行排序,然后再将其返回到树的顶部

以下是迄今为止我用于构建树的伪代码:

void CreateTree(int level)
{
    if level = 0 return

    int left_child = fork();
    if(left_child != 0)        //we are the parent
    {
        int right_child = fork();
        if(right_child == 0)
            CreateTree(level - 1);
    }
    else
    {
        CreateTree(level-1);
    }
}

那么,我如何单独抓取每个进程来处理它们呢?

您提到了fifo,也称为命名管道,我们来看看。(此处代码假定为*nix):

这个快速示例显示了从父级向子级发送数据,让子级操作数据,然后将数据返回给父级。因此,您不是在“传递”fifo,而是每个进程(或子进程)都有权访问
char*
,这将为他们提供fifo的名称,以便他们可以根据需要打开它进行读写。您可以采用此概念,并将其扩展到您拥有的每个子节点:

int main()
{
    int fd, n, ret;
    fd_set rfds;
    char * myfifo = "/tmp/myfifo";

    mkfifo(myfifo, 0666);  // Create this buffer

    if(fork())     //Kid code
    {
      char kid_buffer[4] = {0};
      char temp;

      fd = open(myfifo, O_RDONLY); //Open the fifo for reading
      n = read(fd, kid_buffer, 4);

      printf("Kid %d read %d bytes, parent gave us %s\n",getpid(), n, kid_buffer);
      fflush(stdout);
      close(fd);

      // "sort" the data the parent gave us
      temp = kid_buffer[0];
      kid_buffer[0] = kid_buffer[1];
      kid_buffer[1] = kid_buffer[2];
      kid_buffer[2] = temp;
      kid_buffer[3] = '\0';
      printf("Kid %d reoriginized the list %s\n",getpid(), kid_buffer);
      fflush(stdout);

      // send the data back
      fd = open(myfifo, O_WRONLY);
      write(fd, kid_buffer, strlen(kid_buffer));
      close(fd);
      return 0; 
    }
    else
    {
      char arr[] = "abc";

      //Open the fifo for writing
      fd = open(myfifo, O_WRONLY);
      write(fd, arr, strlen(arr));  //Sent my data to kid
      printf("Parent process %d, just sent my data %s to the kid\n", getpid(), arr);
      fflush(stdout);
      close(fd);

      //Open the fifo for reading
      fd = open(myfifo, O_RDONLY);
      n = read(fd, arr, 4);

      // show the data we got back
      printf("Parent %d read %d bytes, kid gave us back %s\n",getpid(), n, arr);
      fflush(stdout);
      close(fd);
    }

    unlink(myfifo);

    return 0;
}
因此,从这里的输出中,您可以看到父级创建了自己的数组“abc”,并且它被子级(通过FIFO传递)修改为“bca”,现在它与父级一起返回并格式化

mike@linux-4puc:~> ./a.out 
Parent process 4295, just sent my data abc to the kid
Kid 4294 read 3 bytes, parent gave us abc
Kid 4294 reoriginized the list bca
Parent 4295 read 3 bytes, kid gave us back bca
  • 为每个孩子分配一个数字(不是PID;在有PID之前,您需要知道数字!)
  • 创建一个FIFO(
    mkfifo()
    ),其名称为
    FIFO.N
    ,其中N是数字
  • 每个孩子都知道该写入哪个FIFO
  • 每个家长都知道要从哪个FIFO读取。(大概父母只是在运行合并而不是排序。)
想必,孩子们也都知道他们需要排序哪些数据


初始化所有FIFO后,如何知道哪个进程正在执行以及何时执行?当我在构建树之后回到主程序时,有没有办法根据PID和控制语句来确定哪个进程正在运行

流程可分为“叶”流程和“非叶”流程

叶进程没有任何子进程。它们执行排序分配,并将排序后的数据写入输出FIFO。它们将在FIFO上被阻止,直到它们的父进程打开它进行读取。当它们完成写入时,它们关闭FIFO,父进程获得EOF

每个非叶进程正在合并来自其两个子进程的排序数据。每个非叶进程都需要知道它自己的输出FIFO是什么(根节点可能写入标准输出而不是FIFO),以及它的两个子进程的FIFO是什么。可能非叶进程创建fifo.$$.1和fifo.$$.2(其中,$$是已经运行的非叶进程的PID),而不是让父进程预先创建它们。然后它分叉它的两个子级,用一个变量指示每个子级使用哪个FIFO。然后,非叶进程打开两个FIFO进行读取(以及它自己的输出FIFO进行写入),并合并两个数据流(从两个数据流中读取一行,将较小的写入输出,读取替换行,直到其中一个出现EOF,然后从另一个完成读取)。一旦完成,进程将删除它创建的两个FIFO(基本卫生),并关闭其输出FIFO并退出


在顶层(原始流程),基本机制与任何非叶流程相同;它创建两个FIFO,启动两个子FIFO,打开FIFO进行读取,在两个流上进行合并,写入标准输出,而不需要与另一个FIFO混淆。

您没有说明任何数据流要求,例如叶子要排序的数据源。在分工方面,叶节点将进行排序,但分支只需合并。从某种意义上说,您正在创建一个使用进程和FIFO而不是堆栈的混合

如前所述,您可以使用简单但不雅观的方法来分配要排序的值数组,并在主进程中预先创建所有FIFO。基于每个子节点的标识符或索引号,它将从整个阵列和适当的FIFO中选择一系列数据(例如,
FIFO.N
,用于节点N用于向其父节点传输数据的FIFO)。回想一下,例如,使用
fork
创建的子进程共享其父进程的地址空间,并且可以在全局范围内查看数组

二叉树很好地组合成一个数组。根据维基百科

二叉树也可以作为数组中的隐式数据结构以广度优先的顺序存储,如果树是一个完整的二叉树,这种方法不会浪费空间。在这种紧凑的排列中,如果节点有索引i,则其子节点位于索引2i+1(对于左子节点)和2i+2(对于右子节点)处,而其父节点(如果有)位于索引处⌊(i-1)/2⌋ (假设根的索引为零)

注意⌊x⌋ 是不大于x的最大整数,也称为。在C语言中,您可以通过将
(i-1)/2
的值赋给
int
类型的变量来获得下限

要在树的周围穿行节点标识符,可以使用如下代码

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

void proc_tree(int i, int current_depth, int max_depth)
{
  pid_t kid = fork();

  if (kid == -1) {
    fprintf(stderr, "[%d]: fork: %s\n", getpid(), strerror(errno));
  }
  else if (kid == 0) {
    /* child */
    printf("[%d]: i=%d (depth %d)\n", getpid(), i, current_depth);

    if (current_depth < max_depth) {
      proc_tree(2*i+1, current_depth+1, max_depth);
      proc_tree(2*i+2, current_depth+1, max_depth);
    }

    exit(EXIT_SUCCESS);
  }
  else {
    /* parent */
    pid_t pid;
    int status;
    pid = waitpid(kid, &status, 0);
    if (pid == -1)
      fprintf(stderr, "[%z]: waitpid: %s\n", getpid(), strerror(errno));
  }
}
#包括
#包括
#包括
无效进程树(int i,int current\u depth,int max\u depth)
{
pid_t kid=fork();
如果(kid==-1){
fprintf(stderr,[%d]:fork:%s\n,getpid(),strerror(errno));
}
else if(kid==0){
/*孩子*/
printf(“[%d]:i=%d(深度%d)\n”,getpid(),i,当前深度);
if(当前深度<最大深度){
过程树(2*i+1,当前深度+1,最大深度);
过程树(2*i+2,当前深度+1,最大深度);
}
退出(退出成功);
}
否则{
/*母公司*/
pid_t pid;
智力状态;
pid=waitpid(孩子和状态,0);
int main(int argc, char *argv[])
{
  int depth;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s depth\n", argv[0]);
    return EXIT_FAILURE;
  }

  depth = atoi(argv[1]);
  if (depth < 0) {
    fprintf(stderr, "%s: depth must be non-negative\n", argv[0]);
    return EXIT_FAILURE;
  }

  proc_tree(0, 0, depth);

  return EXIT_SUCCESS;
}
$ ./tree-sort 3 [28837]: i=0 (depth 0) [28838]: i=1 (depth 1) [28839]: i=3 (depth 2) [28840]: i=7 (depth 3) [28841]: i=8 (depth 3) [28842]: i=4 (depth 2) [28843]: i=9 (depth 3) [28844]: i=10 (depth 3) [28845]: i=2 (depth 1) [28846]: i=5 (depth 2) [28847]: i=11 (depth 3) [28848]: i=12 (depth 3) [28849]: i=6 (depth 2) [28850]: i=13 (depth 3) [28851]: i=14 (depth 3)