I have a C problem where one process starts off other processes at runtime, and this process has to do inter-process communication with the other processes. Now i know the basics of fork() and execl() but other than that my knowledge regarding processes is quiet rudimentary(especially how to start a process at runtime), so any help would be much appreciated.
-
2A [mcve] would be nice. The simplest form of communication between one process to another started by itself is passing info by command line arguments. Beside of this, there are many ways of IPC including a common file which sender opens for write, receiver for read. – Scheff's Cat Sep 21 '18 at 08:11
-
Could you elaborate the procedure as to how the execlp() works here? I aim to replace the child's address space with my own compiled executable but haven't been able to do it. I'm attempting something like this right now, correct me where i'm wrong. execlp("C:\C code\sampleSolution\bin\Release", "sampleSolution", NULL); – Kamran Ahmed Sep 22 '18 at 12:28
-
I extended my answer below and elaborated a bit on this. You seem to work on Windows. (In Linux and cygwin, \ is not a directory separator.) Please, don't forget that you have to duplicate \s if used in a C string literal. Additionally, it has to include the file name of executable, and then the same again as described at the bottom of my answer. :-) So, it should be `execlp("C:\\C code\\sampleSolution\\bin\\Release\\sampleSolution", "C:\\C code\\sampleSolution\\bin\\Release\\sampleSolution", NULL);`. – Scheff's Cat Sep 22 '18 at 15:32
1 Answers
The simplest form of communication between one process to another started by itself is passing info by command line arguments. Beside of this, there are many ways of IPC including a common file which sender opens for write, receiver for read.
This is what I already wrote in my comment. I couldn't resist to make a small demo.
testSimpleIPC.c:
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
/* This is done in one program which is started
* - without command line arguments to behave as parent
* - with command line argument to behave as child.
*/
int mainParent(void);
int mainChild(int argc, char **argv);
int main(int argc, char **argv)
{
if (argc == 1) { /* becomes parent */
return mainParent();
} else {
return mainChild(argc, argv);
}
}
int mainParent(void)
{
const char *ipcFileName = "testSimpleIPC.txt";
/* open file for IPC */
FILE *fIn = fopen(ipcFileName, "a+");
if (!fIn) {
perror("Failed to fopen() for reading");
return -1;
}
/* fork and start child */
int pid;
switch (pid = fork()) {
case -1: /* failed */
perror("Failed to fork()");
return -1;
case 0: /* returned in child process */
execlp("./testSimpleIPC",
"./testSimpleIPC", ipcFileName, NULL);
/* If this point is reached execlp failed! */
perror("Failed to execlp()");
return -1;
default:
printf("testSimpleIPC spawned child with PID %d\n", pid);
}
/* read messages from child */
char buffer[80];
for (;;) {
if (fgets(buffer, sizeof buffer, fIn) != NULL) {
/* clip line-ending from buffer end */
for (size_t len = strlen(buffer); len--;) {
if (isspace(buffer[len])) buffer[len] = '\0';
else break;
}
/* report */
printf("Parent received :'%s'\n", buffer);
/* bail out in case */
if (strcmp(buffer, "quit") == 0) break;
}
}
fclose(fIn);
/* done */
return 0;
}
int mainChild(int argc, char **argv)
{
assert(argc == 2);
const char *const ipcFileName = argv[1];
/* write messages to parent */
FILE *fOut = fopen(ipcFileName, "a");
if (!fOut) {
perror("Failed to fopen() for writing");
return -1;
}
for (int i = 1; i < 10; ++i) {
printf("Sending 'message %d'...\n", i);
if (fprintf(fOut, "message %d\n", i) < 0
|| fflush(fOut)) {
perror("Failed to fprintf()");
return -1;
}
}
if (fprintf(fOut, "quit\n") < 0 || fclose(fOut)) {
perror("Failed to fprintf()");
return -1;
}
/* done */
return 0;
}
Compiled and tested in cygwin64 on Windows 10:
$ gcc -std=c11 -o testSimpleIPC testSimpleIPC.c
$ ./testSimpleIPC
testSimpleIPC spawned child with PID 27320
Sending 'message 1'...
Sending 'message 2'...
Parent received :'message 1'
Sending 'message 3'...
Parent received :'message 2'
Sending 'message 4'...
Parent received :'message 3'
Sending 'message 5'...
Parent received :'message 4'
Sending 'message 6'...
Parent received :'message 5'
Sending 'message 7'...
Parent received :'message 6'
Sending 'message 8'...
Parent received :'message 7'
Sending 'message 9'...
Parent received :'message 8'
Parent received :'message 9'
Parent received :'quit'
$
Notes:
At first, I forgot to pass the file path of executable as first command line argument in
execlp()
. Thus, the child was started with only one argument – recognizing itself as parent. Desperated pressing CtrlC didn't help – I had to kill myxterm
(before it killed my system). So, don't forget:argv[0]
is by convention the file path of executable itself but you explicitly have to provide it as argument inexeclp()
.The last addition was the
fflush()
inmainChild()
. Before I did this, the child wrote everything before the parent even started to receive. Although, the reason was obvious for me I found this worth to be mentioned.
A little bit more details about execlp()
:
I used the execlp(3) - Linux man page to recall the details.
The signature of execlp()
is
int execlp(const char *file, const char *arg, ...);
Parameters:
file
provides the filename associated with the file being executed.arg
is the first argument passed to the executable....
may be an arbitrary number of additional arguments passed to the executable.
Please, note that the last passed argument must be NULL
to terminate the list. (Otherwise, the call of execlp()
may cause Undefined Behavior).
A nasty pitfall is the first argument (in execlp()
the 2nd, named arg
).
If using argc
and argv
in int main(int argc, char **argv)
, we are used that argc
is always at least 1
and argv[0]
provides the file path with which the executable itself has been called.
The bad news: This is a convention (which is simply everywhere considered). So, there is no automatism built in somewhere – the caller of execlp()
has to consider this:
repeating the file name of executable as first argument, before providing 0 ... n additional arguments, and not to forget the final NULL
.
In the above example:
execlp("./testSimpleIPC",
"./testSimpleIPC", ipcFileName, NULL);
Recalling my 1. Note (above), when I forgot the first argument "./testSimpleIPC"
, the contents of ipcFileName
became the first and was passed to the child process as argv[0]
. As the code does not use argv[0]
, this was unnoticed by me. However, the fact that the argv[1]
was missing caused that the child process identified itself as parent again. So, I got an avalanche of started processes which was hard to stop.
Actually, I didn't forget the assert(argc == 2);
which makes a check for the expected value of argc
. However, this is active in debug code only and didn't rescue me as I compiled without -g
, so that the assert
became inactive.
Just today I found 1251 processes (left from my recent test) which ate 40 % CPU load until I managed to stop them with
for I in $( ps | grep testSimpleIPC | awk '{ print $1 }' ); do kill $I ; done

- 19,528
- 6
- 28
- 56