Transcript lesson11
Controlling concurrency
A look at some techniques for
process synchronization in the
Linux environmemt
What is a ‘race condition’ ?
• Without any ‘synchronization’ mechanism,
multiprogramming is vulnerable to ‘races’
in which programs produce unpredictable
and erroneous results, due to the relative
timing of instruction-execution in separate
threads or processes
• An example program demonstrates this
phenomenon (see our ‘racedemo.cpp’)
Two tasks write to one terminal
• A parent-process forks a child-process,
and both write messages to the screen,
but without coordinating their efforts
• The operating system’s task-scheduler
repeatedly preempts each of them
• The result is incomprehensible gibberish!
• Programs like this one are said to contain
a ‘race condition’
The cure is communication
• What’s needed is some way for the tasks
to be made aware of each other’s actions
• Various mechanisms for this exist in Linux
• One of the simplest ways is by ‘signaling’
(i.e., one task can wait for an ‘all clear’
signal to be sent to it by the other task)
• But ‘busy-waiting’ needs to be avoided,
since it wastes CPU time and degrades
the overall efficiency of the system
The ‘signal mask’
• Each process has a ‘signal mask’ that can
be used to ‘block’ certain specific signals
• The signal mask for each process is kept
in its process control block (in the kernel)
• But a process can inspect and modify its
signal mask by using special system-calls
The ‘sigset_t’ type
struct task_struct
Process
Control
Block
sigset_t
blocked
The signal mask is a collection
of flag-bits that indicates which
signals are to be ‘blocked’
How to inspect ‘signal mask’
• 1. Include the <signal.h> header-file:
#include <signal.h>
• 2. Declare a ‘sigset_t’ object:
sigset_t sigmask;
• 3. Call the ‘sigprocmask()’ library-function:
sigprocmask( 0, NULL, &sigmask );
How to modify ‘signal mask’
• 1. Declare two ‘sigset_t’ objects:
sigset_t
nset, oset;
• 2. Initialize the ‘new’ signal-set:
sigemptyset( &nset );
sigaddset( &nset, SIGUSR1 )
sigaddset( &nset, SIGUSR2 )
• 3. Call the ‘sigprocmask()’ library-function:
sigprocmask( SIG_BLOCK, &nset, &oset );
How to wait for a signal
• 1. Declare and initialize a global variable:
int
done = 0;
• 2 Define your signal-handling function:
void upon_signal( int signum ) { done = 1; }.
• 3. Install your signal-handler function:
signal( SIGUSR1, upon_signal );
signal( SIGUSR2, upon_signal );
• 4. Declare and initialize a ‘sigset_t’ object:
sigset_t
zeromask;
sigemptyset( &zeromask );
• 5. Then use ‘sigsuspend()’ to wait for your signal:
while ( done == 0 ) sigsuspend( &zeromask );
Our ‘racecure.cpp’ demo
• These signal-handling library-functions are
used by this demo-program to remove the
‘race condition’ without doing busy-waiting
• It’s based on ideas of W. Richard Stevens
from his classic: “Advanced Programming
in the UNIX Environment” (1993).
How it works
parent-process
child-process
done
done
signal-handler
signal-handler
write-message;
TELL_CHILD;
WAIT_CHILD;
SIGUSR1
WAIT_PARENT;
write-message;
TELL_PARENT;
SIGUSR2
user
kernel
signal-mask
signal-mask
In-class exercises
• Modify ‘racedemo’ so that the parent forks
twice (i.e., two child-processes), with three
processes all writing to the ‘stdout’ stream
• Then add synchronization functions which
will eliminate the race conditions and allow
the parent-process to finish writing before
either of the child-processes begins