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