I am trying to design and implement a simulation for a making an exam through POSIX processes and Inter-Process Communication Techniques. The simulation works as follows:
A process named Teacher will manage the whole simulation from the beginning till the end.
Teacher process uses fork/exec system calls to generate/load 10 Student processes.
Each Student process:
a. waits a SIGNAL from the Teacher to start their exam.
b. Once the Student receives a SIGNAL, it will start its exam.
c. The Student process will invoke sleep system API to sleep a random number selected between [10 – 30] seconds, in order to emulate different times for finishing the exam among different processes.
d. Once the sleep is over for that process it will trigger a SIGNAL that it has finished the exam
The Teacher process will keep tracking the order of Student processes that have finished.
At the end of all Student processes, the Teacher process will announce the results of the exam as follows and terminate.
Exam is over -> RESULTS ARE:
Student of ID=4128 Finished First
Student of ID=4122 Finished Second
Student of ID=4126 Finished Third ...
I have made this code in c:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <math.h>
#include <time.h>
#include <string.h>
#include <sys/wait.h>
#include<signal.h>
static int child_recieved_sig = 0;
static int parent_recieved_sig = 0;
static int pids[10];
static int pidIndex = 0;
int random_time() {
// generate random time from 10-30 sec
srand(time(NULL) + getpid());
int rand_time = (rand() % 21) + 10; //form [0-20]+10
return rand_time;
}
//signal sent from the parent to the child to start the exam
void start_signal(int sig) {
if (sig == SIGUSR1) {
child_recieved_sig = 1;
}
}
//signal sent from the child to the parent indicating that the exam is over
void finish_signal(int sig, siginfo_t* info, void* context) {
if (sig == SIGUSR2) {
parent_recieved_sig++;
pids[pidIndex] = info->si_pid;
pidIndex++;
}
}
int main() {
struct sigaction ss = { 0 }; // fs = start signal
ss.sa_sigaction = start_signal;
ss.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1, &ss, NULL);
struct sigaction fs = { 0 }; // fs = finish signal
fs.sa_sigaction = finish_signal;
fs.sa_flags = SA_SIGINFO;
sigaction(SIGUSR2, &fs, NULL);
for (int i = 0;i < 10;i++) { //generate 10 children
pid_t pid = fork();
if (pid < 0)
perror("Forking has Failed!");
else if (pid == 0) { //child process
while (child_recieved_sig != 1); // wait for signal
printf("My ID is %d and I started my exam\n", getpid());
int time_interval = random_time();
printf("SleepTime: %d<-----------------------\n", time_interval);
sleep(time_interval);
kill(getppid(), SIGUSR2);
printf("Student %d finished exam\n", getpid());
exit(0);
} else {
kill(pid, SIGUSR1);
}
}
while (parent_recieved_sig < 10) usleep(50000);
printf("parent_received_sig: %d\n", parent_recieved_sig);
printf("The Exam is Over ==> Results are:\n");
printf("Student ID=%d Finished First\n", pids[0]);
printf("Student ID=%d Finished Second\n", pids[1]);
printf("Student ID=%d Finished Third\n", pids[2]);
printf("Student ID=%d Finished Forth\n", pids[3]);
printf("Student ID=%d Finished Fifth\n", pids[4]);
printf("Student ID=%d Finished Sixth\n", pids[5]);
printf("Student ID=%d Finished Seventh\n", pids[6]);
printf("Student ID=%d Finished Eighth\n", pids[7]);
printf("Student ID=%d Finished Nineth\n", pids[8]);
printf("Student ID=%d Finished Tenth\n", pids[9]);
}
The problem is sometimes the code works and does the job as expected and other times without any modification to the code it gets stuck in the while look (while (parent_recieved_sig < 10) usleep(50000);
), I have been trying to figure out the reason but i can't to come to a conclusion, I have tried printing the value of parent_recieved_sig
sometimes it does not reach 10 meaning some signals have not been handled?
As explained in the other answer, non-real-time signals are not queued, thus you can miss some of them if they are sent simultaneously (from the OS point of view).
Below is your example slightly modified in order to replace the handling of SIGUSR2
by the handling of SIGCHLD
.
This signal is automatically sent to its parent when a child process terminates.
The interesting part is the signal handler which loops on waitpid()
for any child process (-1
) in a non-blocking fashion (WNOHANG
) as long as this succeeds.
The handling of one SIGCHLD
can lead to the detection of several simultaneous child-process terminations.
EDIT1 As suggested in the comments, a more direct solution would be to get rid of the handling of SIGCHLD
and modify the waiting loop in the main()
function like this
while (parent_recieved_sig < 10) {
pid_t r=wait(NULL);
if(r>0) {
parent_recieved_sig++;
pids[pidIndex] = r;
pidIndex++;
} else {
perror("wait()");
exit(1);
}
}
EDIT2 As suggested in the comments, the global int
s should be volatile sig_atomic_t
s in order to be detected (not considered unchanged by the optimizer) by the main thread when modified by a signal handler.
/**
gcc -std=c99 -o prog_c prog_c.c \
-pedantic -Wall -Wextra -Wconversion \
-Wc++-compat -Wwrite-strings -Wold-style-definition -Wvla \
-g -O0 -UNDEBUG -fsanitize=address,undefined
**/
#undef __STRICT_ANSI__
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <math.h>
#include <time.h>
#include <string.h>
#include <sys/wait.h>
#include <signal.h>
// ensure modifications in the signal handlers are detected
static volatile sig_atomic_t child_recieved_sig = 0;
static volatile sig_atomic_t parent_recieved_sig = 0;
static volatile sig_atomic_t pids[10];
static volatile sig_atomic_t pidIndex = 0;
int random_time(void) {
// generate random time from 10-30 sec
srand((unsigned int)time(NULL) + (unsigned int)getpid());
int rand_time = (rand() % 5) + 2; //form [0-20]+10
return rand_time;
}
//signal sent from the parent to the child to start the exam
void start_signal(int sig, siginfo_t* info, void* context) {
(void)info;
(void)context;
if (sig == SIGUSR1) {
child_recieved_sig = 1;
}
}
//signal sent from the child to the parent indicating that the exam is over
void finish_signal(int sig, siginfo_t* info, void* context) {
(void)info;
(void)context;
if (sig == SIGCHLD) {
for(;;) {
pid_t r=waitpid(-1, NULL, WNOHANG);
if(r<=0) break;
parent_recieved_sig++;
pids[pidIndex] = r;
pidIndex++;
}
}
}
int main(void) {
struct sigaction ss = { 0 }; // fs = start signal
ss.sa_sigaction = start_signal;
ss.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1, &ss, NULL);
struct sigaction fs = { 0 }; // fs = finish signal
fs.sa_sigaction = finish_signal;
fs.sa_flags = SA_SIGINFO;
sigaction(SIGCHLD, &fs, NULL);
for (int i = 0;i < 10;i++) { //generate 10 children
pid_t pid = fork();
if (pid < 0)
perror("Forking has Failed!");
else if (pid == 0) { //child process
while (child_recieved_sig != 1); // wait for signal
printf("My ID is %d and I started my exam\n", getpid());
int time_interval = random_time();
printf("SleepTime: %d<-----------------------\n", time_interval);
sleep((unsigned int)time_interval);
// kill(getppid(), SIGUSR2);
printf("Student %d finished exam\n", getpid());
exit(0);
} else {
kill(pid, SIGUSR1);
}
}
while (parent_recieved_sig < 10) usleep(50000);
printf("parent_received_sig: %d\n", parent_recieved_sig);
printf("The Exam is Over ==> Results are:\n");
printf("Student ID=%d Finished First\n", pids[0]);
printf("Student ID=%d Finished Second\n", pids[1]);
printf("Student ID=%d Finished Third\n", pids[2]);
printf("Student ID=%d Finished Forth\n", pids[3]);
printf("Student ID=%d Finished Fifth\n", pids[4]);
printf("Student ID=%d Finished Sixth\n", pids[5]);
printf("Student ID=%d Finished Seventh\n", pids[6]);
printf("Student ID=%d Finished Eighth\n", pids[7]);
printf("Student ID=%d Finished Nineth\n", pids[8]);
printf("Student ID=%d Finished Tenth\n", pids[9]);
}