0

I am trying to develop a simple railroad simulation following the answer to the question Make children process wait until receiving parent's signal.

My task: I have exactly 5 process representing trains. I need to create these 5 process (T1, T2, T3, T4, and T5) via fork(), and pause each one until all of them are created. After that the parent process will send a signal to the children, and each child will use an execl (i.e. execl(execl_path_name, CHILDETCONE, i, NULL);). After signaling, the parent waits for all the children to complete their tasks.

I quite understand the handler function, but I am not clear on these points:

  1. Do I need to insert my execl inside the handler function?

  2. I don't understand the significance of this last loop from the answer to the previous question:

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

This is my code:

#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);
}
John Bollinger
  • 160,171
  • 8
  • 81
  • 157
Gianni Spear
  • 7,033
  • 22
  • 82
  • 131
  • 1
    There is a race condition in the code because the parent process can send the SIGUSR1 signal before the child processes have set up the signal handler. This could result in none, some, or all of the child processes being terminated by the signal instead of catching it. – Ian Abbott Aug 06 '18 at 12:47
  • Thanks @IanAbbott, Do I need to use a sleep to give time? – Gianni Spear Aug 06 '18 at 12:54
  • A sleep will probably work, but it is not guaranteed to be sufficient. – Ian Abbott Aug 06 '18 at 13:04
  • 1
    you could simply call `signal()` before the `fork()` loop the handler will be inherited. And to answer the original question: call `execl()` after `pause()`, not from within the signal handler – Ingo Leonhardt Aug 06 '18 at 13:07
  • @IngoLeonhardt Is there still a race condition between signal delivery and and `pause()`? It would probably need the signal to be blocked with `sigprocmask` before the `fork()` and the child replacing the `pause()` call with `sigsuspend()`. I haven't really digged into the details though. – Ian Abbott Aug 06 '18 at 14:12

1 Answers1

1

Do I need to insert my execl inside the handler function?

No. pause() will return only after the process in which it is called catches a signal that causes a signal-handling function to run. The execl call can therefore go just after the pause call. I think that would be clearer in your case.

Note, too, that POSIX standardizes a list of "async-signal-safe" functions that are safe for a signal handler to call, and that it is unsafe for a handler to call others. execl is on the list, but printf and other stream I/O functions are not. Signal handlers should not call printf. Your particular signal handler does not need to do anything at all.

Additionally, consider using sigsuspend() in place of pause(), as the former will give you more control over which signals cause your trains to start.

I don't understand the significance of this last loop from the answer to the previous question:

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

The wait() function instructs the calling process to block until one of its children terminates. The loop makes as many wait() calls as there are children, so that, in the absence of errors, the main program does not proceed until all its children have terminated.

It looks like you may have tried to achieve something similar by calling sleep() in the loop with the kill call, but that strategy is plain wrong. In the first place, waiting after each kill means that the children's execl calls will be spaced out by at least the sleep time, which is not what I understood you to want. In the second place, you cannot know in advance how long it will take the children to finish, so the one second you allow may not be enough under some circumstances. In the third place, since you seem to expect that the children will run very quickly, one second is probably much more than you need most of the time.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157