Download as pdf or txt
Download as pdf or txt
You are on page 1of 10

2/4/2017 www.cs.rochester.

edu/courses/252/spring2014/notes/08_exceptions

Lecture notes for CSC 252, Thur. Mar. 27 ff, 2014 

Announcements 
    A6 will be assigned next Tuesday 
        trivia will be due a week from today 
        main assignment will be due the night of Monday April 14 

    Read chapter 8.  Skim chapter 7.  I'm not sure how much of the 
    latter I'll be able to cover in class. 

======================================== 
Exceptions 

In computer architecture, an exception is a condition that causes an 
abrupt transfer of control to the operating system.  Three categories: 

    interrupt   ‐‐ caused by I/O device (external to the processor) 
    trap        ‐‐ requested by the program (syscall) 
    fault       ‐‐ accidentally caused by the program (divide by zero, 
        access to invalid address, illegal instruction, etc.) 

    (NB: The book distinguishes between faults, which may return, and 
    aborts, which cannot.  Power failure is definitely an abort.  Some 
    user errors may be.  Most authors don't make this fine a 
    distinction.  Also: exact use of the terms "interrupt", "exception", 
    "trap", and "fault" varies from author to author and machine to 
    machine.) 

(In programming languages, an exception is an abrupt, potentially 
multi‐level return from a nested procedure ‐‐ more on this related but 
different use of the term later ‐‐ and much more in CSC 254.) 

Remember that processors don't "work" the way people do.  They execute 
madly along at a steady, furious rate, never slowing, stopping, or 
"noticing" whether they're executing user programs, the kernel, or the 
idle loop ‐‐ or for that matter processing an interrupt.  Seen in this 
light, interrupts are a "normal" event.  The processor's instruction 
fetch unit *polls* for interrupts.  If something unusual is happening in 
the machine, it inserts an interrupt pseudo‐instruction into the 
pipeline instead of the normal instruction. 

Worst case we have a bogus instruction in the exception handler for 
bogus instructions and we spend all our time looping through it. 

Kernel (privileged) v. user (non‐privileged) mode. 
Stuff you can do (only) in kernel mode: 
    change mappings from virtual to physical addresses 
    access I/O devices 
    mask and unmask interrupts 
    exit kernel mode 
    access various special hardware registers having to do with address 
        translation, exception handling, IEEE floating point modes, 
        performance monitoring, etc. 

Transitions from kernel to user mode are performed explicitly by the 
operating system, generally at the end of an interrupt handler or 
kernel call, using a privileged RFE (return from exception) instruction. 

The processor starts up in kernel mode with address translation turned 
off, running the bootstrap loader from ROM.  The bootstrap loader finds 
attached hard drives and copies of the OS on them; picks one, and loads it 
(still running in kernel mode).  The OS then initializes and turns on 
address translation, starts the first user‐level programs (including a 
shell) and switches to user mode. 

Exceptions ideally occur "between" instructions, though some machines 
don't always provide a clean break.  An exception is said to be 
*precise* if when the HW traps into the OS every instruction before the 
exception has completed and no instruction after the exception has had 
http://www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions 1/10
2/4/2017 www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions

any noticeable effect.  An exception is said to be *restartable* if the 
HW provides the OS with enough information to tell which instructions 
have completed, to complete those that haven't completed (if any), and 
to get the pipeline going again in user mode.  An exception can be 
restartable w/out being precise. 

what does the hardware do when an exception occurs? 
    For faults and aborts, squash the current instruction. 
    squash any subsequent instructions that have started down 
        the pipeline(s) 
    disable further exceptions 
    switch to kernel mode.  This induces 
        a change of address space (see below) 
        a change of stack pointer (%esp) 
    push the address at which execution can resume onto the (kernel) stack 
    push other processor state, including the condition codes 
        CISC machines generally put interrupt state onto the kernel stack. 
        RISC machines tend to put this state in special registers instead. 
    jump to a pre‐defined address 

        On the x86 there is a table in the kernel address space that 
        lists the address of the exception‐handling routines, one for 
        each class of exception.  The base address of the table is in a 
        special hardware register (readable/writable only in kernel 
        mode). 

        An alternative approach, taken on many RISC machines, is to have 
        a single exception handler address.  The class of exception is 
        put into another special register, and the OS arranges for the 
        code of the handler to consist of a switch statement that uses 
        the contents of that register as argument. 

    All of this happens atomically. 

