二叉进程树与叉()

时间:2012-10-23 17:49:12

标签: c fork binary-tree

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

我可以创建一个带有fork()的N深度树,每个进程有2个子节点。我无法弄清楚的是如何将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);
    }
}

那么我如何单独抓取每个进程来与它们一起工作?

3 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

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

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

二叉树很好地包装到一个数组中。根据维基百科上的Binary tree

  

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

请注意,⌊ x ⌋是不大于 x 的最大整数,也称为floor of 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 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)

答案 2 :(得分:0)

  • 为每个孩子分配一个号码(不是PID;在有PID之前你需要知道号码!)。
  • 创建一个名为mkfifo()的FIFO(fifo.N),其中N是数字。
  • 然后每个孩子都知道要写入哪个FIFO。
  • 每个家长都知道要从哪个FIFO读取。 (据推测,父母只是合并而不是一种合并。)

据推测,孩子们不知何故都知道他们要分类的数据。


  

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

流程可以分为“叶子”和“非叶子”流程。

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

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

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