使用fork创建进程树

时间:2018-10-27 17:52:40

标签: c linux ubuntu fork

我正在尝试创建图中所示的进程树。基本上,如果级别是平均的,我想创建一个子进程并终止父进程。如果级别是奇数,我想创建两个子进程,然后终止父进程。我现在已经编写了一个程序,但是我认为很难看到我的程序实际上在创建哪个进程树。我在代码中写了一些注释,以解释我的想法。我还想输出树的底部子代的PID,但我的代码无法正确执行。

enter image description here

#include <stdio.h> 
#include <stdlib.h>
#include <sys/types.h> 
#include <unistd.h> 

int main(int argc, char *argv[]){ 
    pid_t pid, ppid; 
    int n, i;
    int childstate;
    int count = 0; 

    if(argc != 2){ 
        printf("Wrong number of arguments"); 
        exit(-1); 
    } 
    n = atoi(argv[1]); 

    fork(); //start process 0
    for(i = 1; i < n + 1; i++){ 
        if(i % 2 != 0){ 
            fork(); //if odd level start 1 child process
             if(getpid() == 0){
                kill (getppid(), 9); //terminate parent process
            }
        } else { 
            if(fork() > 0){  //start new process
                fork(); //if new process is not a child start another process
                if(getpid() == 0){
                    kill (getppid(), 9); //terminate parent process
                }
            } 
        } 
        if(i == n){ //print pid of leaves (not working correctly)
            printf("Process: %d \n", getpid()); 
        } 
    }
    return 0; 
}

4 个答案:

答案 0 :(得分:2)

根据您的描述,您的基本逻辑应该是:

void fork_loop(int level, int stop) {
    if (level > stop) return;
    if (is_even(level)) {
        fork_child(level, stop);
        exit(0);
    } else {
        fork_child(level, stop);
        fork_child(level, stop);
        exit(0);
    }
}

fork_child()呼叫fork()的地方。子进程将调用fork_loop(level+1, stop),而父进程将返回。

答案 1 :(得分:2)

fork(); //if odd level start 1 child process
if (getpid() == 0){
    kill (getppid(), 9); //terminate parent process
}

此逻辑是错误的:getpid()在子进程中不返回0 / fork在子进程中不返回 pid -它仅返回0表示它是子进程-它可以通过之前调用getpid来知道父进程的pid。

逻辑应为:

pid_t child = fork();
if (child > 0) {
    // use exit instead of kill! exit terminates this process
    exit(0);
}
if (child < 0) {
    ... an error occurred in fork ...
}

答案 2 :(得分:2)

getpid永远不能为零。正如我在最上面的评论中提到的那样,您希望父母等待孩子,而不是反过来等待太多叉子。

这是我认为可行的清理版本:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
    pid_t pid;
    pid_t ppid;
    int i;
    int n;
    int pcur;
    int pcnt;

    if (argc != 2) {
        printf("Wrong number of arguments");
        exit(-1);
    }
    n = atoi(argv[1]);

    pid = fork();                               // start process 0
    if (pid != 0) {
        wait(NULL);
        n = -5;
    }

    for (i = 1; i < n + 1; i++) {
        // odd/even level -- get number of children to start
        // NOTE: you may need to reverse this if
        if (i % 2 != 0)
            pcnt = 1;
        else
            pcnt = 2;

        // get parent pid
        ppid = getpid();

        // do the forks
        for (pcur = 0;  pcur < pcnt;  ++pcur)
            fork();

        // get current pid
        pid = getpid();

        // parent should wait on children
        if (pid == ppid) {
            while (wait(NULL) >= 0);
            break;
        }

        // print pid of leaves (not working correctly)
        if (i == n) {
            printf("Process: %d\n", pid);
        }
    }

    return 0;
}

答案 3 :(得分:1)

  

我还想输出树的底部子代的PID,但我的代码无法正确执行。

让您的进程以Dot语言输出树,并使用Graphviz输出树。

例如,如果将以下内容保存为 tree.c

#define  _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

