0

Hello I Have problem with getting signal in my second program. I have code like this:

Program1:

int main() {
    int id = fork();
    
    if (id == 0) {
        execl("program2", "program2", NULL);
        kill(id, SIGUSR1);
    }
    return 0;
}

Program 2:

void signal_handler1(int sig) {
    printf("test\n");
}

int main(int argc, char* argv[]) {
    signal(SIGUSR1, signal_handler1);
    printf("Hello world\n");

    return 0;
}

And why I get

Hello World

Instend of

test
Hello World
Barmar
  • 741,623
  • 53
  • 500
  • 612
Kosky
  • 11
  • 3
  • Nothing runs after `execl()`, since the process image has been replaced with the new program. Did you mean to do `kill(id, SIGUSR1)` in the parent process? – Barmar Nov 07 '22 at 20:07
  • Once you move the `kill` outside the `if`, you may run into a race condition. `execl()` takes a moment to start the new program. If the signal is sent before the new program is running and has configured the signal behavior, you'll get the default behavior, which according to `signal(7)` man page is to terminate. Alternately, after printing "Hello world" the child exits, so the signal handler will never be called. – Perette Nov 07 '22 at 20:10
  • @Barmar when I use kill(id, SIGUSR1) after condition with execl nothing happens, I even dont got Hello World – Kosky Nov 07 '22 at 20:11
  • 1. `kill` is never executed in program1 if `execl` succeed. 2. program2 terminates after printing and doesn't wait for anything. – dimich Nov 07 '22 at 20:12
  • program 1 should sleep for a bit to allow time for program 2 to set up the signal handler. Program 2 should sleep for a bit after adding the signal handler so it doesn't exit before the signal. – Barmar Nov 07 '22 at 20:14
  • `printf` is not an async-signal-safe function, you cannot *generally* use it in a signal handler (although for this small test it *might* be OK). See the full list of async-signal-safe functions in `man 7 signal-safety`. – n. m. could be an AI Nov 07 '22 at 20:17
  • See [How to avoid using `printf()` in a signal handler?](https://stackoverflow.com/q/16891019/15168) – Jonathan Leffler Nov 08 '22 at 04:15

2 Answers2

0

execl() return only if an error occured, so kill() is never executed in program1 if execl() succeed. It should send signal in parent process. But signal may be sent before program2 setup signal handler, so signal may be ignored. Let it wait some time before sending signal:

#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    pid_t id = fork();
    if (id == -1) {
        perror("fork");
        return 1;
    }

    if (id == 0)
    {   /* Child process */
        /* Execute program from current directory */
        execl("program2", "program2", NULL);
        perror("execl");
    } else
    {   /* Parent process */
        /* Wait while child setup signal handler */
        sleep(1);

        if (kill(id, SIGUSR1) == -1)
        {
            perror("kill");
        }
    }
    return 0;
}

If you want program2 to catch signal, let it wait for signal:

#include <signal.h>
#include <unistd.h>
#include <stdio.h>

static sig_atomic_t sigusr1_received = 0;

static void signal_handler(int)
{
    sigusr1_received = 1;
}

int main(void)
{
    signal(SIGUSR1, signal_handler);

    printf("Hello World!\n");

    while (1)
    {
        /* Check flag first to avoid race condition */
        if (sigusr1_received)
        {
            printf("test\n");
            /* Exit after receiving signal */
            break;
        }
        pause();        /* Wait for any signal */
    }

    return 0;
}
dimich
  • 1,305
  • 1
  • 5
  • 7
0

Look to your parent code:

int main()
{
    int id = fork();
    
    if (id == 0) {
  • you don't test for negative id (indicating some error from fork()) but, as this error is rare, it's not a problem here)
  • you have tested if id is zero (just above), so think twice, are you killing process with process id 0??? Shouldn't this kill be in some other part of the code?
  • execl() only returns if it indeed fails. You should only print some error about why execl() failed and exit() with a return code indicating that.
        execl("program2", "program2", NULL);
        fprintf(stderr, "EXECL: %s\n", strerror(errno));
        exit(EXIT_FAILURE);
        /* kill(id, SIGUSR1); <-- this code will never execute */
    }
    return 0;
}

