Slide Set 13

Download Report

Transcript Slide Set 13

Signals
Operating
Systems
What is a Signal?
A signal is a notification that some event has occurred.
Usually a signal is sent to a process asynchronously and
whatever the process is doing is interrupted.
Signals have been around since the early days of Unix, but
early versions were not reliable, signals could get lost. Both
4.3BSD and SVR3 made revisions to their signal code to make
signals reliable, but the changes were incompatible. Posix.1
standardized the reliable signal routines.
Operating
Systems
The Life Cycle of a Signal
A signal is born when an event occurs that
generates the signal.
A signal ceases to exist when it is delivered, which
means that some action specified for the signal has
been taken.
Between generation and delivery a signal is said to
be pending.
Operating
Systems
Conditions that generate signals
Most signals can be generated naturally
for example, by a divide by zero error
or a segmentation fault
All signals can be generated synthetically by one
of the following system calls:
kill
killpg
pthread_kill
raise
sigqueue
Operating
Systems
Conditions that generate signals
Five signals cannot be generated naturally
SIGKILL
SIGSTOP
SIGTERM
SIGUSR1
SIGUSR2
terminate immediately
pause
requests termination
Signal names are defined in signal.h
Operating
Systems
Conditions that generate signals
Terminal generated signals:
occur when users type certain keys on the keyboard. For
example, Ctrl-C normally generates the signal SIGINT.
Hardware exceptions:
conditions detected by hardware, like divide by zero, are sent
to the kernel. The kernel generates the appropriate signal.
The kill( ) function or the kill command:
The kill function allows a process to send any signal to another
process or process group, as long as it owns that process.
Software Conditions:
Some software conditions can cause signals to be generated.
For example, SIGURG is generated when out of band data
arrives over a network connection.
Operating
Systems
A signal is a classic example of an asynchronous event.
They can occur at random times as far as the process is
concerned. A process must tell the kernel what to do
if and when a signal is received. A process can:
* Catch the signal and ignore it
- sigkill and sigstop cannot be ignored
- ignored signals have no effect on the process
* Catch the signal and do something with it
* Let the default apply
- usually terminates the process
Operating
Systems
When you get a signal from a hardware detected error
condition, you cannot ignore the signal. Your program
will likely not get safely past the condition.
Such signals should be handled right away and the
program terminated with a call to exit( )
More on this later . . .
Operating
Systems
Terminology
A signal is a software notification of an event.
A signal is generated when the event that causes the signal occurs.
A signal is delivered when the process catches the signal and takes
some action on it. A signal that has not been delivered is pending.
A signal handler is a function that the programmer writes to take
some specific action when a given signal occurs.
A program installs a signal handler by calling sigaction( ). A signal
handler may choose to ignore a signal, in which case it is thrown
away.
A process can use a signal mask to block a set of signals. A
blocked signal waits in a pending mode until the process unblocks
the signal, at which time it is delivered to the process.
Some Common Signal Names
Operating
Systems
This set is required by Posix.1
Symbol
Meaning
SIGABRT
SIGALRM
SIGFPE
SIGHUP
SIGILL
SIGINT
SIGKILL
SIGPIPE
SIGQUIT
SIGSEGV
SIGTERM
SIGUSR1
SIGUSR2
abnormal termination, initiated by abort
timeout signal, initiated by alarm
arithmetic error, such as divide by zero
hang up on controlling terminal
invalid hardware instruction
interactive attention signal (Ctrl-C)
terminate ( cannot be caught or ignored )
write on a pipe with no readers
interactive termination
invalid memory reference
termination
user defined
default action for all
user defined
of these signals is to terminate
the process.
Job Control Signals
Operating
Systems
Symbol
Meaning
SIGCHLD
SIGCONT
SIGSTOP
SIGSTP
SIGTTIN
SIGTTOU
child process has stopped
ignore
continue if stopped
continue process
stop signal (can’t be caught/ignored) stop process
interactive stop signal
stop process
background process trys to read
stop process
background process trys to write stop process
See all of the signals the
os supports by typing
kill -l
Default Action
See the key sequences that generate
signals on your system by typing
stty -a
Operating
Systems
What to do with a Signal
Ignore the signal. Most signals can be ignored, but
SIGKILL and SIGSTOP cannot.
Catch the signal by telling the kernel to call a function
provided by the programmer, when a particular signal
is generated.
Let the default action apply. The default for most signals
is to terminate the process.
A really polished program will . . .
Block all signals as soon as your program begins.
Set all keyboard generated signals you don’t want to
handle to be ignored.
Catch SIGTERM and arrange to clean everything up
and terminate when it arrives. This is the standard way
that a sys admin will shut down processes.
Catch all error generated signals and arrange to log them,
print an appropriate error message, do any necessary
clean-up, and terminate.
Generating Signals
from the command line
A user can only kill a process that he or she owns.
Generate a signal with the kill command. From the shell
kill –s signal pid
This is one of the signal names less the “sig” prefix
Example:
kill –s USR1 3423
Generating Signals
from within a program
#include <sys/types.h>
#include <signal.h>
int kill (pid_t pid, int sig );
returns 0 if successful
if pid is positive, kill sends the signal sig to the process pid (must own the process)
if pid is negative, kill sends the signal to the process group |pid|
if pid is zero, kill sends the signal to members of the callers process group
Killing a Parent Process
Sounds grim, but …..
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
if (kill(getppid( ) , SIGTERM) == -1)
perror(“Error in kill command”);
Generating Signals (cont)
The function
alarm ( unsigned int num )
will generate a SIGALRM signal after num seconds have
elapsed. The signal is sent to the calling process.
A process needs permission to send a signal to another
process. The superuser can send a signal to any process. For
others the basic rule is that the real or effective user id of
the sender has to be the same as the real or effective user id
of the receiver, i.e. you can only send signals to processes that
you own...
The Signal set
A process can temporarily prevent a signal from being delivered
by blocking it. Blocked *signals do not affect the behavior of the
process until they are delivered. The signal mask contains the
set of all of the signals that are currently blocked. The following
functions initialize and modify a signal set.
use either of these
functions to initialize
a signal mask before
using it.
int sigemptyset(sigset_t *set); contains no signals
int sigfillset(sigset_t *set); contains all signals
int sigaddset(sigset_t *set, int sig); adds a signal to the signalset
int sigdelset(sigset_t *set, int sig); deletes a signal from the signalset
int sigismember(const sigset_t *set, int sig);
This function returns 1 if sig is a
member of the set. Otherwise
it returns 0.
* note that blocking a signal
is different from ignoring
a signal.
Example Code
this code initializes the signal set twosigs so that it contains
just the two signals SIGINT and SIGQUIT.
#include <signal.h>
sigset_t twosigs;
…
create a signal set
sigemptyset (&twosigs); make the signal set empty
sigaddset (&twosigs, SIGINT); add sigint
sigaddset (&twosigs, SIGQUIT); add sigquit
sigprocmask ( )
Installing a signal set
SIG_BLOCK:
SIG_UNBLOCK:
SIG_SETMASK:
add this set to the signals currently blocked
delete this set from the signals currently blocked
set the signal mask to this set
#include <signal.h>
int sigprocmask (int how, const sigset_t *set, sigset_t *oldset);
the set of signals to be
used for the modification.
If this value is NULL, then the
function simply retrieves the
current signal mask and stores
it in oldset. No change is made to
the current signal set.
the function returns the
current set of signals being
blocked in oldset. This allows
the program to restore the
old signal set. If this value
is NULL, then no value is
returned.
Example code
this code adds SIGINT to the set of signals that the
process currently has blocked.
#include <signal.h>
initialize the mask set
and then set SIGINT
add to the signal mask
using the signal set
we just created
clears the SIGINT
from the signal mask
sigset_t mymask;
…
The old set
is not saved.
sigemptyset(&mymask);
sigaddset(&mymask, SIGINT);
sigprocmask(SIG_BLOCK, &mymask, NULL);
… //if a ctrl-C occurs here, it will be blocked.
sigprocmask(SIG_UNBLOCK, &mymask, NULL);
…
See ex1 on X-Server.
This code will block Ctrl-C then do some useless work. If
a Ctrl-C comes in during this time, the process does not
see the signal. When the process unblocks it, the signal
is delivered.
Handling Signals
Remember that blocking a signal only means that
the signal will not be delivered until the signal is
unblocked. It is a way of temporarily protecting
your code from a signal. Once the signal is unblocked
it will be delivered to the process. Then it must be
handled in some way. In most cases, an unhandled
signal will result in the process terminating.
Handling Signals
There are two issues to think about:
What do you need to do inside of the signal handler
to change the state of the application so it is known
that the signal occurred?
Where do you go after handling the signal. Choices are:
return to where the application was when it was interrupted
terminate the program
do a global jump to another part of the program
Note that when you in the signal handler, you are
limited to making system calls that are considered
to be asynch-signal-safe. These system calls are
guaranteed to be re-entrant. POSIX defines the
system calls that are asynch-signal-safe.
The Signal System Call
This call was defined by ANSI C, but has been deprecated.
It does not work well in multi-process environments. However,
it is a simple call and illustrates signal handling effectively.
#include <signal.h>
void signal ( int signum, void (*act) (int) )
this is just a pointer to a function
that takes an integer parameter
and returns a void.
Example
from Molay chapter 6
#include <stdio.h>
#include <signal.h>
void f(int);
The signal handler
int main ( )
{
int i;
signal ( SIGINT, f);
for (i = 0; i < 10; i++)
{
printf(“hello\n”);
sleep(1);
}
return 0;
}
void f ( int signum )
{
printf(“\nOUCH”);
}
int main ( )
{
int i;
signal ( SIGINT, f); install the signal handler
for (i = 0; i < 10; i++)
{
printf(“hello\n”);
sleep(1);
}
return 0;
}
void f ( int signum )
{
printf(“\nOUCH”);
}
int main ( )
{
int i;
signal ( SIGINT, f);
for (i = 0; i < 10; i++)
{
printf(“hello\n”);
sleep(1);
}
return 0;
}
look at the code
hello
hello
hello
ouch
void f ( int signum )
{
printf(“\nOUCH”);
}
Signal Handlers
#include <signal.h>
May be null
int sigaction (int sig, const struct sigaction *act, struct sigaction *old);
the signal to
be caught
struct sigaction
{
void (*sa_handler) ( );
sigset_t sa_mask;
int sa_flags;
}
the signal handling
information to be set
for handling this signal.
reset to default
the function returns the current
signal handling information in
this structure. If NULL, nothing
is returned.
ignore
// SIG_DFL, SIG_IGN, or pointer to function
// additional signals to be blocked while in handler
// flags and options
eliminates race conditions
SA_RESTART
Restart any system calls interrupted by this signal once the signal handler has finished
A signal handler is an ordinary function that takes a
single integer parameter and returns a void. The
operating system sets the parameter to the signal
that was delivered to the process. Most signal handlers
ignore this parameter, although it is possible to write a
signal handler that handles several different signals.
Example
The following code segment sets up a signal
handler that catches ctrl-C.
…
char handlermsg[ ] = “User hit ctrl-c!\n”;
this is the actual signal handler. It prints a message.
void catch_ctrl_c (int signo)
{
write(STDERR_FILENO, handlermsg, strlen(handlermsg));
}
note that write is signal safe ... printf is not!
…
struct sigaction act;
load the address of the handler
act.sa_handler = catch_ctrl_c;
don’t worry about any other signals
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
no flags
if (sigaction(SIGINT, &act, NULL) < 0)
{
// signal handler is installed
…
Example
The following code segment sets up a signal
handler that ignores SIGINT if it is using the
Default handler for this signal.
#include <signal.h>
#include <stdio.h>
struct sigaction act;
This just gets the current
signal handler in act
…
if (sigaction(SIGINT, NULL, &act) == -1)
perror(“Could not get the old signal handler for SIGINT”);
else if (act.sa_handler == SIG_DFL)
This sets the new
{
signal handler
act.sa_handler = SIG_IGN;
if (sigaction(SIGINT, &act, NULL) == -1)
perror (“Could not ignore SIGINT”);
}
See handler.c example
This code uses a shared variable as a flag.
The program computes in an endless loop
until the flag gets set. The flag gets set by
the signal handler for SIGINT.
Waiting for signals
One of the primary uses of signals is to keep programs from
burning up cpu time while waiting for some event. Instead of
running in a tight loop and checking to see if a signal has been
received (polling), the program can put itself into a suspended
state until the waited-for event occurs.
#include <unistd.h>
int pause ( );
the return value
is not significant.
pause suspends the process until
a signal that is not being ignored
is delivered to the process. If a signal
is caught by the process, the pause
returns after the signal handler returns.
pause( ) always returns -1 with errno set to EINTR
pause()
process B
process A
signal
handler
signal
A problem with Pause
…
static volatile sig_atomic_t
signal_received = 0;
…
while(signal_received == 0)
pause( );
to wait for a particular signal, we need to check
which signal caused the pause to return. This
information is not directly available, so we use
an external static variable as a flag which the
signal handler sets to 1. We can then check this
flag when pause returns.
the pause call is put into a loop
so that we can check the value
of the flag. If it is still zero, we
must call pause again.
what happens if a signal is delivered
between the test and the pause?
// go on to other stuff
…
Volatile: This value may be changed by
something outside of this scope. It should
not be optimized by the compiler.
sig_atomic_t is a data type that is guaranteed
can be read or written without being interruped.
The program does not catch it!!
the pause does not return until another
signal is received. To solve this problem,
we should test the flag while the signal is
blocked!
But … does this work?
…
sigset_t sigset;
int signum; // signum is the signal we want to catch
sigemptyset(&sigset);
sigaddset(&sigset, signum);
sigprocmask(SIG_BLOCK, &sigset, NULL);
while(signal_received == 0)
pause();
…
No! Now the signal is blocked when the
pause statement is executed, so the
program never receives the signal.
sigsuspend
The delivery of signals with pause was a major problem in Unix.
It was fixed by adding the sigsuspend operation.
int sigsuspend(const sigset_t *sigmask);
return value is
always -1
sigsuspend sets the signal mask to
the one pointed to by sigmask and
pauses the process in a single atomic
operation. When sigsuspend returns,
the signal mask is reset to the value it
had before sigsuspend was called.
Consider the following code
sigfillset(&mask);
// set mask to block all signals
sigdelset(&mask, signum); // remove signum from the set
sigsuspend(&mask);
// wait for signum
Now the program pauses. All signals except
signum have been blocked. What’s wrong?
Consider the following code
sigfillset(&mask);
// set mask to block all signals
sigdelset(&mask, signum); // remove signum from the set
sigsuspend(&mask);
// wait for signum
what happens if the signal is delivered just before
this code block is entered? The signal is handled,
and now sigsuspend puts the program in a wait
condition. If another signal is not delivered, the
program waits forever.
Correct code to wait for a signal (signum)
declare static variable to test. It gets set in the signal hander.
static volatile sig_atomic_t sigreceived = 0;
sigset_t maskall, maskmost, maskold;
int signum = SIGNUM; the signal we want to wait for.
now, all signals
are blocked.
this is the
critical
section
sigfillset(&maskall); block all signals
sigfillset(&maskmost);
blocks all signals but SIGNUM
sigdelset(&maskmost, SIGNUM);
sigprocmask(SIG_SETMASK, &maskall, &maskold);
if (sigreceived == 0) test the flag.
unblock SIGNUM and pause
as an atomic action.
sigsuspend(&maskmost);
sigprocmask(SIGSETMASK, &maskold, NULL);
after returning from the signal handler, restore the old signal mask.
Correct code to wait for a signal
... allowing other signals while waiting
for SIGUSR1
static volatile sig_atomic_t sigreceived = 0;
The flag
sigset_t masknew, maskold;
int signum = SIGUSR1; The signal I want to wait for
sigprocmask(SIG_SETMASK, NULL, &masknew); get current
sigaddset(&masknew, signum); add my signal to the current set
sigprocmask(SIG_SETMASK, &masknew, &maskold); set mask
now SIGUSR1 is blocked
sigdelset(&masknew, SIGUSR1); remove SIGUSR1 from the set
while (sigreceived == 0) if flag is ok, wait for SIGUSR1 … it is
sigsuspend(&masknew); unblocked by the sigsuspend call
restore sigprocmask(SIGSETMASK, &maskold, NULL);
System Calls and Signals
Some system calls, like terminal I/O can block the process for
a very long time.
The capability is needed to be able to interrupt these
system calls so that signals can be delivered during long
waits for I/O.
In POSIX.1, slow system calls that are interrupted
return with -1, and errno set to EINTR. The program
must handle this error explicitly and restart the system
call if desired.
Remember the sigaction struct ….
struct sigaction
{
void (*sa_handler) ( );
sigset_t sa_mask;
int sa_flags;
SA_RESTART
Restart any system calls interrupted by
this signal once the signal handler has finished
Slow system Calls
Slow System Calls are those that can block forever. These include
* reads from files that block forever if no data is present.
This includes pipes, network connections, and terminals.
* Writes to the above if the data cannot be accepted.
* opens of files that block until some condition occurs,
for example waiting to open a terminal device until
modem answers the phone.
* pause
* some interprocess communication functions
The following code segment restarts the read system call
if interrupted by a signal.
#include <sys/types>
#include <sys/uio.h>
#include <unistd.h>
#include <errno.h>
note the use of the comma operator:
the left-hand expression is evaluated first
then the second expression is evaluated
and the result used as the condition for the
while loop.
that is, the read occurs. Then the
return value is tested. If it failed
because of an interrupt, the read
is re-started.
int fd, retval, size;
char *buf;
...
while (retval = read(fd, buf, size), retval == -1 && errno == EINTR);
if (retval == -1)
{
// handle errors here …
}
siglongjump and
sigsetjump
see non-local exits in the GNU C library manual
Used to unwind the call stack
sigsetjump is like a statement label
siglongjump is like a goto … it branches to
the label set by sigsetjump
It is not recommended that you use these
Timers
Terminology
A timer keeps track of the passage of time
Calendar time is a point in the time continuum,
for example, October 5, 2001 at 12:30pm. The
Unix time continuum (epoch) begins on January 1, 1970
An interval is a contiguous part of the time continuum,
between two calendar times.
Interval timers generate an interrupt after a specific
time interval.
An elapsed time is the length of an interval, for example,
30 minutes.
An amount of time is a sum of elapsed times.
CPU time is like calendar time, except that it is based
on the subset of the time continuum when a particular
process is actively using a CPU. CPU time is, therefore,
relative to a process
Interval Timers
Each process has three independent interval timers available:
* A real-time timer that counts elapsed time in real time.
This timer sends a SIGALRM signal to the process
when it expires.
* A virtual timer that counts processor time used by the process.
It only runs when the process is running. This timer sends a
SIGVTALRM signal to the process when it expires.
* A profiling timer that counts both time used by the process
and processor time spent in system calls on behalf of the
process. This timer sends a SIGPROF signal to the process
when it expires. The interval timer mechanism does not
have the fine granularity necessary for profiling native code
setitimer
#include <sys/time.h>
int setitimer (int which, struct itimerval *new, struct itimerval *old) ;
ITIMER_REAL
ITIMER_VIRTUAL
ITIMER_PROF
This is the period between successive
timer interrupts. If zero, the alarm
will only be sent once.
struct timeval it_value;
struct timeval it_interval;
This is the period between now and the first
timer interrupt. If zero, the alarm is disabled.
Comprehensive Example
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>
// define the signal handler to write an asterisk
char astbuf[ ] = “*”;
void astWriter(int s)
{
write(2, astbuf, sizeof(char));
}
note that we use the kernel write call because printf is not
asynch-signal-safe.
void init_time_interrupt(void)
{
struct sigaction newact;
newact.sa_handler = astWriter;
newact.sa.flags = 0;
sigemptyset(&newact.sa_mask);
sigaction(SIGPROF, &newact, NULL);
}
Signal handler is installed …
SIGPROF is the signal generated by
the profiling timer.
value
it_interval
tv_sec
tv_usec
2 secs before
1st interrupt
2 secs between
interrupts
it_value
void setup_interval_timer(void)
{
tv_sec
struct itimerval value;
tv_usec
value.it_interval.tv_sec = 2;
value.it_interval.tv_usec = 0;
value.it_value = value.it_interval;
setitimer(ITIMER_PROF, &value, NULL);
}
int main (int argc, char *argv[ ])
{
init_time_interrupt();
setup_interval_timer();
// some useless code
for ( ; ; )
{
}
exit(0);
}