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
$ _