Reputation: 354
I would like to ask you guys some help with C programming. Basically Im having issues with fork()
system call.
Here's my question:
We have a Manager Process which has to create POP_SIZE
Student processes. Manager Process and Student Processes itself cannot do anything else until all Student Processes have been created.
Every Student Process is identified by:
1) its identification number (6-digit integer)
2) grade obtained in specific exam (integer)
Here's the code I managed to write:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#define POP_SIZE 10
int main(int argc, char *argv[]){
pid_t firstFork;
int *status;
int numStudents = 0;
pid_t managerChild, managerParent;
pid_t students[POP_SIZE];
int studentStatus[POP_SIZE];
switch(firstFork = fork()){
case -1:
perror("Something wrong with fork()\n");
break;
case 0:
managerChild = getpid();
printf("Manager Child Process %d started\n", managerChild);
printf("I have to create %d Student Processes\n", POP_SIZE);
for(int i = 0; i < POP_SIZE; i++){
switch(students[i] = fork()){
case -1:
perror("Something wrong with FORK in Manager Child Process\n");
break;
case 0:
printf("Created first Student Process PID: %d\n", getpid());
numStudents++;
break;
default:
printf("Haven't created all Student Processes\n");
waitpid(managerChild, status, WUNTRACED | WNOHANG);
printf("%d Student Processes succesfully created\n", numStudents);
break;
}
}
break;
default:
for(int i = 0; i < POP_SIZE; i++)
wait(NULL);
}
}
I'd need some help in understanding where to put wait(*status)
or waitpid(pid, *status, __options)
functions in my code in order to achieve my requirements specified above?
Moreover, how can I assign and keep storing of variables for every single process?
Thank you very much
Upvotes: 0
Views: 2684
Reputation: 9336
I think there are some mistakes in your code. The output I get is something like:
5 Student Processes succesfully created
Haven't created all Student Processes
Haven't created all Student Processes
3 Student Processes succesfully created
4 Student Processes succesfully created
Created first Student Process PID: 11436
Created first Student Process PID: 11438
Created first Student Process PID: 11437
Haven't created all Student Processes
4 Student Processes succesfully created
Haven't created all Student Processes
3 Student Processes succesfully created
Created first Student Process PID: 11439
Haven't created all Student Processes
3 Student Processes succesfully created
Created first Student Process PID: 11440
Haven't created all Student Processes
3 Student Processes succesfully created
Created first Student Process PID: 11441
Haven't created all Student Processes
2 Student Processes succesfully created
Created first Student Process PID: 11442
Created first Student Process PID: 11443
You see there are too much children executing, so this should make you suspicious (paricularly note that sometimes the number for the student processes seems decreasing from print to print). The parent will continue executing the for
loop. However the child continues executing from the point where the fork
is called and being it inside a loop, it will fork too creating another child and so on and so on. To avoid that you need a break
from the for
loop for the children processes.
You can try something like the following. I added a variable jj
that if <0
means it is a child process executing. Before next loop iteration the variable is checked and if <0
it breaks from the for
loop.
It is not the most elegant solution but seems ok.
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#define POP_SIZE 10
int main(int argc, char *argv[]){
pid_t firstFork;
int *status;
int numStudents = 0;
pid_t managerChild, managerParent;
pid_t students[POP_SIZE];
int studentStatus[POP_SIZE];
switch(firstFork = fork()){
case -1:
printf("Something wrong with fork()\n");
break;
case 0:
managerChild = getpid();
printf("Manager Child Process %d started\n", managerChild);
printf("I have to create %d Student Processes\n", POP_SIZE);
int jj = 0;
for(int i = 0; i < POP_SIZE; i++){
switch(students[i] = fork()){
case -1:
printf("Something wrong with FORK in Manager Child Process\n");
jj = -1;
break;
case 0:
printf("Created first Student Process PID: %d\n", getpid());
numStudents++;
jj = -1;
break;
default:
printf("Haven't created all Student Processes\n");
waitpid(managerChild, status, WUNTRACED | WNOHANG);
printf("%d Student Processes succesfully created\n", numStudents);
break;
}
if (jj<0) break;
}
break;
default:
for(int i = 0; i < POP_SIZE; i++)
wait(NULL);
}
}
Upvotes: 0
Reputation: 39346
Since you will be creating many child processes, it is best to start by creating a function that creates the child process, and has it execute a function specified by the caller. Let's assume both the ID number and grade are int
s. Then,
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
/* Run func(id, grade) in a child process.
Returns the child process PID if success,
or -1 with errno set in case an error occurs.
*/
pid_t run_child(int id, int grade,
int (*func)(int id, int grade))
{
pid_t p;
p = fork();
if (p == -1) {
/* fork() failed; it set errno to indicate the error. */
return -1;
} else
if (!p) {
/* Run child process function. When it returns,
have the child exit with that exit status. */
exit(func(id, grade));
} else {
/* Parent process. p is positive. */
return p;
}
}
Note that the third parameter is a function pointer. We specify it using the function name. That function must take two int
parameters (the ID and the grade, respectively), and return an int
. For example:
/* Each child process runs this function.
*/
int child_process(int id, int grade)
{
printf("Child: id = %d, grade = %d, PID = %d.\n", id, grade, (int)getpid());
return EXIT_SUCCESS;
}
We can create a child process that runs that function using child_pid = run_child(123456, 5, child_process);
. Note how the name of the function can be used to specify a function pointer. The standard C qsort()
function uses the exact same mechanism to allow one to quicksort anything; the caller just needs to specify a function that can compare two elements in the array to be sorted.
We will be creating several children, and reaping them at once. That means it makes sense to write a function that reaps all child processes, essentially blocking until they all exit. We are likely interested in the exit statuses of at least some of them, so let's pass the interesting child processes PIDs, ints to save the status to, and the number of processes in those arrays, as parameters:
/* Reap all child processes.
If child_count > 0, child processes with PID in child_pid[]
will have child_pid[] negated when reaped, with exit status saved
in child_status.
The function returns the number of child processes reaped.
*/
size_t reap_children(pid_t *child_pid, int *child_status, size_t child_count)
{
size_t reaped = 0;
size_t i;
int status;
pid_t p;
while (1) {
/* Reap a child process, if any. */
p = wait(&status);
if (p == -1) {
/* errno == EINTR is not an error; it occurs when a
signal is delivered to a hander installed without
SA_RESTART flag. This will not occur in this program,
but it is good practice to handle that case gracefully. */
if (errno == EINTR)
continue;
/* errno set by wait(). */
return reaped;
}
/* Another child process was reaped. */
reaped++;
/* If the reaped child was one of the interesting ones,
negate its pid and save the exit status. */
for (i = 0; i < child_count; i++) {
if (child_pid[i] == p) {
child_pid[i] = -p;
child_status[i] = status;
break;
}
}
}
}
Note that p = wait(&status)
reaps a child process. This means that if one or more child processes have already exited, it picks one of them, and returns its PID, with exit status saved to &status
. If all child processes left are still running, the call will wait until at least one of them exits. If there are no more child processes, it returns -1
with errno
set to ECHILD
.
If signal handlers were used, wait()
can also return -1
with errno
set to EINTR
, if a signal was delivered to a signal handler that was installed without the SA_RESTART
flag with sigaction()
. Many programmers forgo this check (because "it'll never happen"), but I do like to include that check because it is easy, and makes sure adding signal handling to my code won't bite me in the butt later on. I very often do, too. (Add signal handling, I mean.)
The reason we negate the pids when the respective child process is reaped, is simple: it allows us to easily detect which child processes were reaped. (POSIX says all process IDs are positive, and pid_t
is a signed type. Negating a PID is a commonly used technique, too; just see e.g. waitpid()
.)
If we wanted to reap a specific child process, we'd use waitpid()
. For example,
pid_t child, p; /* wait for 'child'. */
int status;
do {
p = waitpid(child, &status, 0);
if (p == -1) {
if (errno == EINTR)
continue;
break;
}
} while (p != child);
if (p == child) {
/* Reaped 'child', status in 'status'. */
} else {
/* Error: failed to reap 'child'. See 'strerror(errno)'. */
}
Do note that in POSIX/Unix terminology 'child process' refers to processes created by this process only; not "grandchildren", processes created by child processes.
I prefer to write my processes to take in parameters from the command line. If no parameters are specified, or -h
or --help
is specified, a short help ("usage") is displayed; this is extremely common in POSIX and Unix command-line tools, and therefore very intuitive.
The following main()
takes one or more ID:grade
as command-line parameters. For each one, it creates a child process, and has it run the child_process()
function with the specified ID and grade. The main program will then reap them all, and describe the exit status of each child process.
int main(int argc, char *argv[])
{
pid_t child_pid[argc];
int child_status[argc];
int count, i, n, arg, id, grade, status;
char dummy;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s ID:GRADE [ ID:GRADE ]*\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
status = EXIT_SUCCESS;
count = 0;
for (arg = 1; arg < argc; arg++) {
if (sscanf(argv[arg], "%d:%d %c", &id, &grade, &dummy) == 2) {
child_pid[count] = run_child(id, grade, child_process);
if (child_pid[count] == -1) {
fprintf(stderr, "Cannot fork a child process: %s.\n", strerror(errno));
status = EXIT_FAILURE;
} else
count++;
} else {
fprintf(stderr, "%s: Not a valid ID:GRADE specification.\n", argv[arg]);
status = EXIT_FAILURE;
}
}
if (count < 0) {
fprintf(stderr, "No running child processes.\n");
return EXIT_FAILURE;
}
n = reap_children(child_pid, child_status, count);
printf("Reaped %d child processes.\n", n);
for (i = 0; i < count; i++) {
if (child_pid[i] < 0) {
printf("Child process %d (%d of %d)", (int)(-child_pid[i]), i + 1, count);
if (WIFEXITED(child_status[i])) {
if (WEXITSTATUS(child_status[i]) == EXIT_SUCCESS)
printf(" exited with success (EXIT_SUCCESS), %d.\n", EXIT_SUCCESS);
else
if (WEXITSTATUS(child_status[i]) == EXIT_FAILURE)
printf(" exited with failure (EXIT_FAILURE), %d.\n", EXIT_FAILURE);
else
printf(" exited with status %d.\n", WEXITSTATUS(child_status[i]));
} else
if (WIFSIGNALED(child_status[i])) {
printf(" died from signal %d.\n", WTERMSIG(child_status[i]));
} else {
printf(" died from unknown causes.\n");
}
} else {
printf("Child process %d (%d of %d) was lost!\n", (int)child_pid[i], i + 1, count);
}
}
return status;
}
If you save the above as example.c, you can compile it to example using e.g.
gcc -Wall -O2 example.c -o example
If you then run say
./example 100001:1 100002:5 100003:3 21532:4
the output will be something like
Child: id = 100002, grade = 5, PID = 1260.
Child: id = 100001, grade = 1, PID = 1259.
Child: id = 100003, grade = 3, PID = 1261.
Child: id = 21532, grade = 4, PID = 1262.
Reaped 4 child processes.
Child process 1259 (1 of 4) exited with success (EXIT_SUCCESS), 0.
Child process 1260 (2 of 4) exited with success (EXIT_SUCCESS), 0.
Child process 1261 (3 of 4) exited with success (EXIT_SUCCESS), 0.
Child process 1262 (4 of 4) exited with success (EXIT_SUCCESS), 0.
Note that the initial Child:
lines can be in any order, because the child processes run essentially in parallel. Each child process runs as soon as it is started, so this example is not a copy-and-paste answer to OP's requirements.
If you want to experiment with complex process hierarchies, I recommend using Graphviz to visualize them. For example, dot-kids.c:
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
static void reap_all(void)
{
pid_t p;
int status;
while (1) {
p = wait(&status);
if (p == -1) {
if (errno == EINTR)
continue;
if (errno == ECHILD)
return;
fprintf(stderr, "Process %d: reap_all(): %s.\n", (int)getpid(), strerror(errno));
return;
}
printf(" \"%d\" -> \"%d\" [ color=\"#ff0000\" ];\n", (int)p, (int)getpid());
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) == EXIT_SUCCESS)
printf(" \"%d\" [ label=\"%d\" ];\n", (int)p, (int)p);
else
printf(" \"%d\" [ label=\"%d (exit %d)\" ];\n", (int)p, (int)p, WEXITSTATUS(status));
} else
if (WIFSIGNALED(status))
printf(" \"%d\" [ label=\"%d (signal %d)\" ];\n", (int)p, (int)p, WTERMSIG(status));
else
printf(" \"%d\" [ label=\"%d (lost)\" ];\n", (int)p, (int)p);
fflush(stdout);
}
}
static pid_t run_child(int (*child)(int depth, int width), int depth, int width)
{
pid_t p;
fflush(stdout);
fflush(stderr);
p = fork();
if (p == -1) {
fprintf(stderr, "Process %d: Cannot fork: %s.\n", (int)getpid(), strerror(errno));
return -1;
} else
if (!p) {
exit(child(depth, width));
} else {
printf(" \"%d\" -> \"%d\" [ color=\"#0000ff\" ];\n", (int)getpid(), (int)p);
fflush(stdout);
return p;
}
}
int child(int depth, int width)
{
if (depth > 0) {
while (width > 0)
run_child(child, depth - 1, width--);
reap_all();
}
return EXIT_SUCCESS;
}
int main(int argc, char *argv[])
{
int depth, width, i;
char dummy;
if (argc != 3 || !strcmp(argv[1], "-h") || !strcmp(argv[2], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s depth width | dot -Tx11\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}
if (sscanf(argv[1], " %d %c", &depth, &dummy) != 1 || depth < 0) {
fprintf(stderr, "%s: Invalid depth.\n", argv[1]);
return EXIT_FAILURE;
}
if (sscanf(argv[2], " %d %c", &width, &dummy) != 1 || width < 1) {
fprintf(stderr, "%s: Invalid width.\n", argv[2]);
return EXIT_FAILURE;
}
printf("digraph {\n");
printf(" \"%d\" [ shape=\"box\", label=\"%d\" ];\n", (int)getpid(), (int)getpid());
fflush(stdout);
for (i = 0; i < width; i++)
run_child(child, depth, width - 1);
reap_all();
printf("}\n");
return EXIT_SUCCESS;
}
Compile it using e.g.
gcc -Wall -O2 dot-kids.c -o dot-kids
and run using e.g.
./dot-kids 1 3 | dot -Tx11
to see a process graph similar to
where the numbers are process IDs, blue arrows show which process created which, and red arrows show which process reaped which.
Upvotes: 3