I'm working on a shell lab for a Systems course, and I've been having some really weird race condition bugs that I've been trying to solve since Friday night and can't seem to pin down.
My current code: http://buu700.com/tsh
Everything before START OF MY CODE and after END OF MY CODE is provided by the course instructors, so none of that should be the source of the issues.
We have a test script as well; here is the output of my current test results: http://buu700.com/sdriver
/*****************
* Signal handlers
*****************/
/*
* sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
* a child job terminates (becomes a zombie), or stops because it
* received a SIGSTOP, SIGTSTP, SIGTTIN or SIGTTOU signal. The
* handler reaps all available zombie children, but doesn't wait
* for any other currently running children to terminate.
*/
void
sigchld_handler(int sig)
{
pid_t pid;
int status, termsig;
struct job_t *job;
sigset_t s;
sigemptyset(&s);
sigaddset(&s, SIGCHLD);
sigaddset(&s, SIGINT);
sigaddset(&s, SIGTSTP);
sigprocmask(SIG_BLOCK, &s, NULL);
while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
if (WIFEXITED(status)) {
deletejob(job_list, pid);
}
if ((termsig = WTERMSIG(status))) {
deletejob(job_list, pid);
safe_printf("Job [%i] (%i) %s by signal %i\n",
pid2jid(pid), pid, "terminated", termsig);
}
if (WIFSTOPPED(status)) {
job = getjobpid(job_list, pid);
job->state = ST;
safe_printf("Job [%i] (%i) %s by signal %i\n",
pid2jid(pid), pid, "stopped", SIGTSTP);
}
}
if (errno != ECHILD)
unix_error("waitpid error");
sigprocmask(SIG_UNBLOCK, &s, NULL);
return;
}
/*
* sigint_handler - The kernel sends a SIGINT to the shell whenver the
* user types ctrl-c at the keyboard. Catch it and send it along
* to the foreground job.
*/
void
sigint_handler(int sig)
{
sigset_t s;
sigemptyset(&s);
sigaddset(&s, SIGCHLD);
sigaddset(&s, SIGINT);
sigaddset(&s, SIGTSTP);
sigprocmask(SIG_BLOCK, &s, NULL);
kill(-1, sig);
sigprocmask(SIG_UNBLOCK, &s, NULL);
return;
}
/*
* sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
* the user types ctrl-z at the keyboard. Catch it and suspend the
* foreground job by sending it a SIGTSTP.
*/
void
sigtstp_handler(int sig)
{
sigset_t s;
sigemptyset(&s);
sigaddset(&s, SIGCHLD);
sigaddset(&s, SIGINT);
sigaddset(&s, SIGTSTP);
sigprocmask(SIG_BLOCK, &s, NULL);
kill(-1, sig);
sigprocmask(SIG_UNBLOCK, &s, NULL);
return;
}
I have a suspicion that the bug comes from your racing against other signals in your signal handlers:
sigint_handler(int sig)
{
sigset_t s;
sigemptyset(&s);
sigaddset(&s, SIGCHLD);
sigaddset(&s, SIGINT);
sigaddset(&s, SIGTSTP);
sigprocmask(SIG_BLOCK, &s, NULL);
When you register the signal handler with sigaction(2), you can provide an sa_mask that the kernel will use to block signals for you while your signal handler is running. This is done atomically and requires no extra work on your part. Simply populate this mask once when registering the signal handler.
Another possibility comes from the SIGTSTP signal; page 350 of my copy of APUE, 2nd edition says, in part:
Only a job-control shell should reset the disposition of [SIGTSTP, SIGTTIN, SIGTTOU] to SIG_DFL.
What's left unsaid in those paragraphs, but I think is fair to assume, is that the shell should set the signal disposition to SIG_DFL for the child processes -- it still needs to do something to handle the signals itself.
Did you properly handle the signal dispositions in the children?
Related
I am studying this piece of code from the CSAPP book:
#include "csapp.h"
volatile sig_atomic_t pid;
void sigchld_handler(int s)
{
int olderrno = errno;
pid = Waitpid(-1, NULL, 0);
errno = olderrno;
}
void sigint_handler(int s)
{
}
#define N 10
int main(int argc, char **argv)
{
sigset_t mask, prev;
int n = N; /* 10 */
Signal(SIGCHLD, sigchld_handler);
Signal(SIGINT, sigint_handler);
Sigemptyset(&mask);
Sigaddset(&mask, SIGCHLD);
while (n--) {
Sigprocmask(SIG_BLOCK, &mask, &prev); /* Block SIGCHLD */
if (Fork() == 0) /* Child */
exit(0);
/* Wait for SIGCHLD to be received */
pid = 0;
while (!pid)
Sigsuspend(&prev);
/* Optionally unblock SIGCHLD */
Sigprocmask(SIG_SETMASK, &prev, NULL);
/* Do some work after receiving SIGCHLD */
printf(".");
}
printf("\n");
exit(0);
}
While I understand a large part of it such as how sigsuspend uses mask and interacts with SIGCHLD, I don't understand this piece from the book If the parent caught a SIGINT, then the loop test succeeds and the next iteration calls sigsuspend again. If the parent caught a SIGCHLD, then the loop test fails and we exit the loop. At this point, SIGCHLD is blocked, and so we can optionally unblock SIGCHLD.
How does SIGINT interact with sigsuspend()? How will loop test succeed if SIGINT gets caught by parent? I guess the behavior of SIGINT is overwritten by the call to Signal(), which gets overwritten to doing nothing?
Also, how is SIGCHLD still blocked after loop test fails(by sigchld_handler())?
Much thanks in advance!
Other than this, can someone please do a walkthrough of this whole example? I don't have a solid understanding after reading it multiple times.
I need to synchronize parent and child process to work in order in endless loop but I am getting deadlock after a while.
I am not allowed to use sleep, wait and I must implement the synchronization with signals. After a search I found a similar question which is used pause method, in the answers it is recommended to use sigsuspend rather than pause. So I am not sure if there is problem with my implementation or I need to use another way to avoid deadlock
Heres my code
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
void catcher( int sig ) {
printf( "inside catcher() function\n" );
}
void timestamp( char *str ) {
time_t t;
time( &t );
printf( "%s the time is %s\n", str, ctime(&t) );
}
int main( int argc, char *argv[] ) {
struct sigaction sigact;
sigset_t block_set1,block_set2;
sigfillset( &block_set1 );
sigfillset( &block_set2 );
sigdelset( &block_set1, SIGUSR1 );
sigdelset( &block_set2, SIGUSR2 );
sigemptyset( &sigact.sa_mask );
sigact.sa_flags = 0;
sigact.sa_handler = catcher;
sigaction( SIGUSR1, &sigact, NULL );
sigaction( SIGUSR2, &sigact, NULL );
pid_t child_id;
child_id = fork ();
if (child_id == 0){
while(1){
printf("child send signal\n");
kill (getppid (), SIGUSR1);
printf("child wait signal\n");
sigsuspend( &block_set2 );
printf("child is going to die\n");
}
}else{
while(1){
timestamp( "before sigsuspend()" );
sigsuspend( &block_set1 );
timestamp( "after sigsuspend()" );
kill (child_id, SIGUSR2);
printf("parent is going to die\n");
}
}
return( 0 );
}
This program has an issue, Consider a scenario where child/parent sends a signal to parent/child even before they suspend their execution using sigsuspend(). In this case handler will be executed and later no one will be
their to interrupt sigsupend() which may arise to infinite suspend state.
Quoting man page
int sigsuspend(const sigset_t *mask);
sigsuspend() temporarily replaces the signal mask of the calling
process with the mask given by mask and then suspends the process
until delivery of a signal whose action is to invoke a signal handler
or to terminate a process.
I would suggest to use sigwaitinfo() instead,
int sigwaitinfo(const sigset_t *set, siginfo_t *info);
sigwaitinfo() suspends execution of the calling thread until one of
the signals in set is pending (If one of the signals in set is already
pending for the calling thread, sigwaitinfo() will return
immediately.)
Please find the example program for same.
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <time.h>
void timestamp( char *str ) {
time_t t;
time( &t );
printf( "%s the time is %s\n", str, ctime(&t) );
}
int main( int argc, char *argv[] ) {
sigset_t parent_set, child_set;
sigset_t set;
siginfo_t info;
/*Mask SIGUSR2 and SIGUSR1 signal, you can choose this mask
* according to your requirement*/
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
sigaddset(&set, SIGUSR2);
sigprocmask(SIG_SETMASK, &set, NULL);
sigemptyset(&child_set);
sigaddset(&child_set, SIGUSR2);
sigemptyset(&parent_set);
sigaddset(&parent_set, SIGUSR1);
pid_t child_id;
child_id = fork ();
if (child_id == 0){
while(1){
printf("child send signal\n");
kill (getppid (), SIGUSR1);
printf("child wait signal\n");
/* Child will suspend its execution if parent did not notify it
* using signal(SIGUSR2), if parent has already notified sigwaitinfo()
* will return immediately
**/
sigwaitinfo(&child_set, &info );
printf("child is going to die\n");
}
}else{
while(1){
timestamp( "before sigwaitinfo()" );
/* Parent will suspend its execution if child did not notify it
* using signal(SIGUSR1), if child has already notified sigwaitinfo()
* will return immediately
**/
sigwaitinfo(&parent_set, &info );
timestamp( "after sigwaitinfo()" );
kill (child_id, SIGUSR2);
printf("parent is going to die\n");
}
}
}
I hope this will help you.
The problem, as #monc noted, is that one process signals the other when the other process isn't ready.
So, the signal is delivered (i.e., the sigaction() handler is invoked) before the call to sigsuspend(), and thus the delivery cannot interrupt the sigsuspend(). Something like this happens:
while(1){
printf("child send signal\n");
kill (getppid (), SIGUSR1);
// SIGUSR2 delivered here
printf("child wait signal\n"); // or here
// or here
sigsuspend( &block_set2 ); // wait forever...
...
}
The solution is to block (mask) SIGUSR1 in the parent and SIGUSR2 in the child from the very start. Blocking the signals will forestall delivery, keeping them pending until sigsuspend() atomically swaps out the signal mask to allow delivery (and interruption). To be safe, the signals should be blocked before you call fork(), and the child can inherit the signal mask from the parent:
sigemptyset(&ss);
sigaddset(&ss, SIGUSR1);
sigaddset(&ss, SIGUSR2);
sigprocmask(SIG_BLOCK, &ss, NULL);
... fork();
// child
while (1) {
sigset_t wakemask;
sigemptyset(&wakemask);
...
sigsuspend(&wakemask);
}
// similarly for parent ...
Note that in the above the parent and child are each blocking both signals, including the ones they send to each other. That's not necessary — you could unblock right after the fork() — but also not harmful.
Additionally, you could also accept rather than deliver the signals using the sigwait family of functions. In this case you would not need to set a handler with sigaction, but you would still need to block the signals.
My daemon (linux only) has the following signal handler:
static void signal_handler(int id, siginfo_t *si, void *context) {
if (id == SIGTERM) {
/* prevent suicide - see below */
if (si->si_pid == getpid()) {
printf("Warning: received SIGTERM from own process\n");
return;
}
/* rest of code omitted */
}
/* rest of code omitted */
}
... which is installed like this in main():
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = &signal_handler;
sa.sa_flags = SA_SIGINFO;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
The reason for the suicide check in the signal handler is that from time to time (once in 4 weeks) my daemon terminated because it received a SIGTERM from itself.
I am unable to find the cause. The only single kill() call used in the program is this one:
int kill_wrapper(pid_t pid, int sig) {
if (pid <= 0 || pid == getpid())
return -1;
return kill(pid, sig);
}
The code has no single raise() or abort() calls.
I wonder which possible (maybe external) reasons might exist that can cause this program to receive SIGTERM from itself under Linux ?
See this discussion. The bottom line is that si_pid is meaningful in very few cases.
I have two child processes and one parent process. The two child send a SIGUSR1 signal at the same time. The handler handles only one of them, and the parent receives only one of them too. I think it can be solved by using real time signal, but i don't now how to do it. Thank you for your help.
void handler(int signalnumber)
{
//do stuff
}
int main()
{
sigset_t blockset;
sigfillset(&blockset);
struct sigaction action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask);
sigaction(SIGUSR1, &action, NULL);
pid_t pid = getpid();
pid_t pids[2];
for(i = 0; i < 2; ++i)
{
if(getpid() == pid)
pids[i] = fork();
}
if(getpid() != pid)
{
while(1)
{
kill(getppid(), SIGUSR1);
}
} else
{
while(1)
{
sigdelset(&blockset, SIGUSR1);
sigsuspend(&blockset);
//do stuff
}
}
}
Edit: I replaced SIGUSR1 with SIGRTMIN+1. Now the handler receives both signals, but the parent does not. (I think, because it's not waiting for any.)
From man sigaction
sa_mask specifies a mask of signals which should be blocked (i.e.,
added to the signal mask of the thread in which the signal handler is
invoked) during execution of the signal handler. In addition, the sig‐
nal which triggered the handler will be blocked, unless the SA_NODEFER
flag is used.`
So, use SA_NODEFER.
I have tow handlers for each one of them (SIGTSTP, SIGCHLD), the thing is that when I pause a process using SIGTSTP the handler function of SIGCHLD run too. what should I do to prevent this .
signal handlers :
void signalHandler(int signal) {
int pid, cstatus;
if (signal == SIGCHLD) {
susp = 0;
pid = waitpid(-1, &cstatus, WNOHANG);
printf("[[child %d terminated]]\n", pid);
DelPID(&JobsList, pid);
}
}
void ctrlZsignal(int signal){
kill(Susp_Bg_Pid, SIGTSTP);
susp = 0;
printf("\nchild %d suspended\n", Susp_Bg_Pid);
}
Susp_Bg_Pid used to save the paused process id.
susp indicates the state of the "smash" the parent process if it is suspended or not .
Set up your SIGCHLD handler using sigaction with SA_NOCLDSTOP.
From sigaction (2)
SA_NOCLDSTOP - If signum is SIGCHLD, do not receive notification when child processes stop (i.e., when they receive one of SIGSTOP, SIGTSTP, SIGTTIN or SIGTTOU) or resume (i.e., they receive SIGCONT)(see wait(2)). This flag is only meaningful when establishing a handler for SIGCHLD.
update
void signalHandler(int sig)
{
//...
}
struct sigaction act;
act.sa_handler = signalHandler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NOCLDSTOP;
if (sigaction(SIGCHLD, &act, 0) == -1)
{
perror("sigaction");
exit(1);
}
If you aren't familiar with sigaction you should read up on it because it has several different options and behaviors that are vastly superior to signal but come at the cost of complexity and confusion before you figure out how to use it. I took my best guess at the minimum of what you seem to want to do but you'll need to learn this sooner rather than later.