The return‐from‐exception instruction 
    restores processor state from the (kernel) stack 
    pops the return address into a temporary special register 
    returns to user mode (this includes switching %esp and the memory map) 
    puts the return address back into the PC 
    continues 

    Note that an exception handler can "return" to a different place and 
    even a different address space by changing the return address and 
    other state on the kernel stack before executing the RFE 
    instruction.  This is how context switches happen. 

Under Windows 9X/ME or MacOS <=9, there was a single address space that 
included all user processes and almost all of the operating system. 
Operating system calls were ordinary subroutine calls. 

In a "real" operating system (Linux, Windows 2000/XP/Vista/7/8, MacOS X), 
user‐level processes all run in different address spaces, so they can't 
hurt each other, and a big chunk of the operating system (the *kernel*, 
which includes everything required to implement protection) runs in 
kernel mode. 

Syscall (trap) instructions are littered through /usr/lib/clib.a.  User 
programs don't generally execute them in‐line.  The stuff in section 2 
of the Unix manual consists of routines that package stuff up, maybe do 
some error checking, execute a syscall instruction, and then re‐package 
results from the kernel for return to the user program.  Communication 
of parameters to and from the kernel is usually done in ISA registers on 
a RISC machine, and on the (user) stack in a CISC machine. 

Many OSes choose to make the kernel‐mode address map a superset of the 
user‐mode address map: it includes the kernel and the most 
recently‐running user program.  This makes it easy on a CISC machine to 
access parameters on the user stack.  If the kernel wants to run a 
different user‐level program it switches address maps while in kernel 

http://www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions 2/10
2/4/2017 www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions

mode, making a different user part of the address space visible, but not 
interfering with the kernel‐mode part of the space, which overlaps in all 
address maps.  Privileged instructions also allow the kernel to read and 
write locations in an arbitrary address space.  This is slow, but useful 
if the current user's space is not in the kernel's map, or if the kernel 
needs to, say, copy information from one user space to another. 

More details about the x86 

Up to 256 exception classes (slots in the exception jump table) 
0‐31 are hardware defined, and the same on all x86 systems. 

    0   divide by zero or divide overflow 
    13  general protection fault ‐‐ attempt to use invalid address 
    14  page fault ‐‐ attempt to use address not currently in RAM 
    18  machine check ‐‐ hardware error (abort: not recoverable) 

32‐255 are OS‐specific.  Most are associated with I/O devices.  By 
convention (not enforced by HW) 128 is traditionally used for system 
calls.  The OS will expect to find the syscall number and parameters on 
the user‐level stack. 

======================================== 
Processes 

A process is "an instance of a program in execution". 
Includes 
    address map 
    contents of physical memory in that address map 
    contents of registers, including stack pointers (kernel and user) and PC 
    environment variables (usually in memory) 
    open file/socket descriptors 
    user & group ids 

Context switches 
    expensive due to both inherent latency and loss of cache/TLB footprint 
multiprogramming v. multiprocessing 
time‐slicing (preemptive multiprogramming) 

Private address space 

