None of the solutions so far offer an approach without using SIGCHLD as the question requests. Here is an implementation of an alternative approach using poll as outlined in this answer (which also explains why you should avoid using SIGCHLD in situations like this):
Make sure you have a pipe to/from each child process you create. It can be either their stdin/stdout/stderr or just an extra dummy fd. When the child process terminates, its end of the pipe will be closed, and your main event loop will detect the activity on that file descriptor. From the fact that it closed, you recognize that the child process died, and call waitpid to reap the zombie.
(Note: I omitted some best practices like error-checking and cleaning up file descriptors for brevity)
/**
* Specifies the maximum number of clients to keep track of.
*/
#define MAX_CLIENT_COUNT 1000
/**
* Tracks clients by storing their process IDs and pipe file descriptors.
*/
struct process_table {
pid_t clientpids[MAX_CLIENT_COUNT];
struct pollfd clientfds[MAX_CLIENT_COUNT];
} PT;
/**
* Initializes the process table. -1 means the entry in the table is available.
*/
void initialize_table() {
for (int i = 0; i < MAX_CLIENT_COUNT; i++) {
PT.clientfds[i].fd = -1;
}
}
/**
* Returns the index of the next available entry in the process table.
*/
int get_next_available_entry() {
for (int i = 0; i < MAX_CLIENT_COUNT; i++) {
if (PT.clientfds[i].fd == -1) {
return i;
}
}
return -1;
}
/**
* Adds information about a new client to the process table.
*/
void add_process_to_table(int i, pid_t pid, int fd) {
PT.clientpids[i] = pid;
PT.clientfds[i].fd = fd;
}
/**
* Removes information about a client from the process table.
*/
void remove_process_from_table(int i) {
PT.clientfds[i].fd = -1;
}
/**
* Cleans up any dead child processes from the process table.
*/
void reap_zombie_processes() {
int p = poll(PT.clientfds, MAX_CLIENT_COUNT, 0);
if (p > 0) {
for (int i = 0; i < MAX_CLIENT_COUNT; i++) {
/* Has the pipe closed? */
if ((PT.clientfds[i].revents & POLLHUP) != 0) {
// printf("[%d] done\n", PT.clientpids[i]);
waitpid(PT.clientpids[i], NULL, 0);
remove_process_from_table(i);
}
}
}
}
/**
* Simulates waiting for a new client to connect.
*/
void accept() {
sleep((rand() % 4) + 1);
}
/**
* Simulates useful work being done by the child process, then exiting.
*/
void childfunction() {
sleep((rand() % 10) + 1);
exit(0);
}
/**
* Main program
*/
int main() {
/* Initialize the process table */
initialize_table();
while (1) {
accept();
/* Create the pipe */
int p[2];
pipe(p);
/* Fork off a child process. */
pid_t cpid = fork();
if (cpid == 0) {
/* Child process */
close(p[0]);
childfunction();
}
else {
/* Parent process */
close(p[1]);
int i = get_next_available_entry();
add_process_to_table(i, cpid, p[0]);
// printf("[%d] started\n", cpid);
reap_zombie_processes();
}
}
return 0;
}
And here is some sample output from running the program with the printf
statements uncommented:
[31066] started
[31067] started
[31068] started
[31069] started
[31066] done
[31070] started
[31067] done
[31068] done
[31071] started
[31069] done
[31072] started
[31070] done
[31073] started
[31074] started
[31072] done
[31075] started
[31071] done
[31074] done
[31081] started
[31075] done