120 likes | 134 Views
This article explores different techniques for process synchronization in the Linux environment, focusing on the concept of race conditions and their impact on multiprogramming. It discusses signaling, signal masks, and how to inspect and modify signal masks. The article also provides code examples and demonstrations for better understanding.
E N D
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 The signal mask is a collection of flag-bits that indicates which signals are to be ‘blocked’ sigset_t 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; WAIT_PARENT; write-message; TELL_PARENT; SIGUSR1 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