0

I have a problem with my code,

I want all the children stop when the program start. and after that I want just the child with the index of i to continue executing and others to be stopped .

I want to execute them in this order p0 ,p1,p2,p3,p4,p0,p1....
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

#define N 5

void handler(int i)
{
    if (i == SIGCONT)
    {
        printf("signal cont\n");
    }
}
int main()
{
    int pid[N];
    for (int i = 0; i < N; i++)
    {
        if ((pid[i] = fork()) == 0)
        {
            /* code */
            while (1)
            {
                printf("ici fils %d\n", i);
                usleep(50000);
            }
        }
        else
        {
            kill(pid[i], SIGSTOP);
            // kill(pid[i], SIGSTOP);
            if (i == N - 1)
            {
                kill(pid[i], SIGCONT);
                sleep(2);
                kill(pid[i], SIGSTOP);
                kill(pid[0], SIGCONT);
            }
            else
            {

                kill(pid[i], SIGCONT);
                sleep(2);
                kill(pid[i], SIGSTOP);
                kill(pid[i + 1], SIGCONT);
            }
            // kill(pid[i], SIGKILL);
            waitpid(pid[i], NULL, 0);
        }
    signal(SIGCONT, &handler);
    }
}

  • 1
    If you want the default handling of `SIGSTOP` and `SIGCONT` (that is, stopping and continuing the receiving process, respectively) then do not override that by installing handlers for those. – John Bollinger Jun 15 '22 at 13:49
  • but I want to print `signal cont ` if the `SIGCONT` is captured – Hicham Zakroum Jun 15 '22 at 13:56
  • Actually, `SIGCONT` is special: it will continue the receiving process (if it is stopped) even if you register a handler for it. Not that I think registering a handler is good form. The same is not true of `SIGSTOP` or any other signal whose disposition you can change. Note also that if you register a handler for `SIGCONT` then it will fire whenever the process receives that signal, even if the process isn't stopped. – John Bollinger Jun 15 '22 at 14:07
  • Note, too, that involving `pause()` as a separate mechanism for suspending your processes pending a signal adds a layer of complication that you would probably be better off without. Not to mention that it's racy. – John Bollinger Jun 15 '22 at 14:11
  • 1
    Aside: [`signal-safety(7)`](https://man7.org/linux/man-pages/man7/signal-safety.7.html) for a list of *async-signal-safe* functions that are safe to call from a signal handler. `printf` is not included in that list. – Oka Jun 15 '22 at 15:03
  • 1
    Please [use `sigaction`](https://stackoverflow.com/q/231912/132382) rather than `signal`, and please [avoid `printf`](https://stackoverflow.com/q/16891019/132382) (as Oka mentioned) inside handlers. – pilcrow Jun 15 '22 at 15:05

2 Answers2

3

There are several issues with your code, among them:

  1. Any processes to be stopped via SIGSTOP must not have a handler registered for that signal. Registering a handler causes the handler's behavior to replace the default behavior of stopping the process.

  2. It's usually a bad idea to register a handler for SIGCONT. Doing so will not prevent a SIGCONT from continuing the process, which is a special characteristic of SIGCONT that can be surprising, but also the handler will fire whenever a SIGCONT is delivered, even if the process was not stopped, which is often a different kind of surprise.

  3. You register your signal handlers only in the parent, after the first fork. The subsequently forked children will inherit those, but the first one will not. Among other things, this will prevent the first child's pause() from being unblocked by the signals the parent sends to it. You can make each child register any needed handlers for itself, or you can register them in the parent, before the first fork.

  4. There is a race between each child's pause() and the parent's first kill() targeting that child. It is possible for the child to receive the SIGCONT before it calls pause(), in which case it will wait for the next signal. You can prevent that by blocking SIGCONT in the parent before forking, and using sigsuspend() in the child, with an appropriate mask, instead of the initial pause(). In that case, you probably want to unblock SIGCONT after returning from that initial sigsuspend().

  5. The parent attempts to send signals to processes that it has not forked yet (kill(pid[i + 1], SIGCONT);).

It's not clear what the full behavior you are trying to achieve is, but you may want to fork all the children first, and only then start sending signals.

Update

With respect to the update to the question,

  1. You apparently want to cycle repeatedly through the child processes, but your code runs through them only once. This is a good reason to implement what I already suggested above: fork all the children first, then, separately, do all the signalling.
John Bollinger
  • 160,171
  • 8
  • 81
  • 157
2

In the child processes, instead of using pause(2), use raise(3) to signal the calling process to stop with SIGSTOP. There is no real need to register signal handlers.

In the parent process, after creating a child process, wait for it to stop (or terminate) by using waitpid(2) with the WUNTRACED flag set. The WIFSTOPPED(...) macro can be used to specifically determine the status of the child. The WCONTINUE flag can be used to wait for a child process to continue, and like before there is the WIFCONTINUED(...) macro.

Here is a cursory example, with no meaningful error handling. Note that concurrent sleeps, while simple, are not technically a consistent way to schedule things. The output of this program may differ slightly between executions.

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

#define CHILDCOUNT 5

sig_atomic_t looping = 1;

void handler(int sig) {
    (void) sig;
    looping = 0;
}

pid_t create_child(void) {
    pid_t pid = fork();

    if (!pid) {
        /* child */
        raise(SIGSTOP);
        pid_t self = getpid();
        printf("SIGCONT in %d\n", self);

        while (1) {
            printf("RUNNING in %d\n", self);
            sleep(1);
        }

        /* bug net */
        exit(EXIT_SUCCESS);
    }

    return pid;
}

void killwait(pid_t pid, int sig) {
    kill(pid, sig);

    int status;
    waitpid(pid, &status, WUNTRACED | WCONTINUED);

    if (WIFSTOPPED(status))
        printf("P: C(%d) STOPPED!\n", pid);
    if (WIFCONTINUED(status))
        printf("P: C(%d) CONTINUED!\n", pid);
    if (WIFSIGNALED(status) && SIGKILL == WTERMSIG(status))
        printf("P: C(%d) SUCCESSFULLY KILLED!\n", pid);
}

int main(void) {
    pid_t pids[CHILDCOUNT];

    /* tentative: catch this in all processes so the parent may reap manually */
    signal(SIGINT, handler);

    for (size_t i = 0; i < CHILDCOUNT; i++) {
        pid_t current = pids[i] = create_child();

        printf("Parent now has child (%d) [#%zu].\n", current, i);
        killwait(current, 0);
    }

    for (size_t i = 0; looping; i = (i + 1) % CHILDCOUNT)  {
        pid_t current = pids[i];

        printf("P: C(%d) STARTING [#%zu].\n", current, i);

        killwait(current, SIGCONT);
        sleep(2);
        killwait(current, SIGSTOP);
    }

    for (size_t i = 0; i < CHILDCOUNT; i++)
        killwait(pids[i], SIGKILL);
}
Oka
  • 23,367
  • 6
  • 42
  • 53
  • I considered recommending `raise()` in place of `pause()`, but then I realized that that does not resolve the race with the parent sending a `SIGCONT`. – John Bollinger Jun 15 '22 at 15:08
  • @Oka I don't want to kill the child ,I want to execute p0 ,p1,p2,p3,p4,p0,p1.... – Hicham Zakroum Jun 15 '22 at 15:19
  • @JohnBollinger Waiting on the child to stop itself prevents the race condition, no? (Assuming nothing *else* occurs, since this is obviously non-exhaustive.) – Oka Jun 15 '22 at 17:43
  • Yes, it should. The OP should be mindful that the two must be used together. – John Bollinger Jun 15 '22 at 19:13