如何在C中创建自定义多个进程?

时间:2018-12-21 15:52:34

标签: c fork

我想问你们一些C编程方面的帮助。基本上,我在fork()系统调用方面遇到问题。 这是我的问题: 我们有一个经理流程,该流程必须创建POP_SIZE个学生流程。在创建所有学生流程之前,经理流程和学生流程本身无法执行任何其他操作。 每个学生进程的标识是: 1)其标识号(6位整数) 2)在特定考试中获得的分数(整数)

这是我设法编写的代码:

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

int main(int argc, char *argv[]){
pid_t firstFork;
int *status;
int numStudents = 0;
pid_t managerChild, managerParent;
pid_t students[POP_SIZE];
int studentStatus[POP_SIZE];


switch(firstFork = fork()){
    case -1:
        perror("Something wrong with fork()\n");
        break;
    case 0:
        managerChild = getpid();
        printf("Manager Child Process %d started\n", managerChild);
        printf("I have to create %d Student Processes\n", POP_SIZE);
        for(int i = 0; i < POP_SIZE; i++){
            switch(students[i] = fork()){
                case -1:
                    perror("Something wrong with FORK in Manager Child Process\n");
                    break;
                case 0:
                    printf("Created first Student Process PID: %d\n", getpid());
                    numStudents++;
                    break;
                default:
                    printf("Haven't created all Student Processes\n");
                    waitpid(managerChild, status, WUNTRACED | WNOHANG);
                    printf("%d Student Processes succesfully created\n", numStudents);
                    break;
            }
        }
        break;
    default:
        for(int i = 0; i < POP_SIZE; i++)
            wait(NULL);
}

}

我需要一些帮助来理解在我的代码中放置wait(*status)waitpid(pid, *status, __options)函数的位置,以便达到上述要求? 此外,如何为每个进程分配并保持变量存储? 非常感谢

2 个答案:

答案 0 :(得分:1)

由于您将创建许多子进程,因此最好首先创建一个函数来创建该子进程,并使该函数执行调用方指定的功能。假设ID号和等级均为int。然后,

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

/* Run func(id, grade) in a child process.
   Returns the child process PID if success,
   or -1 with errno set in case an error occurs.
*/
pid_t run_child(int id, int grade,
                int (*func)(int id, int grade))
{
    pid_t  p;

    p = fork();
    if (p == -1) {
        /* fork() failed; it set errno to indicate the error. */
        return -1;
    } else
    if (!p) {
        /* Run child process function. When it returns,
           have the child exit with that exit status. */
        exit(func(id, grade));
    } else {
        /* Parent process. p is positive. */
        return p;
    }
}

请注意,第三个参数是函数指针。我们使用函数名称指定它。该函数必须采用两个int参数(分别为ID和等级),并返回一个int。例如:

/* Each child process runs this function.
*/
int child_process(int id, int grade)
{
    printf("Child: id = %d, grade = %d, PID = %d.\n", id, grade, (int)getpid());
    return EXIT_SUCCESS;
}

我们可以使用child_pid = run_child(123456, 5, child_process);创建一个运行该功能的子过程。注意如何使用函数名称来指定函数指针。标准的C qsort()函数使用完全相同的机制来允许对任何内容进行快速排序。调用者只需指定一个函数即可比较要排序的数组中的两个元素。

我们将创建多个孩子,并立即收获他们。这意味着编写一个可以重获所有子进程(实际上是blocking直到它们全部退出)的函数是有意义的。我们可能对其中至少一些的退出状态感兴趣,因此,让我们传递有趣的子进程PID,将状态保存到的int以及这些数组中的进程数作为参数:

