Сделать дочерний процесс приостановленным до тех пор, пока не будет получен родительский сигнал для выполнения задачи execl.

Я пытаюсь разработать простую симуляцию железной дороги после ответа на вопрос Сделать дочерний процесс ожидает получения родительского сигнала.

Моя задача: у меня есть ровно 5 процессов, представляющих поезда. Мне нужно создать эти 5 процессов (T1, T2, T3, T4 и T5) через fork() и приостановить каждый из них, пока все они не будут созданы. После этого родительский процесс отправит сигнал дочерним элементам, и каждый дочерний процесс будет использовать execl (т.е. execl(execl_path_name, CHILDETCONE, i, NULL);). После подачи сигнала родитель ждет, пока все дети выполнят свои задачи.

Я хорошо понимаю функцию обработчика, но не совсем понимаю эти моменты:

  1. Мне нужно вставить мой execl в функцию-обработчик?

  2. Я не понимаю значения этого последнего цикла из ответа на предыдущий вопрос:

    for (int i = 0; i < NUMBER_TRAINS; i++)
           {
               wait(NULL);
           }
    

Это мой код:

#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include "accessory.h"


#define NUMBER_TRACKS 16
#define NUMBER_STATIONS 8
#define NUMBER_TRAINS 5

#define TRACKS_INITIALS "MA"
#define STATION_INITIALS "S"
#define SIZE 256
#define CHILDETCONE "childETCone"

void handler(int sig);

int main(int argc , char *argv[]) {
    pid_t pid;
    pid_t pid_array[NUMBER_TRAINS];

    char track_name[2];
    char track_number[2];
    int execl_return;
    char str[2];
    char * execl_path_name;

    memset(pid_array, 0, sizeof(pid_array));

    /* create the MAx file initialized to zero */
    for (int i = 1; i < (NUMBER_TRACKS + 1); i++) {
        memset(track_name, '\0', sizeof(track_name));
        memset(track_number, '\0', sizeof(track_number));
        strcpy(track_name, TRACKS_INITIALS);
        sprintf(track_number, "%d", i);
        strcat(track_name, track_number);
        create_track_file(track_name, "", SIZE);
    }

    execl_path_name = get_file_name(CHILDETCONE, "", SIZE);
    printf("path %p\n", execl_path_name);


    for(int i = 0; i < NUMBER_TRAINS; i++) {
        pid = fork();

        if (pid < 0) {
            perror("fork");
            exit(1);
        }
        if (pid == 0) { //child
            //sprintf(str, "%d", i+1);
            //execl_return = execl(execl_path_name, CHILDETCONE, i, NULL);
            signal(SIGUSR1, handler);
            pause();
            exit(0);
        }
        //parent
        pid_array[i] = pid;
    }

    for (int j = 0; j < NUMBER_TRAINS; j++) {
        kill(pid_array[j], SIGUSR1);
        sleep(1);
    }

    for (int i = 0; i < NUMBER_TRAINS; i++) {
        wait(NULL);
    }

    return 0;
}

void handler(int sig) {
    printf("printed from child [%d]\n", getpid());
    printf("signal [%d]\n", sig);
}

person Gianni Spear    schedule 06.08.2018    source источник
comment
В коде присутствует состояние гонки, потому что родительский процесс может отправить сигнал SIGUSR1 до того, как дочерние процессы установят обработчик сигнала. Это может привести к тому, что ни один, некоторые или все дочерние процессы не будут завершены сигналом вместо его перехвата.   -  person Ian Abbott    schedule 06.08.2018
comment
Спасибо @IanAbbott, мне нужно спать, чтобы дать время?   -  person Gianni Spear    schedule 06.08.2018
comment
Сон, вероятно, подойдет, но не всегда.   -  person Ian Abbott    schedule 06.08.2018
comment
вы можете просто вызвать signal() перед тем, как fork() цикл будет унаследован обработчиком. И чтобы ответить на исходный вопрос: вызовите execl() после pause(), а не из обработчика сигнала   -  person Ingo Leonhardt    schedule 06.08.2018
comment
@IngoLeonhardt Есть ли еще состояние гонки между доставкой сигнала и pause()? Вероятно, потребуется заблокировать сигнал с помощью sigprocmask перед fork(), а потомок заменит вызов pause() на sigsuspend(). Хотя я особо не вдавался в подробности.   -  person Ian Abbott    schedule 06.08.2018


Ответы (1)


Мне нужно вставить мой execl в функцию-обработчик?

Нет. pause() вернется только после того, как процесс, в котором он вызывается, поймает сигнал, который вызывает запуск функции обработки сигналов. Таким образом, вызов execl может идти сразу после вызова pause. Думаю, в вашем случае это будет яснее.

Также обратите внимание, что POSIX стандартизирует список «асинхронно-сигнально-безопасных» функций, которые безопасны для вызова обработчика сигнала, и что для обработчика небезопасно вызывать других. execl есть в списке, но printf и другие функции потокового ввода-вывода нет. Обработчики сигналов не должны вызывать printf. Вашему конкретному обработчику сигналов вообще ничего не нужно делать.

Кроме того, рассмотрите возможность использования sigsuspend() вместо pause(), поскольку первый даст вам больше контроля над тем, какие сигналы запускают ваши поезда.

Я не понимаю значения этого последнего цикла из ответа на предыдущий вопрос:

for (int i = 0; i < NUMBER_TRAINS; i++)
       {
           wait(NULL);
       }

Функция wait() инструктирует вызывающий процесс блокировать, пока не завершится один из его дочерних процессов. Цикл выполняет столько wait() вызовов, сколько дочерних, так что при отсутствии ошибок основная программа не будет продолжена до тех пор, пока все ее дочерние элементы не завершатся.

Похоже, вы, возможно, пытались достичь чего-то подобного, вызывая sleep() в цикле с вызовом kill, но эта стратегия явно неверна. Во-первых, ожидание после каждого kill означает, что детские execl вызовы будут отложены по крайней мере на sleep время, что, как я понял, вам не нужно. Во-вторых, вы не можете заранее знать, сколько времени потребуется детям, чтобы закончить, поэтому одной секунды, которую вы позволяете, может оказаться недостаточно при некоторых обстоятельствах. В-третьих, поскольку вы, кажется, ожидаете, что дети будут бегать очень быстро, одна секунда, вероятно, намного больше, чем вам нужно в большинстве случаев.

person John Bollinger    schedule 06.08.2018