System Programming Chapter 10 1 Signals • Objectives: – An overview of signals – Related function libraries and problems, e.g., reliability & incompatibility. • What is a signal? – Software interrupts • A way of handling asynchronous events – Asynchronous means random time occurrences • e.g., SIGABRT, SIGALRM. – 15 signals for Version 7, 31 signals for SVR4 & 4.3+BSD – <signal.h> (# > 0) 2 What conditions generate signals to a process? • Terminal-generated signals – DELETE key, ^c SIGINT • Hardware exceptions – SIGFPE ~ divided-by-0, SIGSEGV ~ illegal memory access, etc. • Function kill: one process sends a signal to another process – Owner or superuser • Shell command kill, e.g., kill –9 pid • Software conditions – SIGPIPE ~ reader of the pipe terminated – SIGALRM ~ expiration of an alarm clock 3 The disposition of a signal (the action) • Tell the kernel what to do when a signal occurs • Ignore signals – SIGKILL and SIGSTOP can not be ignored -> process termination – Some undefined behaviors for ignoring signals, such as SIGFPE. • Catch signals – Provide a signal handler • e.g., calling waitpid() when a process receives SIGCHLD • Apply the default action – Figure 10.1 4 Signals • How to read Figure 10.1? • ISO C / SUS: signals are defined in ISO C POSX.1 / XSI • Default action: Terminate w/core – not POSIX.1 – Core = memory of image is left in the file named core in the current working directory of the process -> for debugging – When core file is not generated? • • • • Non-owner setuid process Non-grp-owner setgid process No access rights at the working dir File is too big (RLIMIT_CORE) • Description: hardware faults – Implementation-defined faults 5 Signals • 41 UNIX signals in figure 10.1: let’s go through them! • SIGABRT (abort?) – terminate w/core – Call abort() • SIGALRM (alarm?) – terminate – Call setitimer() • SIGBUS – terminate w/core – Implementation-defined HW fault • SIGCHLD (child?) – ignore – It was sent whenever a process terminates or stops 6 More Signals • SIGCONT – continue/ignore – Continue a stopped process, e.g., vi – redraw the terminal screen • SIGEMT – terminate w/core – Implementation-defined HW fault • SIGFPE (floating point error?) – terminate w/core – Divid-by-0, floating point overflow, etc. • SIGHUP (hung up?) – terminate – Disconnection is detected by the terminal interface (no daemons) controlling process of a terminal – Triggering of the rereading of the config files (daemons) 7 Signals • SIGILL (illegal instruction?) – terminate w/core – Illegal hardware instruction (4.3BSD do it for abort() in the past) • SIGINFO – ignore (BSD4.3+) – Status request for fg processes (^T) • SIGINT (interrupt?) – terminate – DELETE key or ^C • SIGIO – terminate/ignore – Indicate an asynchronous I/O event (SIGIO=SIGPOLL, Terminate on SVR4) 8 Signals • SIGIOT – terminate w/core – Implementation-defined HW fault (System V did it for abort() in the past) • SIGKILL – terminate – Could not be ignored or caught! • SIGLWP – ignore – Used internally by Solaris thread library • SIGPIPE – terminate – reader of the pipe/socket terminated • SIGPOLL – terminate (SVR4) – A specific event happens on a pollable device. – Use with poll() function 9 Signals • SIGPROF – terminate – A profiling timer expires (setitimer) • SIGPWR (power) – ignore (SVR4) – System dependent on SVR4 • UPS init shutdowns the system • SIGQUIT – terminate w/core – ^\ triggers the terminal driver to send the signal to all foreground processes. • SIGSEGV – terminate w/core – Invalid memory access 10 Signals • SIGSTOP – stop process (like SIGTSTP) – Can not be caught or ignored • SIGSYS – terminate w/core – Invalid system call • SIGTERM – terminate – Termination signal sent by kill command • SIGTRAP – terminate w/core – Implementation-defined HW fault • SIGTSTP – stop process – Terminal stop signal (^Z) to all foreground processes 11 Signals • SIGTTIN – stop process – Generated when bg processes try to read from the controlling terminal • SIGTTOU – stop process – Generated when bg processes try to write to the controlling terminal – Could be generated by terminal operations, e.g., tcflush • SIGURG – ignore – Urgent condition (e.g., receiving of out-of-band data on a network connection) – out-of-band data = data delivered with high priority (not received in order) 12 Signals • SIGUSR1 – terminate – User-defined • SIGUSR2 – terminate – User-defined • SIGVTALRM – terminate – A virtual timer expires (setitimer) • SIGWINCH – ignore – Changing of a terminal window size 13 Signals • SIGXCPU – terminate w/core – Exceed the soft CPU time limit • SIGXFSZ – terminate w/core – Exceed the soft file size (max size of a file created)! 14 Catching a signal • Specify a function (signal handler) to be called when a signal occurs. #include <signal.h> void (*signal(int signo, void (*func)(int)))(int); – signo – Figure 10.1 – func: SIG_IGN, SIG_DFL, the address of the signal handler/ signal-catching function • SIGKILL & SIGSTOP – Returned value: the address of the previous signal handler. 15 static void sig_usr(int); /* one handler for both signals */ int main(void) { if (signal(SIGUSR1, sig_usr) == SIG_ERR) err_sys("can't catch SIGUSR1"); if (signal(SIGUSR2, sig_usr) == SIG_ERR) err_sys("can't catch SIGUSR2"); for ( ; ; ) pause(); } static void sig_usr(int signo) /* argument is signal number */ { if (signo == SIGUSR1) printf("received SIGUSR1\n"); else if (signo == SIGUSR2) printf("received SIGUSR2\n"); else err_dump("received signal %d\n", signo); } linux1:~/sys_prog_06/test> fig10.2.exe Suspended linux1:~/sys_prog_06/test> bg [1] fig10.2.exe & linux1:~/sys_prog_06/test> ps PID TTY TIME CMD 1735 pts/7 00:00:00 tcsh 1793 pts/7 00:00:00 fig10.2.exe 1798 pts/7 00:00:00 ps linux1:~/sys_prog_06/test> kill -USR1 1793 received SIGUSR1 linux1:~/sys_prog_06/test> kill 1793 16 signal() • Remark: – SVR4: signal function – unreliable signal semantics • Unreliable = signals can be lost & a process never knows it – 4.3+BSD: defined in terms of sigaction function – reliable signal semantics – typedef void Sigfunc(int) • Sigfunc *signal(int, sigfunc *); – Constants: #define SIG_ERR (void (*)())-1 #define SIG_DFL (void (*)())0 #define SIG_IGN (void (*)())1 17 Program Start-up through exec • All signals are set to their default actions unless some are ignored. – The exec functions change the disposition of any signals that are being caught to their default action. – Why? • The address of the signal handler no longer exists in the new program – How about Fork()? • Child inherits the parent’s signal dispositions • The shells automatically set the disposition of the interrupt and quit signals of background processes to “ignored”. – Type ^C: does not terminate the background processes? 18 Unreliable Signals: Problem 1 • In earlier version of UNIX: unreliable Signals – Signals could get lost & a process never knows it • Action of a signal was reset to its default each time the signal occurred • Does this prevent process termination? – What if another signal (SIGINT) occurs before the call to signal in the signal handler? – Easy to debug? int sig_int(); // my signal handler routine … signal(SIGINT, sig_int); // establish the handler … sig_int() { signal(SIGINT, sig_int); // reestablish handler for next time … // process the signal } 19 Unreliable Signals: Problem 2 • Catch a signal or ignore the signal – Cannot turn off when the process does not want it to occur • Code: prevent the following signals from occurring, but remember if they do occur – What happens if signal occurs before after comparison and before pause()? int sig_int_flag; main() { int sig_int(); … signal(SIGINT, sig_int); … while (sig_int_flag == 0) pause(); } sig_int() { signal(SIGINT, sig_int); sig_int_flag = 1; } • Process sleeps forever 20 Interrupted System Calls • What if a signal occurs in a system call? • When to deliver the signal to the process? – Immediate: Interrupt the system call & deliver the signal – Wait: till the completion of the system call & then deliver the signal • How to choose between them? – Slow system calls vs. others. 21 Slow system calls & Slow devices • “Slow” System Calls – Reads from or writes to files that can block the caller forever (e.g., pipes, terminal devices, and network devices) – Opens of files (e.g., terminal device) that block until some conditions occurs. – pause, wait, certain ioctl operations – Some IPC functions • Disk IO system calls are not slow (blocked only temporarily) • Traditional Approach – “Slow” system calls could be interrupted errno = EINTR • Interactive devices = slow devices 22 Dealing with Interrupted System Calls • A typical code sequence again: if ((n = read(fd, buff, BUFFSIZE)) < 0) { if (errno == EINTR) goto again; } • Restarting of interrupted system calls – since 4.2BSD – ioctl, read, readv, write, writev, wait, and waitpid. • What if restarting is not wanted? – 4.3BSD allows a process to disable the restarting on a persignal basis. 23 Different signal implementations • Scan Figure 10.3 on page 305 24 Reentrant Functions • When interrupts occur, a function could be called twice at the same time and causes problems. – Example: calling a function from a signal handler (figure 10.5) • Potential Problem: – In the signal handler, we can’t tell where the process was executing when the signal was caught! • Example: may be calling malloc() and in the middle of changing a linked list 25 Calling a function (getpwnam) twice static void my_alarm(int signo) { struct passwd *rootptr; int main(void) { struct passwd *ptr; printf("in signal handler\n"); if ((rootptr = getpwnam("root")) == NULL) err_sys("getpwnam(root) error"); alarm(1); signal(SIGALRM, my_alarm); alarm(1); for ( ; ; ) { if ((ptr = getpwnam("sar")) == NULL) err_sys("getpwnam error"); if (strcmp(ptr->pw_name, "sar") != 0) printf("return value corrupted!, pw_name = %s\n", ptr->pw_name); } } } 26 Non-Reentrant Functions /* non-reentrant function */ char *strtoupper(char *string) { static char buffer[MAX_STRING_SIZE]; int index; for (index = 0; string[index]; index++) buffer[index] = toupper(string[index]); buffer[index] = 0 ; return buffer; } Where/what is the problem? overwriting the static buffer in the 2nd call static variable should not be used 27 Reentrant Functions /* reentrant function (a poor solution) */ char *strtoupper(char *string) { char *buffer; int index; /* error-checking should be performed! */ buffer = malloc(MAX_STRING_SIZE); for (index = 0; string[index]; index++) buffer[index] = toupper(string[index]); buffer[index] = 0 ; return buffer; } Where/what is the problem? malloc() is not reentrant safe. 28 Better Solution /* reentrant function (a better solution) */ char *strtoupper_r(char *in_str, char *out_str) { int index; for (index = 0; in_str[index]; index++) out_str[index] = toupper(in_str[index]); out_str[index] = 0 ; return out_str; } From “General Programming Concepts: Writing and Debugging Programs” 29 Some Non-reentrant Functions • The POSIX.1 process environment functions getlogin(), ttyname() (see ISO/IEC 9945:1-1996, §4.2.4 and 4.7.2) – getlogin() returns a pointer to static data whose content is overwritten by each call. • The C-language functions asctime(), ctime(), gmtime() and localtime() (see ISO/IEC 9945:1-1996, §8.3.4-8.3.7) • The POSIX.1 system database functions getgrgid(), getgrnam(), getpwuid() and getpwnam() (see ISO/IEC 9945:1-1996, §9.2.1 a 31 Reentrant Functions • Figure 10.4 – reentrant functions – *-marked functions – not in POSIX.1, but in SVR4 • Non-Reentrant Functions – Those which use static data structures – Those which call malloc or free – Those which are part of the standard I/O library – usage of global data structures 32 Reentrant Functions • One Errno variable per thread – Cause problems for reentrance functions? – Example: main calling read() & setting errno; signal occurs, signal handling calling read() overwriting errno. • Solution: save & restore errno inside a handler • longjmp() is not reentrance because of updating a global data structure – How to make it reentrance safe? 33 SIGCLD vs SIGCHLD • Recall zombie process – A process has terminated, but its parent has not waited for it. • SIGCLD (SV) vs SIGCHLD (BSD, POSIX) – SIGCHLD • Handling is similar to those of other signals. – SIGCLD: Use signal() or sigset() to set its disposition • The children of the calling process which sets its disposition to SIG_IGN will not generate zombie processes (not for BSD). – wait() returns –1 with errno = ECHILD until all children terminate. 34 SIGCLD Semantics • When SIGCLD is set to be caught, the kernel checks if there is any child ready to be waited. – If yes, call SIGCLD handler immediately! • Problem in Program 10.3 – Page 309 35 static void sig_cld(int); int main() { pid_t pid; static void sig_cld(int signo) /* interrupts pause() */ { pid_t pid; int status; if (signal(SIGCLD, sig_cld) == SIG_ERR) perror("signal error"); if ((pid = fork()) < 0) { perror("fork error"); } else if (pid == 0) { /* child */ sleep(2); _exit(0); } pause(); /* parent */ exit(0); } printf("SIGCLD received\n"); if (signal(SIGCLD, sig_cld) == SIG_ERR) /* reestablish handler */ perror("signal error"); if ((pid = wait(&status)) < 0) /* fetch child status */ perror("wait error"); printf("pid = %d\n", pid); } continual string of SIGCHL How to fix this? move signal() after wait() 36 Reliable Signals Signal generated Pending Signal delivered set flag sig_int() Time Divided by 0 • A signal is generated when – the event that causes the signal occurs! – A flag is set in the process table. • A signal is delivered when – the action for the signal is taken. • A signal is pending during – the time between its delivery and generation. 37 Reliable Signals • A signal is blocked until – the process unblock the signal, or – The corresponding action become “ignore”. – (if the action is either default or a handler) • A process can set which signals are blocked and pending! – sigpending() – Set signal mask for each process – sigprocmask() • 1 bit per signal • More details later 38 More than one signal is ready for delivery? • Signals are queued when – a blocked signal is generated more than once. – POSIX.1 (but not over many Unix) allows the system to deliver the signal either once or more than once. • Delivery order of signals – No order under POSIX.1, but its Rationale states that signals related to the current process state, e.g., SIGSEGV, should be delivered first. 39 Send a signal to a process or a process group #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int signo); int raise(int signo); // itself – raise(signo) = kill(getpid(), signo) – Return 0 if okay, -1 on error – pid > 0 to the process – pid == 0 to “all” processes with the same gid of the sender (excluding proc 0, 1, 2) – pid < 0 to “all” processes with gid == |pid| – pid == -1 broadcast signals under SVR4 and 4.3+BSD 40 kill and raise • Right permissions must be applied! – Superuser is mighty! – Real or effective uid of the sender == that of the receiver • _POSIX_SAVED_IDS receiver’s saved set-uid is checked up, instead of effective uid • SIGCONT member of the session • signo == 0 ~ a null signal – Normal error checking is performed by kill() to see if a specific process exists. • kill() returns –1, and errno == ESRCH 41 Setting a timer (SIGALRM) #include <unistd.h> unsigned int alarm(unsigned int secs); – Set a timer that will expire at a specified time in the future – A previously registered alarm is replaced by the new value – the left seconds is returned! – There could be a delay in calling handler because of processor scheduling delays. – alarm(0) resets (cancels) the alarm. – Default: termination 42 Suspend a process till a signal is caught #include <unistd.h> int pause(void); – Return if a signal handler is executed. • Returns –1 with errno = EINTR 43 Implement sleep1() with alarm() & pause(): 3 potential problems static void sig_alrm(int signo) { /* nothing to do, just return to wake up the pause */ } unsigned int sleep1(unsigned int nsecs) { if (signal(SIGALRM, sig_alrm) == SIG_ERR) return(nsecs); alarm(nsecs); /* start the timer */ pause(); /* next caught signal wakes us up */ return(alarm(0)); /* turn off timer, return unslept time */ } 44 Three potential problems • Any previous alarm? – What should be done if old alarm < nsecs? – What should be done if old alarm > nsecs? • The lost of the previous SIGALRM handler • A race condition – signal between alarm() & pause() -> sleep in pause() forever 45 Any previous alarm - solution if ( (oldalarm = alarm(nsecs)) > 0) // get the old alarm value if (oldalarm < nsecs) { alarm(0); alarm( oldalarm ); pause(); return(alarm(0));} else { pause(); return( alarm( oldalarm – nsecs) ); } 46 Previous SIGALRM handler - solution if ( (oldhandler = signal(SIGALRM, sig_alrm)) == SIG_ERR) return (nsecs); // lines 15-16 here. signal(SIGALRM, oldhandler); return( alarm(0) ); 47 race condition - solution • What is problem? – pause() may not be executed after signal() • How to fix it? – The trick is in the signal handler: jump past pause(). 48 race condition – SV2 solution static jmp_buf env_alrm; static void sig_alrm(int signo) { longjmp(env_alrm, 1); } unsigned int sleep2(unsigned int nsecs) { if (signal(SIGALRM, sig_alrm) == SIG_ERR) return(nsecs); if (setjmp(env_alrm) == 0) { alarm(nsecs); /* start the timer */ pause(); /* next caught signal wakes us up */ } return(alarm(0)); /* turn off timer, return unslept time */ } 49 problem in SV2 solution • What if SIGALRM interrupts another signal handler? – longjmp() will abort that signal handler. 50 unsigned int static void sleep2(unsigned int); sig_int(int); int main(void) { unsigned int static jmp_buf env_alrm; static void sig_alrm(int signo) { longjmp(env_alrm, 1); } unslept; if (signal(SIGINT, sig_int) == SIG_ERR) err_sys("signal(SIGINT) error"); unslept = sleep2(5); printf("sleep2 returned: %u\n", unslept); exit(0); } static void sig_int(int signo) { int i, j; volatile int k; unsigned int sleep2(unsigned int nsecs) { if (signal(SIGALRM, sig_alrm) == SIG_ERR) return(nsecs); if (setjmp(env_alrm) == 0) { alarm(nsecs); /* start the timer */ pause(); /* next caught signal wakes us up */ } return(alarm(0)); /* turn off timer, return unslept time */ } /* * Tune these loops to run for more than 5 seconds * on whatever system this test program is run. */ printf("\nsig_int starting\n"); for (i = 0; i < 300000; i++) for (j = 0; j < 4000; j++) k += i * j; printf("sig_int finished\n"); // does not finish } 51 Lessons from sleep1() & sleep2() • Easy to use signals incorrectly • One more example: – time out read on a slow device after some amount of time 52 Program with 2 problems (race condition & automatic restarting) int main(void) { int n; char line[MAXLINE]; if (signal(SIGALRM, sig_alrm) == SIG_ERR) err_sys("signal(SIGALRM) error"); race condition: process blocks more than 10 seconds alarm(10); if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0) err_sys("read error"); in case of automatically restarting, the restarted alarm(0); read call is not interrupted by alarm(10) write(STDOUT_FILENO, line, n); exit(0); in case of } static void sig_alrm(int signo) { /* nothing to do, just return to interrupt the read */ } 53 static void sig_alrm(int); static jmp_buf env_alrm; int main(void) { int n; char line[MAXLINE]; static void sig_alrm(int signo) { longjmp(env_alrm, 1); } if (signal(SIGALRM, sig_alrm) == SIG_ERR) err_sys("signal(SIGALRM) error"); if (setjmp(env_alrm) != 0) err_quit("read timeout"); alarm(10); if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0) err_sys("read error"); alarm(0); write(STDOUT_FILENO, line, n); exit(0); } 54 Announcements • Final exam: 6/21 class time – Coverage: chapters 7, 8, 10.1~10.19, 15.1~15.5 • MP4 is due after final exam – TA will talk about it 55 Signal Sets: representing multiple signals • Why new type sigset_t? – The number of different signals could exceed the number of bits in an integer! #include <signal.h> int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int sig_no); int sigdelset(sigset_t *set, int sig_no); int sigismember(const sigset_t *set, int sig_no); 56 Changing signal set (signal mask) int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); • initialize the signal set pointed to by set so that all signals are excluded/included int sigaddset(sigset_t *set, int sig_no); int sigdelset(sigset_t *set, int sig_no); • add sign_no to set int sigismember(const sigset_t *set, int sig_no); • check if sign_no is in set 57 Signal Sets • A macro Implementation if # of signals <= bits in an integer: #define sigemptyset(ptr) ( *(ptr) = 0) #define sigfillset(ptr) ( *(ptr) = ~(sigset_t)0, 0) – Program 10.9 • Not one-line macros because of the checking requirements for validity and the setting of errno under POSIX.1. 58 sigprocmask() examine/change signal mask #include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oset); – If oset is non-null, current signal mask is returned through oset – If set is non-null, the how argument indicates how the current signal mask is modified. • SIG_BLOCK: add set to blocked signals (union of current signal mask & set) • SIG_UNBLOCK: add set to unblocked signals (intersection of current signal mask & complement of set) • SIGMASK: replace current signal mask by set – At least one of the pending or one of unblocking signals will be delivered when the sigprocmsk() returns. 59 sigprocmask example print names of signals in signal mask void pr_mask(const char *str) { sigset_t sigset; int errno_save; errno_save = errno; /* we can be called by signal handlers */ if (sigprocmask(0, NULL, &sigset) < 0) err_sys("sigprocmask error"); printf("%s", str); if (sigismember(&sigset, SIGINT)) printf("SIGINT "); if (sigismember(&sigset, SIGQUIT)) printf("SIGQUIT "); if (sigismember(&sigset, SIGUSR1)) printf("SIGUSR1 "); if (sigismember(&sigset, SIGALRM)) printf("SIGALRM "); /* remaining signals can go here */ printf("\n"); errno = errno_save; } 60 sigpending() access pending & blocked signal #include <signal.h> int sigpending(sigset_t *set); – Returns the set of pending, blocked signals • Program 10.15 – Page 322 – SIGQUIT is blocked, current signal mask is saved, the process sleeps for 5 seconds, and SIGQUIT is unblocked. – SIGQUIT is delivered until the signal is unblocked and before sigprocmask() returns. – Can we use “SET_UNBLOCK” instead of SIG_SETMASK? – No queuing of signals. 61 int main(void) { sigset_t newmask, oldmask, pendmask; if (signal(SIGQUIT, sig_quit) == SIG_ERR) err_sys("can't catch SIGQUIT"); /* * Reset signal mask which unblocks SIGQUIT. (4) */ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); printf("SIGQUIT unblocked\n"); /* * Block SIGQUIT and save current signal mask. sleep(5); /* SIGQUIT here will terminate with core file */ (1) */ sigemptyset(&newmask); exit(0); sigaddset(&newmask, SIGQUIT); } if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) static void sig_quit(int signo) { err_sys("SIG_BLOCK error"); printf("caught SIGQUIT\n"); if (signal(SIGQUIT, SIG_DFL) == SIG_ERR) sleep(5); /* SIGQUIT here will remain err_sys("can't reset SIGQUIT"); pending */ } (3) if (sigpending(&pendmask) < 0) err_sys("sigpending error"); if (sigismember(&pendmask, SIGQUIT)) printf("\nSIGQUIT pending\n"); $ ./a.out (2) ^\ // generate signal once before 5 seconds (3) SIGQUIT pending (5) caught SIGQUIT SIGQUIT unblocked 62 sigaction() examine/change signal actions #include <signal.h> int sigaction(int signo, const struct sigaction *act, struct sigaction *oact); – change the action associated with signo in act – if oact is non-null, return the previous action in oact. – how does it differ from signal()? • sa_mask is added to current signal mask before signal-catching function is called, then removed after signal-catching function is done. – Why? • block signals whenever a signal hanlder is invoked struct sigaction { void (*sa_handler)(int); sigset_t sa_mask; int sa_flags; void (*sa_sigaction) (int siginfo_t *, void *) }; 63 sigaction() examine/change signal actions #include <signal.h> int sigaction(int signo, const struct sigaction *act, struct sigaction *oact); – what is sa_flags? • options for handling signal – SA_INTERRUPT: system calls interrupted are not auto-restarted – SA_INFO: additional info to a signal handler (sa_sigaction) • see figure 10.16 – what is sa_sigaction? • an alternative handler struct sigaction { void (*sa_handler)(int); sigset_t sa_mask; int sa_flags; void (*sa_sigaction) (int siginfo_t *, void *) }; 64 sa_sigaction() • Used with sa_flags has SA_SIGINFO void handler(int signo, siginfo_t * info, void *context) • info: additional information about why this signal is generated • context: process context at the time of signal delivery struct siginfo { int si_signo; int si_errno; int si_code; pid_t si_pid; uid_t si_uid; void *si_addr; int si_status; long si_band; } /* signal number */ /* if nonzero, errno value */ /* additional info – see figure 10.17*/ /* sending process id */ /* sending process real user id */ /* address that caused the fault */ /* exit value or signal number */ /* band number for SIGPOLL */ 65 Implement signal() with sigaction() /* Reliable version of signal(), using POSIX sigaction(). */ Sigfunc * signal(int signo, Sigfunc *func) { struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (signo == SIGALRM) { #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; // any system calls interrupted by other signals are restarted automatically, except for SIGALARM #endif } if (sigaction(signo, &act, &oact) < 0) return(SIG_ERR); return(oact.sa_handler); 66 } Implement signal_intr() prevent interrupted system calls from being restarted Sigfunc * signal_intr(int signo, Sigfunc *func) { struct sigaction act, oact; #ifdef act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif if (sigaction(signo, &act, &oact) < 0) return(SIG_ERR); return(oact.sa_handler); } 67 recall: setjmp and longjmp Consider this as a non-local goto #include <setjmp.h> int setjmp(jmp_buf env); – Return 0 if called directly; otherwise, it could return a value val from longjmp(). – env tends to be a global variable. int longjmp(jmp_buf env, int val); – longjmp() unwinds the stack and affect some variables. • Program 7.11 – setjmp and longjmp 68 main(void) { char line[MAXLINE]; if (setjmp(jmpbuffer) != 0) printf(“error”); while (fgets(line, MAXLINE, stdin) != NULL) do_line(line); exit(0); } } } void cmd_add(void) { int token; token = get_token(); /* rest of processing for this command */ /* nonfatal error -> invalid number -> go back to reading the next line */ if (token < 0) { longjmp(jumpbuffer, 1); } } char *tok_ptr; get_token() */ /* global pointer for void do_line(char *ptr) of input */ { int cmd; /* process one line tok_ptr = ptr; while ((cmd = get_token()) > 0) { switch (cmd) { /* one case for each command */ ….. } int get_token(void) { /* fetch next token from line pointed to by tok_ptr */ } 69 sigsetjmp & siglongjmp nonlocal branch in signal handlers • Why are they needed? – When a signal is caught, the signal-catching function is entered with the current signal automatically added to the signal mask. • Why? prevent subsequent occurrences of that signal from interrupting the same signal handler. – What are values in the signal mask when we longjmp out of the signal handler? • Some (e.g., BSD) saves signal mask (at the time when sigsetjmp is called) & restores it (at the time when siglongjmp is called) • Some (e.g., Linux) does not. 70 sigsetjmp & siglongjmp nonlocal branch in signal handlers #include <setjmp.h> int sigsetjmp(sigjmp_buf env, int savemask); – Return 0 if called directly, nonzero if returning from a call to siglongjmp void siglongjmp(sigjmp_buf env, int val); – What is the difference with setjmp & longjmp? • sigsetjmp() saves the current signal mask of the process in env if savemask !=0. • When the siglongjmp() is called, signal mask is restored. 71 example using sigsetjmp & siglongjmp what is it going to print? static void sig_usr1(int), sig_alrm(int); static sigjmp_buf jmpbuf; static volatile sig_atomic_t canjump; static void sig_usr1(int signo) { time_t starttime; if (canjump == 0) return; /* unexpected signal, ignore */ int main(void) { if (signal(SIGUSR1, sig_usr1) == SIG_ERR) err_sys("signal(SIGUSR1) error"); if (signal(SIGALRM, sig_alrm) == SIG_ERR) err_sys("signal(SIGALRM) error"); pr_mask("starting main: "); /* {Prog prmask} */ if (sigsetjmp(jmpbuf, 1)) { pr_mask("ending main: "); (4) none exit(0); } canjump = 1; /* now sigsetjmp() is OK */ for ( ; ; ) pause(); } pr_mask("starting sig_usr1: "); (1) SIGUSER1 alarm(3); /* SIGALRM in 3 seconds */ starttime = time(NULL); for ( ; ; ) /* busy wait for 5 seconds */ if (time(NULL) > starttime + 5) break; pr_mask("finishing sig_usr1: "); (3) SIGUSER1 canjump = 0; siglongjmp(jmpbuf, 1); /* jump back to main, don't return */ } static void sig_alrm(int signo) { pr_mask("in sig_alrm: "); } (2) SIGUSER1 SIGALARM72 sigsuspend #include <signal.h> if (sigprocmask(…) < 0) err_sys(…); int sigsuspend(const sigset_t *sigmask); pause(); CPU Scheduling could occur! – Atomic operation: (1) set the signal mask to sigmsk and (2) suspend until a signal is caught or until a signal occurs that terminates the process. – Return –1. errno = EINTR 73 why needs sigsuspend()? • Say if you want to protect a region of code from a specific signal. • So implement something like: sigset_t newmask, oldmask sigemptyset(&newmask); sigaddset(&newmask, SIGINT) /* block SIGINT and save current signal mask */ if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) err_sys(“SIG_BLOCK error”); /* critical region of code */ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys(“SIG_BLOCK error”); block indefinitely pause(); /* wait for signal to occur */ 74 a correct implementation using sigsuspend() sigset_t newmask, oldmask sigemptyset(&newmask); sigaddset(&newmask, SIGINT) if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) err_sys(“SIG_BLOCK error”); /* critical region of code */ /* pause & reset the signal maks which unblocks SIGINT*/ if (sigsuspend(&oldmask) < 0) err_sys(“SIG_BLOCK error”); 75 #include "apue.h" volatile sig_atomic_t quitflag; /* set nonzero by signal handler */ static void sig_int(int signo) /* one signal handler for SIGINT and SIGQUIT */ { if (signo == SIGINT) printf("\ninterrupt\n"); else if (signo == SIGQUIT) quitflag = 1; /* set flag for main loop */ } waiting for a signal handler to set a global variable (quitflag) int main(void) { sigset_t newmask, oldmask, zeromask; if (signal(SIGINT, sig_int) == SIG_ERR) err_sys("signal(SIGINT) error"); if (signal(SIGQUIT, sig_int) == SIG_ERR) err_sys("signal(SIGQUIT) error"); sigemptyset(&zeromask); sigemptyset(&newmask); sigaddset(&newmask, SIGQUIT); /* * Block SIGQUIT and save current signal mask. */ if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) err_sys("SIG_BLOCK error"); while (quitflag == 0) sigsuspend(&zeromask); /* * SIGQUIT has been caught and is now blocked; do whatever. */ quitflag = 0; /* * Reset signal mask which unblocks SIGQUIT. */ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) 76 err_sys("SIG_SETMASK error"); abort #include <stdlib.h> void abort(void); – Sends SIGABRT to the process! – Can catch it in signal handler to do cleanup work, but abort() won’t return to the caller (ANSI C) • The SIGABRT won’t return if its handler calls exit, _exit, longjmp, siglongjmp. – Program 10.18 – Page 341 • POSIX.1 implementation of abort() 78 abort – ANSI C • The Implementation determines whether output streams are flushed and whether temporary files are deleted. – POSIX.1 • POSIX.1 specifies that abort overrides the blocking or ignoring of the signal by the process. • If abort() terminates a process, all open standard I/O streams are closed (by fclose()); otherwise, nothing happens. 79 signal issues in system() implementation • A correct implementation of system() system call should block SIGINT & SIGCHLD of the calling process. • Why blocking these signals? – system() fork/exec a child shell process (/bin/sh), which then fork/exec the shell command string. • What happened when interrupt signal occurs? – SIGINT (default) sends to all parent processes. • What happened when the command string process terminates? – SIGCHLD sends to calling process and its signal handler – (but should be dealt with by the system()) 80 system() • Program 10.20 – Page 345 – The implementation of system() – Ignoring of SIGINT and SIGCHLD & blocking of SIGCHLD (POSIX.2) – Setting of a proper signal mask before fork() – Termination status of the shell 81 sleep #include <unistd.h> unsigned int sleep(unsigned int secs); – Suspend until (1) the specified time elapsed, or (2) a signal is caught and the handler returns returns the unslept seconds. • Problems: – alarm(10), 3 secs, sleep(5)? • Another SIGALRM in 2 seconds? Both SVR4 & 4.3BSD – not POSIX.1 requirements – Alarm() & sleep() both use SIGALRM. 82 sleep • Under SVR4, alarm(6), 3 secs, sleep(5) sleep() returns in 3 secs! • Program 10.21 – Page 318 – Implementation of sleep() – Program 10.4 – Page 286 • Unreliable signals! – No handling of previously set alarms. 83 main() signal() signal() pr_mask() sigsetjmp() pause() SIGUSR1 delivered SIGUSR1 sig_usr1 pr_mask() alarm() time() time() time() SIGUSR1 SIGARM sig_alrm SIGALARM delivered pr_mask() return() Return from signal hander pr_mask() siglongjmp() sigsetjmp() pr_mask() exit() SIGUSR1 84 85