int process(const unsigned int level, const unsigned int maxlevel, FILE *dot)
{
    int           status = EXIT_SUCCESS, childstatus;
    unsigned int  children, i;
    pid_t         p, child[2];

    if (dot) {
        /* Output a node for this child, */
        fprintf(dot, "    \"%ld\" [ label=\"Process %ld\" ];\n", (long)getpid(), (long)getpid());

        /* and if not at the top level (0), an edge from our parent. */
        if (level)
            fprintf(dot, "    \"%ld\" -> \"%ld\";\n", (long)getppid(), (long)getpid());

        fflush(dot);
    }

    /* No more forking? */
    if (level >= maxlevel) {
        if (level)
            exit(status);
        else
            return status;
    }

    /* Odd levels create two child processes, even one. */
    if (level & 1)
        children = 2;
    else
        children = 1;

    /* Fork the child processes, */
    for (i = 0; i < children; i++) {
        child[i] = fork();
        if (child[i] == -1) {
            fprintf(stderr, "Cannot fork: %s.\n", strerror(errno));
            exit(EXIT_FAILURE);
        } else
        if (!child[i]) {
            /* have each child run process() and nothing else, */
            exit(process(level + 1, maxlevel, dot));
        }
        /* This line is run in parent only. */
    }

    /* and wait for them. */
    for (i = 0; i < children; i++) {
        if (child[i] != -1) {
            do {
                p = waitpid(child[i], &childstatus, 0);
            } while (p == -1 && errno == EINTR);
            if (p != child[i])
                status = EXIT_FAILURE;
        } else
            status = EXIT_FAILURE;
    }

    if (level)
        exit(status);
    else
        return status;
}

int dot_process_tree(const int levels, FILE *out)
{
    int  retval = EXIT_SUCCESS;

    if (out) {
        fprintf(out, "digraph {\n");
        fflush(out);
    }

    if (levels > 0)
        retval = process(0, levels - 1, out);

    if (out) {
        fprintf(out, "}\n");
        fflush(out);
    }

    return retval;
}

int main(void)
{
    return dot_process_tree(5, stdout);
}

并使用

进行编译和运行
reset ; gcc -Wall -Wextra -O2 tree.c -o tree && ./tree | dot -Tx11

您将获得一个漂亮的图形处理树。 (使用dot -Tsvg > out.svgdot -Tpng > out.png将其另存为SVG或PNG图像。)在我的系统上:

example process tree

请注意,没有任何理由使进程ID处于树顺序。虽然例如Linux以一种相当有序的方式递归它们,它们可以以任何顺序排列,甚至是完全随机的。因此,请勿对PID进行任何假设。

点语言本身很简单。上面程序的输出类似于

digraph {
    "12375" [ label="Process 12375" ];
    "12377" [ label="Process 12377" ];
    "12375" -> "12377";
    "12378" [ label="Process 12378" ];
    "12377" -> "12378";
    "12379" [ label="Process 12379" ];
    "12377" -> "12379";
    "12380" [ label="Process 12380" ];
    "12378" -> "12380";
    "12381" [ label="Process 12381" ];
    "12379" -> "12381";
    "12382" [ label="Process 12382" ];
    "12380" -> "12382";
    "12384" [ label="Process 12384" ];
    "12381" -> "12384";
    "12383" [ label="Process 12383" ];
    "12380" -> "12383";
    "12385" [ label="Process 12385" ];
    "12381" -> "12385";
}

这应该很明显;节点由进程ID命名,[ label="Title" ]设置节点中的文本。它与上图不同,因此进程ID不同。

在点中,如果用作名称,则确实需要引用数字,但是如果名称以字母开头,则无需引用。有关更多详细信息,请参见Graphviz documentation。 (Node, Edge and Graph Attributes页面是您通常需要的页面。)

如果要在每个节点中显示级别,请使用

        fprintf(dot, "    \"%ld\" [ label=\"Process %ld, level %u\" ];\n", (long)getpid(), (long)getpid(), level + 1);
process()

。 (它使用级别0转发,所有非零级别都是子进程,级别0是原始进程。这就是为什么级别0返回,而所有其他级别exit()的原因。)