Consider the following shell session, where we start off a long-running pipeline:
$ proc1 | proc2 # we don't get the shell back while this runs
Let’s say that we know it’s going to take a very long time to complete and we want to do other work. What can we do?
After issuing the following commands:
$ proc1 | proc2 & # send pipeline to background
[1] 7106
$ proc3 | proc4 | proc5 # we have our shell, start another pipeline
You have:
A session with a controlling terminal – there is exactly one foreground process group and multiple background process groups.
[1] 7106
refers to the job# and leading pid of the backgrounded pipeline. More job control:
jobs
: List all jobsbg <job>
: Resume <job>
in the backgroundfg <job>
: Bring backgrouded <job>
into the foreground#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
pid < 0
, the signal is sent to the process group with pgid == | pid |
Alternatively, use terminal-generated signals:
SIGINT
to foreground process groupSIGQUIT
to foreground process groupSIGTSTP
to foreground process group (see job control discussion above)Background process groups don’t receive these job control signals.
signal()
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
Sets disposition of signum
to handler
, where handler
can be:
SIG_IGN
: ignore the signalSIG_DFL
: take the default action associated with the signal (see man 7 signal
)sighandler_t
: handler(signum)
called to handle signalPortability (from man 2 signal
):
The only portable use of signal() is to set a signal's disposition to SIG_DFL or
SIG_IGN. The semantics when using signal() to establish a signal handler vary
across systems (and POSIX.1 explicitly permits this variation); do not use it
for this purpose.
read()
The following program, from APUE section 1.9, adds signal handling to the simple shell program we studied before:
#include "apue.h"
#include <sys/wait.h>
static void sig_int(int); /* our signal-catching function */
int main(void)
{
char buf[MAXLINE]; /* from apue.h */
pid_t pid;
int status;
if (signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal error");
printf("%% "); /* print prompt (printf requires %% to print %) */
while (fgets(buf, MAXLINE, stdin) != NULL) {
if (buf[strlen(buf) - 1] == '\n')
buf[strlen(buf) - 1] = 0; /* replace newline with null */
if ((pid = fork()) < 0) {
err_sys("fork error");
} else if (pid == 0) { /* child */
execlp(buf, buf, (char *)0);
err_ret("couldn't execute: %s", buf);
exit(127);
}
/* parent */
if ((pid = waitpid(pid, &status, 0)) < 0)
err_sys("waitpid error");
printf("%% ");
}
exit(0);
}
void sig_int(int signo)
{
printf("interrupt\n%% ");
}
Portability issues:
read()
syscall gets interrupted. errno
set to EINTR
,
causes fgets()
to return NULL
.
errno
is a global variable set to the ID of the last errorEINTR
and manually restart the syscall
signal()
resets after each signal
EINTR
Reentrancy issues: can’t call certain functions in signal handlers
malloc()
, free()
, functions with static buffers, standard I/O functions,
etc. are unsafe!man 7 signal-safety
for async-signal-safe functionsalarm()/pause()
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
// Returns: 0 or number of seconds until previously set alarm
int pause(void);
// Returns: –1 with errno set to EINTR
alarm()
: generate SIGALRM
after seconds
pause()
: suspend program execution indefinitely
Can implement sleep()
via alarm()
/pause()
, but there are many subtleties, see APUE 10.10.
We can use alarm()
to implement a slow read()
with timeout:
#include "apue.h"
static void sig_alrm(int);
int main(void)
{
int n;
char line[MAXLINE];
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
err_sys("signal(SIGALRM) error");
alarm(10);
if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0)
err_sys("read error");
alarm(0);
write(STDOUT_FILENO, line, n);
exit(0);
}
static void sig_alrm(int signo)
{
/* nothing to do, just return to interrupt the read */
}
Two problems:
alarm(10)
and read()
select()
– advanced I/O next week#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
// All four above return: 0 if OK, -1 on error
int sigismember(const sigset_t *set, int signo);
// Returns: 1 if true, 0 if false, -1 on error
sigset_t
is another opaque type – only manipulate using above functions.
A possible implementation – bit mask:
// sigset_t is just a typedef for some integer type, e.g., uint64_t
#define sigemptyset(ptr) (*(ptr) = 0)
#define sigfillset(ptr) (*(ptr) = ~(sigset_t)0, 0)
/*
* <signal.h> usually defines NSIG to include signal number 0.
*/
#define SIGBAD(signo) ((signo) <= 0 || (signo) >= NSIG)
int sigaddset(sigset_t *set, int signo)
{
if (SIGBAD(signo)) {
errno = EINVAL;
return(-1);
}
*set |= 1 << (signo - 1); /* turn bit on */
return(0);
}
int sigdelset(sigset_t *set, int signo)
{
if (SIGBAD(signo)) {
errno = EINVAL;
return(-1);
}
*set &= ~(1 << (signo - 1)); /* turn bit off */
return(0);
}
int sigismember(const sigset_t *set, int signo)
{
if (SIGBAD(signo)) {
errno = EINVAL;
return(-1);
}
return((*set & (1 << (signo - 1))) != 0);
}
Portable (POSIX-compliant) version of signal()
:
#include <signal.h>
int sigaction(int signo, const struct sigaction *restrict act,
struct sigaction *restrict oact);
// Returns: 0 if OK, -1 on error
struct sigaction {
void (*sa_handler)(int); /* addr of signal handler, */
/* or SIG_IGN, or SIG_DFL */
sigset_t sa_mask; /* additional signals to block */
int sa_flags; /* signal options, Figure 10.16 */
/* alternate handler */
void (*sa_sigaction)(int, siginfo_t *, void *);
};
An installed action stays installed until otherwise changed with sigaction()
sigset_t sa_mask
: additional signals to block while signo
is being handled with sa_handler
signo
is blocked for you while in sa_handler
int sa_flags
: handling options – some notable ones:
SA_INTERRUPT
: Don’t automatically restart slow system call (default, there may not be a flag)SA_RESTART
: Automatically restart slow system callSA_RESETHAND
: Reset disposition of signo
to SIG_DFL
APUE Figure 10.18 reimplements signal()
via sigaction()
with reasonable (and portable) semantics:
#include "apue.h"
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 {
act.sa_flags |= SA_RESTART;
}
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
APUE Figure 10.19 implements a portable signal()
that never restarts slow system calls:
#include "apue.h"
Sigfunc *signal_intr(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT;
#endif
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
Signal mask: set of signals currently blocked from delivery
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set,
sigset_t *restrict oset);
// how: SIG_BLOCK, SIG_UNBLOCK, or SIG_SETMASK
// Returns: 0 if OK, -1 on error
int sigpending(sigset_t *set);
// Returns: 0 if OK, -1 on error
sigprocmask()
: manipulate process’s signal mask
sigpending()
: retrieve a set of pending signals that are blocked from delivery
Last updated: 2024-02-04