/* Reap all child processes.
   If child_count > 0, child processes with PID in child_pid[]
   will have child_pid[] negated when reaped, with exit status saved
   in child_status.
   The function returns the number of child processes reaped.
*/
size_t reap_children(pid_t *child_pid, int *child_status, size_t child_count)
{
    size_t  reaped = 0;
    size_t  i;
    int     status;
    pid_t   p;

    while (1) {

        /* Reap a child process, if any. */
        p = wait(&status);
        if (p == -1) {
            /* errno == EINTR is not an error; it occurs when a
               signal is delivered to a hander installed without
               SA_RESTART flag.  This will not occur in this program,
               but it is good practice to handle that case gracefully. */
            if (errno == EINTR)
                continue;

            /* errno set by wait(). */
            return reaped;
        }

        /* Another child process was reaped. */
        reaped++;

        /* If the reaped child was one of the interesting ones,
           negate its pid and save the exit status. */
        for (i = 0; i < child_count; i++) {
            if (child_pid[i] == p) {
                child_pid[i] = -p;
                child_status[i] = status;
                break;
            }
        }
    }
}

请注意,p = wait(&status)将获得子进程。这意味着,如果已经退出一个或多个子进程,它将选择其中一个并返回其PID,并将退出状态保存为&status。如果剩下的所有子进程仍在运行,则调用将等待直到至少其中一个退出。如果没有更多子进程,它将返回-1设置为errno的{​​{1}}。

如果使用了信号处理程序,并且ECHILD还可以返回wait(),且-1设置为errno,如果信号被传递到安装了信号处理程序的信号处理程序中, EINTR标记为sigaction()。许多程序员都放弃了此检查(因为“它永远不会发生”),但是我喜欢包含该检查,因为它很容易,并且可以确保在我的代码中添加信号处理功能不会在以后给我带来麻烦。我也经常这样做。 (我的意思是增加信号处理。)

在收获相应的子流程时我们否定了pid的原因很简单:它使我们能够轻松地检测到收获了哪些子流程。 (POSIX表示所有进程ID都是正数,SA_RESTART是带符号的类型。对PID取反也是一种常用的技术;例如,请参见waitpid()。)

如果我们想获得一个特定的子进程,我们将使用pid_t。例如,

waitpid()

请注意,在POSIX / Unix术语中,“子进程”仅指由该进程创建的进程;不是“孙代”,而是由子进程创建的进程。

我更喜欢编写进程以从命令行接收参数。如果未指定参数,或者指定了 pid_t child, p; /* wait for 'child'. */ int status; do { p = waitpid(child, &status, 0); if (p == -1) { if (errno == EINTR) continue; break; } } while (p != child); if (p == child) { /* Reaped 'child', status in 'status'. */ } else { /* Error: failed to reap 'child'. See 'strerror(errno)'. */ } -h,则会显示一个简短的帮助(“用法”);这在POSIX和Unix命令行工具中非常普遍,因此非常直观。

以下--help将一个或多个main()作为命令行参数。对于每个进程,它都会创建一个子进程,并使用指定的ID和等级运行ID:grade函数。然后,主程序将全部收获它们,并描述每个子进程的退出状态。

child_process()

如果将以上内容另存为 example.c ,则可以使用例如

将其编译为 example
int main(int argc, char *argv[])
{
    pid_t  child_pid[argc];
    int    child_status[argc];
    int    count, i, n, arg, id, grade, status;
    char   dummy;

    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s ID:GRADE [ ID:GRADE ]*\n", argv[0]);
        fprintf(stderr, "\n");
        return EXIT_SUCCESS;
    }

    status = EXIT_SUCCESS;
    count = 0;

    for (arg = 1; arg < argc; arg++) {
        if (sscanf(argv[arg], "%d:%d %c", &id, &grade, &dummy) == 2) {
            child_pid[count] = run_child(id, grade, child_process);
            if (child_pid[count] == -1) {
                fprintf(stderr, "Cannot fork a child process: %s.\n", strerror(errno));
                status = EXIT_FAILURE;
            } else
                count++;
        } else {
            fprintf(stderr, "%s: Not a valid ID:GRADE specification.\n", argv[arg]);
            status = EXIT_FAILURE;
        }
    }

    if (count < 0) {
        fprintf(stderr, "No running child processes.\n");
        return EXIT_FAILURE;
    }

    n = reap_children(child_pid, child_status, count);
    printf("Reaped %d child processes.\n", n);

    for (i = 0; i < count; i++) {
        if (child_pid[i] < 0) {
            printf("Child process %d (%d of %d)", (int)(-child_pid[i]), i + 1, count);

            if (WIFEXITED(child_status[i])) {                   
                if (WEXITSTATUS(child_status[i]) == EXIT_SUCCESS)
                    printf(" exited with success (EXIT_SUCCESS), %d.\n", EXIT_SUCCESS);
                else
                if (WEXITSTATUS(child_status[i]) == EXIT_FAILURE)
                    printf(" exited with failure (EXIT_FAILURE), %d.\n", EXIT_FAILURE);
                else
                    printf(" exited with status %d.\n", WEXITSTATUS(child_status[i]));
            } else
            if (WIFSIGNALED(child_status[i])) {
                printf(" died from signal %d.\n", WTERMSIG(child_status[i]));
            } else {
                printf(" died from unknown causes.\n");
            }

        } else {
            printf("Child process %d (%d of %d) was lost!\n", (int)child_pid[i], i + 1, count);
        }
    }

    return status;
}