Exec functions are never expected to return, as the program that executes them is overwritten by the program you are attempting to execute. So, if you call execl(2), don't put anything but error code below it. It's not like calling a subroutine, that returns to the calling program. It never returns (well, as said, only if the function fails).

If you want to send a signal to your child, the kill() call should be after the if sentence (where, indeed, is the parent code to execute) You should check first for -1, as this means that fork() failed.

So a better sample code would be: (I have put, not as you did, a complete example, ready to be built and executed at your site, please read How to create a minimal, reproducible example and follow the guidelines there to post testable code. (you lack header files and many things for this code to be testable out of the box)

program1.c

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

#include "log.h"  /* see log.h file, below */

char *p1;
int   pid;

int main(int argc, char **argv)
{
    int res;
    p1       = argv[0];
    char *p2 = argc > 1 ? argv[1] : p1;
    pid      = getpid();

    LOG(p1, pid, "STARTING\n");
    LOG(p1, pid, "IGNORING SIGUSR1(%d)\n", SIGUSR1);
    signal(SIGUSR1, SIG_IGN);

    int  id  = fork();
    if (id < 0) { /* error */
        ERR(p1, pid, "FORK: %s\n", strerror(errno));
    }

    if (id == 0) { /* child process */
        pid  = getpid(); /* a new pid as we are children */

        if (argc <= 1) {
            ERR(p1, pid, "nothing to execute\n");
        }

        /* shift the parameters to get the next command name
         * and parameter list */
        argv++;
        argc--;

        LOG(p1, pid, "about to EXECV(");
        char *sep  = "";
        for (int i = 0; i < argc; i++) {
            LOG_TAIL("%s\"%s\"", sep, argv[i]);
            sep    = ", ";
        }
        LOG_TAIL(").\n");

        /* we use execv(2) instead of execl(2) because
         * we are using the parameters as derived from
         * our own parameters, so we change (we did
         * above) the local parameters to main to get
         * the parameter list to execv(). */
        execv(p2, argv);
        ERR(p1, pid, "EXEC: %s\n", strerror(errno));
    }
    /* id > 0 */
    LOG(p1, pid, "FORKed a new child (pid=%d)\n", id);

    {   /* randomize, as all children start at the same time
         * calling srandom() with a time based value will not
         * be a good chance, so I selected to read the seed
         * value from /dev/random */
        static const char rand_dev[] = "/dev/random";
        int fd = open(rand_dev, O_RDONLY);
        int val;
        if (fd >= 0) {
            read(fd, &val, sizeof val);
            srandom(val);
            close(fd);
        } else {
            ERR(p1, pid, "%s: %s\n", rand_dev, strerror(errno));
        }
    }

    int secs = random() % 10 + 1; /* 1..10 s. delay */
    LOG(p1, pid, "Waiting for %ds to next step\n", secs);
    sleep(secs); /* wait between 1 and 10s. */

    LOG(p1, pid, "Sending SIGUSR1(%d) to %s[pid=%d]\n",
            SIGUSR1, p2, id);
    res = kill(id, SIGUSR1);
    if (res < 0) {
        LOG(p1, pid, "KILL(%d, SIGUSR1): %s\n", id, strerror(errno));
    }
    LOG(p1, pid, "Waiting for %s[pid=%d] to finalize\n", p2, id);
    int status;
    res = wait(&status); /* wait for child to end */
    if (res < 0) {
        /* error in wait(2) */
        ERR(p1, pid, "WAIT: %s\n",
                strerror(errno));
    } else if (WIFEXITED(status)) {
        /* child called exit(2) */
        LOG(p1, pid, "WAIT: %s[pid=%d] terminated with exit code %d.\n",
                p2, id, WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
        /* child was aborted by a signal */
        LOG(p1, pid, "WAIT: %s[pid=%d] terminated by signal %d%s.\n",
                p2, id, WTERMSIG(status),
                WCOREDUMP(status)
                    ? ", and dumped a core file"
                    : "");
    }
    LOG(p1, pid, "finalizing\n");
    exit(EXIT_SUCCESS);
} /* main */

program2.c

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

#include "log.h"

char *pn;
int pid;

void signal_handler1(int sig) {
    LOG(pn, pid, "RECEIVED signal %d\n", sig);
}

int main(int argc, char* argv[]) {

    pn = argv[0];
    pid = getpid();

    LOG(pn, pid, "STARTING\n");

    LOG(pn, pid, "Installing signal handler for SIGUSR1(%d)\n",
            SIGUSR1);
    signal(SIGUSR1, signal_handler1);

    LOG(pn, pid, "Program params: ");
    char *sep = "";
    for (int i = 1; i < argc; i++) {
        LOG_TAIL("%s[%s]", sep, argv[i]);
        sep = ", ";
    }
    LOG_TAIL("\n");

    if (argc > 1) { /* at least one parameter, so pause */
        LOG(pn, pid, "Waiting to receive a signal\n");
        int res = pause();
        if (res < 0) {
            LOG(pn, pid, "PAUSE: %s\n", strerror(errno));
        }
    }

    /* and print final message */
    LOG(pn, pid, "Hello world, program terminating\n");
    return 0;
}

Both programs need:

log.h

#ifndef _LOG_H
#define _LOG_H

#define F(_prog, _pid, _fmt) "%s:%s:%d:pid=\033[1;%dm%d\033[m:%s: "\
    _fmt, _prog, __FILE__, __LINE__, (_pid % 7 + 31), (_pid), __func__

#define LOG_TAIL(_fmt, ...) \
        printf(_fmt, ##__VA_ARGS__)

#define LOG(_prog, _pid, _fmt, ...)     \
        printf(F(_prog, _pid, " "_fmt), \
                ##__VA_ARGS__)

#define ERR(_prog, _pid, _fmt, ...) do {      \
        printf(F(_prog, _pid, "ERROR: "_fmt), \
                ##__VA_ARGS__);               \
        exit(EXIT_FAILURE);                   \
    } while (0)

#endif /* _LOG_H */

and you can build with:

Makefile

targets = p1 p2
toclean = $(targets)

p1_objs = program1.o
p2_objs = program2.o
toclean += $(p1_objs) $(p2_objs)

all: $(targets)
clean:
    rm -f $(toclean)

p1: $(p1_objs)
    $(CC) $(CFLAGS) $(LDFLAGS) $($@_objs) -o $@

p2: $(p2_objs)
    $(CC) $(CFLAGS) $(LDFLAGS) $($@_objs) -o $@

program1.o program2.o: log.h

for a run as:

$ p1 p1 p2 
p1:program1.c:24:pid=11089:main:  STARTING
p1:program1.c:25:pid=11089:main:  IGNORING SIGUSR1(10)
p1:program1.c:62:pid=11089:main:  FORKed a new child (pid=11090)
p1:program1.c:83:pid=11089:main:  Waiting for 1s to next step
p1:program1.c:45:pid=11090:main:  about to EXECV("p1", "p2").
p1:program1.c:24:pid=11090:main:  STARTING
p1:program1.c:25:pid=11090:main:  IGNORING SIGUSR1(10)
p1:program1.c:62:pid=11090:main:  FORKed a new child (pid=11091)
p1:program1.c:83:pid=11090:main:  Waiting for 6s to next step
p1:program1.c:45:pid=11091:main:  about to EXECV("p2").
p2:program2.c:23:pid=11091:main:  STARTING
p2:program2.c:25:pid=11091:main:  Installing signal handler for SIGUSR1(10)
p2:program2.c:29:pid=11091:main:  Program params: 
p2:program2.c:46:pid=11091:main:  Hello world, program terminating
p1:program1.c:86:pid=11089:main:  Sending SIGUSR1(10) to p1[pid=11090]
p1:program1.c:92:pid=11089:main:  Waiting for p1[pid=11090] to finalize
p1:program1.c:86:pid=11090:main:  Sending SIGUSR1(10) to p2[pid=11091]
p1:program1.c:92:pid=11090:main:  Waiting for p2[pid=11091] to finalize
p1:program1.c:101:pid=11090:main:  WAIT: p2[pid=11091] terminated with exit code 0.
p1:program1.c:111:pid=11090:main:  finalizing
p1:program1.c:101:pid=11089:main:  WAIT: p1[pid=11090] terminated with exit code 0.
p1:program1.c:111:pid=11089:main:  finalizing
$ _
Luis Colorado
  • 10,974
  • 1
  • 16
  • 31