Process creation 
    fork 
        duplicate (*not* shared) address space 
        shared open file descriptors (subsequent opens not shared) 

        pid_t fork(void) 

        returns 0 to child; child id to parent 
        (that's how they tell themselves apart) 

        idiom: 

            pid_t child; 
            if (!(child = fork())) { 
                execve(...); 
            } 

        anachronism; wasteful of resources. 
        modern Unixes minimize cost via copy‐on‐write, but fork is still 
        expensive.  Other OSes (e.g. Windows) are better designed in 
        this regard (though not others :‐) 

    getpid 

        pid_t getpid(void) 
        pid_t getppid(void)         // parent 

error handling 

http://www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions 3/10
2/4/2017 www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions

    if ((status = syscall(args)) < 0) { 
        fprintf(stderr, "program: %s\n", strerror(status)); 
        exit(‐1); 
    } 

    Always check all return codes from syscalls.  Create a handy wrapper 
    if you want.  Book describes (in Sec. 8.3) a general approach. 
    A simpler though not quite as pretty way is 
        VERIFY(foo(args)); 
    where 

        #define VERIFY(E) \ 
            {int stat = (E); \ 
            if (stat < 0) { \ 
                int err = errno; \
                fprintf(stderr, "%s[%d] bad return(%d/%d): %s\n", \ 
                    __FILE__, __LINE__, stat, err, strerror(err)); \ 
            exit(‐1); \ 
        }} 

    This has the advantage of telling you where in your program you ran 
    into trouble. 

    In some cases you may get more precise information by using 'errno' 
    instead of the status return from your library call ‐‐ some calls 
    on some systems return ‐1 on all errors and put the more accurate 
    value somewhere else.  'errno' is a name you can use to get that 
    more accurate value.  It may be a variable or a macro, depending on 
    system.  To get the right definition #include <errno.h>. 

process states 
    running, stopped, terminated (zombie) 
    "reaping" (awaiting) 

        pid_t waitpid(pid_t pid, int *status, int options) 

        pid is the one we want to wait for, or ‐1 to indicate any child 
        status is the place to put the child's exit status 
        options can be 
            WNOHANG: don't wait; return 0 if no child has terminated yet 
            WUNTRACED: wait until terminated *or* stopped 
        status return encodes various info 
            WIFEXITED(status) true if exited normally (via exit) 
            WEXITSTATUS(status) exit status if exited normally 
            WIFSIGNALED(status) true if exited due to uncaught signal 
            WTERMSIG(status) signal, if any, that caused termination 
            WIFSTOPPED(status) true if stopped (not terminated) 
            WSTOPSIG(status) signal, if any, that caused stop 
            status == ECHILD caller has no children 
            status == EINTR call interrupted by signal 

        exist several variants: wait, wait3, wait4, waitid; not discussed 
        here 

        If a process forgets to reap its terminated children, they 
        continue to consume a slot in the process table (though not 
        other resources).  When the forgetful parent itself terminates, 
        the kernel transfers ownership to "init", which runs in the 
        background and periodically calls wait(). 

    sleep 

        unsigned int sleep (unsigned int secs) 

        return is number of seconds actually slept (may be low if 
        interrupted by signal) 

        int pause(void)     // wait for signal; returns ‐1 

http://www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions 4/10
2/4/2017 www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions

    exit 
         
        void exit(int status)       // does not return 
        // by convention, 0 is normal exit; negative numbers are errors 

process loading 
    execve 
        (one of several variants of exec) 

        int execve(const char *filename, char *const argv[], char *const envp[]) 
            // doesn't normally return; returns ‐1 on error 

    start‐up arguments 
        argc 
        argv 
            argv[0] is program name (by convention) 
            argv[argc] is null; one typically writes 
                for (int i = 0; i < argc; i++) { 
                    // do something with argv[i] 
                } 
        envp 
            no analogue of argc 
            pointers point to NAME=value pairs 
            last pointer is null 

        argv and envp strings are placed at bottom of stack by start‐up 
        code.  That code calls main as a subroutine, and calls exit 
        after main returns. 

        char *getenv(const char *name) 
            // returns 0 if not found 
        int setenv(const char *name, const char *newvalue, int overwrite) 
            // if not overwrite, return ‐1 if found 
        void unsetenv(const char *name) 

    startup stack contents: 

                    bottom 

        1   null‐terminated environment strings 
        2   null‐terminated argument strings 
        3   0 
        4   last environment pointer 
            ... 
        5   first environment pointer 
        6   0 
        7   last argument pointer 
            ... 
        8   first argument pointer
        9   < stuff used by dynamic linker > 
       10   envp (points to 5) 
       11   argv (points to 8) 
       12   argc 
       13   < stack frame for main > 

                    top 

process groups 
    processes organized into groups 

    A process group represents a bunch of processes working together, 
    e.g. in a series of pipes. 
    Process groups matter for signal handling (coming up below). 

        pid_t getpgrp(void)         // process group id 
        pid_t getpgid(pid_t pid)    // somebody else's process group id 
                                    // (mine if pid == 0) 

http://www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions 5/10
2/4/2017 www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions

        int setpgid(pid_t pid, pid_t pgid) 
            // change process group id of process pid to pgid 
            // change my process group id if pid == 0 
            // (there are restrictions on who is allowed to do this) 

    Process groups organized into "login sessions".  Each login session 
    has a "controlling tty".  One process of the group is in the 
    "foreground"; the others are in the background.  Foreground processes 
    can access the terminal (stdin, out, err).  Background processes get a 
    signal if they try to access the terminal. 

job control 
    each terminal (xterm, ssh connection, etc.) has an associated 
    process group.  If you type ^C, ^Z, or ^Y from the terminal, every 
    process in that process group gets the SIGINT or SIGSTP. 
    (^Y is delayed suspend ‐‐ takes affect only when job tries to read 
    from stdin)  

    The standard Unix shell manages processes by putting its children in 
    different process groups, and then assigning the terminal to the 
    group it wants to be in the "foreground".  (By definition, the 
    foreground process group is the one that owns the terminal.  Others 
    are background process groups.) 

    (You might be tempted to leave/put the shell and the foreground job 
    in the same process group, but this will cause trouble later if you 
    want to move the job to the background: you'll want to give it a new 
    process group, but it may have spawned a whole process tree under it, 
    and you can't easily change all the processes in the tree at once.) 

    The kernel sends a SIGTTIN signal to (all processes of the process 
    group of) a background process that tries to read form the terminal. 
    It optionally sends a signal to a background group that tries to 
    write to the terminal; this is controlled by the tostop termio (stty) 
    setting, normally off by default. 

    *** In the shell assignment as defined by the authors, you were not 
        required to change ownership of the terminal.  (The CMU reference 
        solution does not.)  That meant your processes wouldn't be able to 
        read from the terminal.  You were to leave the terminal attached 
        to the shell, which would catch SIGINT and SIGSTP, and forward 
        them to the process group it is *pretending* is in the foreground. 
        We're requiring you to fix this this year.  We'll be fixing the 
        reference version, so they'll match. 

    The book is confusing in its discussion of this mechanism (p. 740). 
    It suggests that "real" shells work the way the one in the CMU version 
    of the assignment does, but this isn't really the case. 

To assign a terminal to a process group (i.e., make it the foreground 
process group): 

    #include <unistd.h> 

    int status = tcsetpgrp(0, child_gpid); 
    // results in SIGTTOU if caller is in the background 

You can also query the foreground process group: 

    foreground_gpid = tcgetpgrp(0); 

pthreads 
    processes v. kernel threads v. user‐level threads 

======================================== 
Signals 

Essentially user‐level software interrupts. 
Used to induce asynchronous control transfer. 

http://www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions 6/10
2/4/2017 www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions

All modern OSes have something like signals.  We'll focus on the Unix 
variety, but the underlying concepts carry over to Windows and other 
OSes. 

About 30 different signal values in most Unix variants, including Linux. 
Some particularly important examples: 

    SIGSEGV     use of invalid address 
    SIGBUS      misaligned address
    SIGILL      illegal instruction 

    SIGINT      ^C 
    SIGTSTP     ^Z 
    SIGCONT     continue 
    SIGKILL     termination signal; can't be caught or ignored 

    SIGHUP      loss of terminal connection (hangup) 
    SIGIO       asynchronous I/O completion 
    SIGALRM     timer expiration (previously requested with alarm()) 
    SIGTTIN     attempt to read from terminal from background 
    SIGTTOU     attempt to write to terminal from background 
    SIGWINCH    change in GUI window size 
    SIGCHILD    termination or stop of child 

    book gives whole Linux list in Figure 8.25 (p. 737) 
    or try 'man 7 signal' 

NB: kill program and kill() function do NOT necessarily kill a process; 
they just send it a signal.  The name is unfortunate. 

Signals are *delivered* by the kernel in response to various events, 
such as those listed above.  They are *received* by the process when 
unblocked (unmasked).  A delivered but unreceived signal is said to be 
*pending*. 

Like hardware exceptions, signals 
    are received one at a time 
    can be blocked (masked) 
    are held for later receipt when masked 
    but are not queued (are lost if delivered when already pending) 

Signals can be delivered to individual processes or to all processes of 
a given process group (or, in the case of loss of terminal connection, 
all processes of all process groups of a given login session). 

Typically a process group id is the same as the process id of one of its 
members. 

    unix> kill ‐9 12345 
        send signal 9 (SIGKILL) to process 12345 

    unix> kill ‐9 ‐12345 
        send signal 9 to all processes in process group 12345 

There are of course rules on who can send what kinds of signals to whom. 
See the man pages. 

A process can wait for a signal with pause().  It can install a 
*handler* function for a given signal (or arrange to ignore certain 
signals) with signal() or sigaction().  The former is simpler but 
inconsistent across Unix versions; the latter is more portable. 
To use these, include <signal.h>. 

    #include <signal.h> 
    typedef void sighandler_t(int)
    sighandler_t *signal(int signum, sighandler_t *handler) 

handler can be 

http://www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions 7/10
2/4/2017 www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions

    SIG_IGN     ignore signal (assuming this is allowed) 
    SIG_DFL     restore default behavior 
    function address    install handler 
previous value is returned (or SIG_ERR on error) 

Handler will be called asynchronously when the signal is received 
(as soon as it has been delivered and is not blocked).  The single 
argument to the handler is the signal number.  (This allows the same 
handler to be used for different signals.) 

When the handler returns the program will usually continue with the 
instruction after the one it executed immediately before receiving the 
signal.  Exception: if blocked in the kernel for a "long" system call in 
some variants of Unix (e.g. Solaris), the syscall will return with an 
EINTR error rather than continuing/restarting automatically (as it does 
in Linux). 

Simple example. 
If you type ^C at most programs they will terminate. 
But they can arrange not to: 

    #include <signal.h> 

    void my_handler(int sig) { 
        printf("caught signal %d\n", sig); 
    } 

    int main() { 
        if (signal(SIGINT, my_handler) == SIG_ERR) 
            unix_error("signal error'); 
            // note possibility that old handler address might appear 
            // negative; VERIFY macro above can't be used 
        while (1) { 
            pause(); 
            printf("continuing\n"); 
        } 
    } 

You can kill this with SIGKILL (and various other things) but not SIGINT. 

The book has several more examples.  For the current assignment you will 
have to do a lot of signal handling, to notice when children terminate or 
stop, so you can reap them, or inform the user that they have stopped. 
(In the original CMU version of the assignment, you would also use 
signals to catch keyboard events [^C, ^Y ^Z] and "pass them on" to the 
appropriate child process group.  This year we're doing job control 
right, so you don't have to pass them on, but you do have to ignore ^Z 
and ^Y, and kill current input line & reprompt on ^C.) 

‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 
Problem: signal() has inconsistent semantics across Unix variants: 
  ‐ are slow syscalls automatically restarted when interrupted by a signal? 
    (OW calls interrupted this way return EINTR) 
  ‐ are signals blocked during handler? 
  ‐ is handler still established after it returns, as opposed to having to 
    be reestablished explicitly? 
  ‐ is a blocked signal held for later delivery? 

The sigaction() routine is newer and move verbose.  It allows all these 
things to be explicitly controlled.  Figure 8.34 (p. 752) in the book 
gives a Signal() wrapper for signal() that answers YES to all four of the 
above questions. 

Blocking signals: 

 . int sigemptyset(sigset_t *set);
        initializes a sigset_t mask for use in subsequent functions. 
 . int sigaddset(sigset_t *set, int signum); 
        adds a signal to the list of ones you care about in a mask. 

http://www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions 8/10
2/4/2017 www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions

 . int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 
        'how' can be SIG_BLOCK, SIG_UNBLOCK, or SIG_SETMASK. 
        Lets you say that you are ‐‐ or are not ‐‐ interested in  
        signals of a particular type at the moment.  SIG_SETMASK sets the 
        blocked signals to precisely the specified set. 

    If you add a signal to the ignore set (via calling sigprocmask with 
    SIG_BLOCK and a mask containing the signal), then even if you have a 
    signal handler registered for that signal, it won't be called. 

Warnings: 

  ‐ Blocked signals are delivered but kept pending.  If more than 
    one is delivered, it is LOST.  So you can't count signals; you can 
    only tell that *at least one* has occurred.  So, for example, if you 
    set up a handler for SIGCHLD, it has to reap _all_ available 
    children in a loop. 

  ‐ Signal handlers should be short and sweet. Acquiring a lock while 
    inside a signal handler can lead to deadlock ‐‐ BAD! 

  ‐ Granularity of response to SIGALRM: if I have to wait for my process 
    to be rescheduled, and quanta on my machine are 10000 uSecs, then 
    setting an alarm for 250 uSecs is pointless if I have to wait through 
    multiple quanta to be rescheduled. 

Defaults: 

    Absent a call to signal() or sigaction(), every signal has a *default* 
    action.  Some signals (e.g. SIGCHILD and SIGWINCH) are ignored by 
    default.  Others (e.g. SIGSEGV and SIGHUP) cause the kernel to 
    terminate the process, in some cases (e.g. SIGSEGV but not SIGHUP) 
    dumping a "core" file for post‐mortem analysis by a debugger.  A few 
    (e.g. SIGTTIN) cause the process to stop until they get a subsequent 
    SIGCONT signal.  One (SIGKILL) cannot be handled or ignored; it always 
    terminates the process. 

[ Comparison between MS Windows Events and Unix Signals: 
[  . Signals: only 2 user‐defined signals; thousands of user‐defined events 
[  . Windows events allow bidirectional communication 
[     . PostMessage() and SendMessage() return a DWORD (unsigned long) that  
[       can hold any value you want to define as the response. Such as the 
[       number of times the user has typed 'Q' into a buffer. 
[     . Signals return only 0 or ‐1 to indicate whether they were delivered 
[  . Windows Events cannot be blocked (but they can be ignored) 
[  . Windows events are queued ‐‐ can have more than one of the same type 
[    pending; can get more of same type while processing one 

======================================== 
Non‐local jumps 
"Poor man's exceptions" ‐‐ the C approximation of what you get in C++, 
Java, ML, Ada, Common Lisp, ... 

    #include <setjmp.h> 

    int setjmp(jmp_buf jb); 
    sigsetjmp(sigjmp_buf jb, int savesigs); 
        returns once, maybe twice 

    void longjmp(jmp_buf jb, int retval); 
    void siglongjmp(sigjmp_buf jb, int retval); 
        doesn't return; causes setjmp to return again 

Setjmp sets you up to "catch" a longjmp: 

    jmp_buf jb; 

    if (err = setjmp(jb) == 0) { 
        // do what you normally want to do 

http://www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions 9/10
2/4/2017 www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions

        foo(); 
    } else { 
        // handle error, using code passed back through err 
    } 

    ... 

    foo() { 
        ... 
        longjmp(jb, err); 
    } 

Warnings: 

(1) setjmp is usually not a normal function.  It's typically implemented 
    as a macro or special‐cased by the compiler.  Note in particular that 
    jmp_buf is used to store state, but is not passed by reference. 

(2) Upon an "abnormal" return from a setjmp, global and static variables 
    will have whatever value they had at the time the longjmp occurred, 
    but the value of local variables is *undefined*, unless they have 
    been declared volatile.  This is not nearly as nice as the 
    guarantees in C++, Java, etc. 

setjmp()/longjmp() vs. language‐level exceptions: 
  ‐ no unwinding of stack ‐‐ direct restoration of former state 
  ‐ destructors of objects in stack not called 
  ‐ easy to leak memory; side effects not undone automatically 
  ‐ only local (stack‐based) variables restored; globals, statics, dynamic 
    vars unchanged (because they aren't stack based) 
  ‐ somewhat faster return (from not having to unwind the stack as much) 

Like goto, setjmp()/longjmp() should only be used when they improve the  
readability of the program or the performance gain is critical. 

sigsetjmp and siglongjmp are interoperable with setjmp and longjmp. 
The difference is that sigsetjmp saves the signal state of the process 
(what is masked, what has handlers, what is ignored, etc.) in addition 
to the stack state, and siglongjmp restores this state. 

http://www.cs.rochester.edu/courses/252/spring2014/notes/08_exceptions 10/10

You might also like