Reputation: 1083
I have been reading "The Linux Programming Interface". Chapter 27, Program execution.
I understand that the author demonstrates how we could implement the system
call using exec
and fork
. However, the challenging part is handling signals. In particular I am confused with the following text
The first signal to consider is SIGCHLD. Suppose that the program calling system() is also directly creating children, and has established a handler for SIGCHLD that performs its own wait(). In this situation, when a SIGCHLD signal is generated by the termination of the child created by system(), it is possible that the signal handler of the main program will be invoked and collect the child’s status before system() has a chance to call waitpid(). (This is an example of a race condition.)
The following is the code example without signal handling
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int system(char *command)
{
int status;
pid_t childPid;
switch(childPid = fork())
{
case -1: /* Error */
return -1;
case 0: /* Child */
execl("/bin/sh", "sh", "-c", command, (char*) NULL);
_exit(127); /* If reached this line than execl failed*/
default: /* Parent */
if (waitpid(childPid), &status, 0) == -1)
return -1;
else
return status;
}
}
I know what the race condition ism but don't understand the whole scenario the author describes. In particular, I don't understand what "the program calling system" might be. What is the "main program"? Which process creates child procs?
Could someone, please, explain by giving examples how a race condition can arise? In C or in pseudocode.
Upvotes: 1
Views: 308
Reputation: 60067
You could have a SIGCHLD
handler installed that does int ws; wait(&ws);
.
If such a SIGCHLD
handler is allowed to run in response to a SIGCHLD
, it will race with the waitpid
done in system
, preventing system
from successfully retrieving the exit status of the child if the handler wins the race.
For this reason, POSIX prescribes that SIGCHLD
be blocked in system
.
You could still have races with wait
calls done in other signal handlers or other threads, but that would be a design error that POSIX
system won't help you with.
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
int system(char *command)
{
int status;
pid_t childPid;
switch(childPid = fork())
{
case -1: /* Error */
return -1;
case 0: /* Child */
execl("/bin/sh", "sh", "-c", command, (char*) NULL);
_exit(127); /* If reached this line than execl failed*/
default: /* Parent */
/*usleep(1);*/
if (waitpid(childPid, &status, 0) == -1)
return -1;
else
return status;
}
}
void sigchld(int Sig){ int er=errno; wait(0); errno=er; }
int main()
{
/*main program*/
//main program has a sigchld handler
struct sigaction sa;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sa.sa_handler = sigchld;
sigaction(SIGCHLD, &sa,0);
for(;;){
//the handler may occasionally steal the child status
if(0>system("true") && errno==ECHILD)
puts("Child status stolen!");
}
}
Upvotes: 2