如果您随后运行说

gcc -Wall -O2 example.c -o example

输出将类似于

./example 100001:1 100002:5 100003:3 21532:4

请注意,Child: id = 100002, grade = 5, PID = 1260. Child: id = 100001, grade = 1, PID = 1259. Child: id = 100003, grade = 3, PID = 1261. Child: id = 21532, grade = 4, PID = 1262. Reaped 4 child processes. Child process 1259 (1 of 4) exited with success (EXIT_SUCCESS), 0. Child process 1260 (2 of 4) exited with success (EXIT_SUCCESS), 0. Child process 1261 (3 of 4) exited with success (EXIT_SUCCESS), 0. Child process 1262 (4 of 4) exited with success (EXIT_SUCCESS), 0. 的起始行可以采用任何顺序,因为子进程实质上是并行运行的。每个子进程一启动就运行,因此该示例不是对OP要求的复制粘贴回答。


如果要尝试复杂的流程层次结构,建议使用Graphviz对其进行可视化。例如, dot-kids.c

Child:

例如使用

进行编译
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

static void reap_all(void)
{
    pid_t  p;
    int    status;

    while (1) {
        p = wait(&status);
        if (p == -1) {
            if (errno == EINTR)
                continue;
            if (errno == ECHILD)
                return;

            fprintf(stderr, "Process %d: reap_all(): %s.\n", (int)getpid(), strerror(errno));
            return;
        }

        printf("    \"%d\" -> \"%d\" [ color=\"#ff0000\" ];\n", (int)p, (int)getpid());

        if (WIFEXITED(status)) {
            if (WEXITSTATUS(status) == EXIT_SUCCESS)
                printf("    \"%d\" [ label=\"%d\" ];\n", (int)p, (int)p);
            else
                printf("    \"%d\" [ label=\"%d (exit %d)\" ];\n", (int)p, (int)p, WEXITSTATUS(status));
        } else
        if (WIFSIGNALED(status))
            printf("    \"%d\" [ label=\"%d (signal %d)\" ];\n", (int)p, (int)p, WTERMSIG(status));
        else
            printf("    \"%d\" [ label=\"%d (lost)\" ];\n", (int)p, (int)p);

        fflush(stdout);
    }
}

static pid_t run_child(int (*child)(int depth, int width), int depth, int width)
{
    pid_t  p;

    fflush(stdout);
    fflush(stderr);

    p = fork();
    if (p == -1) {
        fprintf(stderr, "Process %d: Cannot fork: %s.\n", (int)getpid(), strerror(errno));
        return -1;
    } else
    if (!p) {
        exit(child(depth, width));
    } else {
        printf("    \"%d\" -> \"%d\" [ color=\"#0000ff\" ];\n", (int)getpid(), (int)p);
        fflush(stdout);
        return p;
    }
}

int child(int depth, int width)
{
    if (depth > 0) {
        while (width > 0)
            run_child(child, depth - 1, width--);
        reap_all();
    }
    return EXIT_SUCCESS;
}

