Как создать пользовательские несколько процессов в C? - PullRequest
0 голосов
/ 21 декабря 2018

Я хотел бы попросить вас, ребята, помочь с программированием на Си.В основном у меня проблемы с 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 голосов
/ 22 декабря 2018

Поскольку вы будете создавать много дочерних процессов, лучше всего начать с создания функции, которая создает дочерний процесс и выполняет его, указанный вызывающей стороной.Предположим, что и идентификационный номер, и оценка 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 (идентификатор и оценку соответственно) и возвращать 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() использует точно такой же механизм, который позволяет быстро сортировать все;вызывающей стороне просто нужно указать функцию, которая может сравнивать два элемента в массиве для сортировки.

Мы будем создавать несколько дочерних элементов и пожинать их сразу.Это означает, что имеет смысл написать функцию, которая собирает все дочерние процессы, по существу блокируя до тех пор, пока все они не завершатся.Скорее всего, нас интересуют состояния выхода по крайней мере для некоторых из них, поэтому давайте передадим в качестве параметров интересные идентификаторы дочерних процессов, целочисленные значения для сохранения состояния и количество процессов в этих массивах:

/* 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, установленным на ECHILD.

Если использовались обработчики сигналов, wait() также может возвращать -1 с errno, установленным наEINTR, если сигнал был доставлен в обработчик сигнала, который был установлен без флага SA_RESTART с sigaction().Многие программисты отказываются от этой проверки (потому что «она никогда не произойдет»), но я действительно хотел бы включить эту проверку, потому что она проста и гарантирует, что добавление обработки кода в мой код не укусит меня в задницу позже.Я тоже очень часто это делаю.(Я имею в виду обработку сигналов.)

Причина, по которой мы отменяем pids при получении соответствующего дочернего процесса, проста: она позволяет нам легко определить, какие дочерние процессы были получены.(POSIX говорит, что все идентификаторы процессов являются положительными, а pid_t является типом со знаком. Отрицание PID также является широко используемой техникой; просто посмотрите, например, waitpid().)

Еслимы хотели получить определенный дочерний процесс, мы бы использовали waitpid().Например,

    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)'. */
    }

Обратите внимание, что в терминологии POSIX / Unix «дочерний процесс» относится только к процессам, созданным только этим процессом;не "внуки", процессы, созданные дочерними процессами.

Я предпочитаю писать свои процессы, чтобы получать параметры из командной строки.Если параметры не указаны или указаны -h или --help, отображается краткая справка («использование»);это очень часто встречается в инструментах командной строки POSIX и Unix и поэтому очень интуитивно понятно.

Следующий main() принимает один или несколько ID:grade в качестве параметров командной строки.Для каждого из них создается дочерний процесс, и он запускает функцию child_process() с указанным идентификатором и оценкой.Затем основная программа пожнет их все и опишет состояние завершения каждого дочернего процесса.

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;
}

Если вы сохраните вышеприведенное как example.c , вы можете скомпилировать его в пример с использованием, например,

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.

Обратите внимание, что начальныйChild: строки могут быть в любом порядке, потому что дочерние процессы выполняются по существу параллельно.Каждый дочерний процесс запускается сразу после его запуска, поэтому этот пример не является ответом копирования и вставки на требования OP.


Если вы хотите экспериментировать со сложными иерархиями процессов, я рекомендую использовать Graphvizвизуализировать их.Например, dot-kids.c :

#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

и запустите, используя, например,

./dot-kids 1 3 | dot -Tx11

, чтобы увидеть график процесса, аналогичный Graphviz dot diagram, где номера - это идентификаторы процессов, синие стрелки показывают, какиекакой процесс создан, а красные стрелки показывают, какой процесс какой был запущен.

0 голосов
/ 21 декабря 2018

Я думаю, что в вашем коде есть ошибки.Вывод, который я получаю, выглядит примерно так:

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);
}
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...