int main(int argc, char *argv[])
{
    int  depth, width, i;
    char dummy;

    if (argc != 3 || !strcmp(argv[1], "-h") || !strcmp(argv[2], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s depth width | dot -Tx11\n", argv[0]);
        fprintf(stderr, "\n");
        return EXIT_SUCCESS;
    }

    if (sscanf(argv[1], " %d %c", &depth, &dummy) != 1 || depth < 0) {
        fprintf(stderr, "%s: Invalid depth.\n", argv[1]);
        return EXIT_FAILURE;
    }
    if (sscanf(argv[2], " %d %c", &width, &dummy) != 1 || width < 1) {
        fprintf(stderr, "%s: Invalid width.\n", argv[2]);
        return EXIT_FAILURE;
    }

    printf("digraph {\n");
    printf("    \"%d\" [ shape=\"box\", label=\"%d\" ];\n", (int)getpid(), (int)getpid());
    fflush(stdout);

    for (i = 0; i < width; i++)
        run_child(child, depth, width - 1);

    reap_all();
    printf("}\n");
    return EXIT_SUCCESS;
}

并使用例如

gcc -Wall -O2 dot-kids.c -o dot-kids

查看类似于以下内容的流程图 Graphviz dot diagram 数字是进程ID,蓝色箭头显示哪个进程创建了哪个进程,红色箭头显示哪个进程获得了哪个进程。

答案 1 :(得分:0)

我认为您的代码中有一些错误。我得到的输出是这样的:

5 Student Processes succesfully created
Haven't created all Student Processes
Haven't created all Student Processes
3 Student Processes succesfully created
4 Student Processes succesfully created
Created first Student Process PID: 11436
Created first Student Process PID: 11438
Created first Student Process PID: 11437
Haven't created all Student Processes
4 Student Processes succesfully created
Haven't created all Student Processes
3 Student Processes succesfully created
Created first Student Process PID: 11439
Haven't created all Student Processes
3 Student Processes succesfully created
Created first Student Process PID: 11440
Haven't created all Student Processes
3 Student Processes succesfully created
Created first Student Process PID: 11441
Haven't created all Student Processes
2 Student Processes succesfully created
Created first Student Process PID: 11442
Created first Student Process PID: 11443

您看到有太多孩子在执行,所以这会让您感到怀疑(尤其要注意,有时学生进程的数量似乎从印刷品到印刷品都在减少)。父级将继续执行for循环。但是,子代将从调用fork的那一点开始继续执行,并将其放入循环内,它也会派生创建另一个子代,依此类推。为避免这种情况,您需要break循环中的for用于子进程。

您可以尝试以下操作。我添加了一个变量jj,如果<0表示它是一个正在执行的子进程。在下一次循环迭代之前,将检查变量,并检查变量<0是否从for循环中断。

这不是最优雅的解决方案,但看起来还可以。

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

int main(int argc, char *argv[]){
pid_t firstFork;
int *status;
int numStudents = 0;
pid_t managerChild, managerParent;
pid_t students[POP_SIZE];
int studentStatus[POP_SIZE];


switch(firstFork = fork()){
    case -1:
        printf("Something wrong with fork()\n");
        break;
    case 0:
        managerChild = getpid();
        printf("Manager Child Process %d started\n", managerChild);
        printf("I have to create %d Student Processes\n", POP_SIZE);
        int jj = 0;
        for(int i = 0; i < POP_SIZE; i++){
            switch(students[i] = fork()){
                case -1:
                    printf("Something wrong with FORK in Manager Child Process\n");
                    jj = -1;
                    break;
                case 0:
                    printf("Created first Student Process PID: %d\n", getpid());
                    numStudents++;
                    jj = -1;
                    break;
                default:
                    printf("Haven't created all Student Processes\n");
                    waitpid(managerChild, status, WUNTRACED | WNOHANG);
                    printf("%d Student Processes succesfully created\n", numStudents);
                    break;
            }
            if (jj<0) break;
        }
        break;
    default:
        for(int i = 0; i < POP_SIZE; i++)
            wait(NULL);
}
}