Professional Documents
Culture Documents
Lab 3 Synchronization v2-1
Lab 3 Synchronization v2-1
Lab 3
Synchronization
Course: Operating Systems
Goal This lab helps student to practice with the synchronization in OS, and understand the reason why
we need the synchronization.
Contents In detail, this lab requires student practice with examples using synchronization techniques
to solve the problem called race condition. The synchronization is performed on Thread using the
following mechanism:
• semaphore
• conditional variable
Besides, the practices also introduce and include some locking variants, i.e. spinlock, read/write spinlock,
sequence lock. In addition to using a lock, we may reach a deadlock state.
Result After doing this lab, student can understand the definition of synchronization and write a
program without the race condition using the techniques above.
1
CONTENTS 1 BACKGROUND
Contents
1 Background 2
1.1 Race condition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.1 Race condition caused by atomicity . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.2 Race condition caused by in-correct ordering . . . . . . . . . . . . . . . . . . . . . 3
3 Practice 7
3.1 Shared buffer problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2 Bounded buffer problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.3 Dining-Philosopher problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4 Exercise 14
4.1 Problem 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4.2 Problem 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.3 Problem 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1 Background
1.1 Race condition
Race condition is the condition of software where the system’substain behavior is dependent on the
sequences of uncontrollable events. Therefore, we need mechanisms that provide mutual exclusion ability.
In high level programming language, we can note that a statement may be implemented in machine
language (on a typical machine) as follows:
i n s t r u c t i o n 1 := r e g i s t e r l o a d
i n s t r u c t i o n 2 := a r i t h m e t i c o p e r a t i o n
i n s t r u c t i o n 3 := r e g i s t e r s t o r e
If there are two or more threads accessing a single storage, the data manipulation which is splitable may
result in an incorrect final state. Such tools are provided by POSIX pthread as follows:
Mutex Lock The problem with mutex is that the thread is put into a sleep state and later is woken
to process the task. This sleeping and waking action are expensive operations.
Spin lock In a short description, spin lock provides a (CPU) poll waiting until it has got previliege.
If the mutex sleeps in a very short time, then it wastes the cost of expensive operations of sleeping and
waking up. But if the long time is quite long, CPU polling is a big waste of computation.
2
1.1 Race condition 1 BACKGROUND
STORAGE
thread 1
data
thread 2
instruction 1 instruction 1
instruction 2
instruction 1 ...
} lock {
instruction 2 instruction 1
instruction 2
instruction 2 ...
}
RACE CONDITION
In the previous chapter of process shared memory, we introduced the bounded buffer problem. Even with
mutex setting, the race conditional may still happen when a producer fills in a buffer an item before a
consumer empties it causing an overwriting with a loosing data. Another example of system behavior
happens when a consumer retrieves a garbage value if an item is retrieved before a producer fills in a
meaningful value. These wrong system behaviors are caused by the in-correct ordering of the sequence of
events.
counting_semaphore counting_semaphore
many-pro many-cons
wait for slot wait for item
Producer Producer
Consumer Consumer
Producer Producer
Consumer Consumer
Producer Producer
3
2 PROGRAMMING INTERFACE OF SYNCHRONIZATION TOOLS
that thread occurs. It includes two operations: wait and signal. The conditional variable can be used by
a thread to block other threads until it notifies the conditional variable.
Read-write spin lock and sequence lock In the previous section, we have seen some locking methods
like mutex, spinlock, etc. In a high-speed manner, i.e., kernel driver, high-speed/fast communication,
when you want to treat both the reader and the writer equally, then you have to use spin lock. The two
following mechanisms provide a different priority policy between reader and writer. We introduce the
main characteristics of them and then, we discuss the reader/writer conflict problem later as an exercise.
(See more details in Section4.1)
Read-write spinlock: In some situations, we may have to give more access frequencies to the reader.
The reader-writer spinlock is a suitable solution in this case.
Sequence lock: Reader-writer lock can cause writer starvation. Seqlock gives more permission to
writer. Sequential lock is a reader-writer mechanism which is given high priority to the writer, so this
avoids the writer starvation problem.
int p t h r e a d m u t e x i n i t ( p t h r e a d m u t e x t ∗mutex ,
const p t h r e a d m u t e x a t t r t ∗ a t t r ) ;
int p t h r e a d m u t e x d e s t r o y ( p t h r e a d m u t e x t ∗mutex ) ;
int p t h r e a d m u t e x l o c k ( p t h r e a d m u t e x t ∗mutex ) ;
int p t h r e a d m u t e x u n l o c k ( p t h r e a d m u t e x t ∗mutex ) ;
Example:
pthread mutex t lock;
pthread mutex init(&lock,NULL);
...
pthread mutex lock(&lock);
< CS >
pthread mutex unlock(&lock);
< RS >
4
2.2 Spin lock 2 PROGRAMMING INTERFACE OF SYNCHRONIZATION TOOLS
int p t h r e a d s p i n i n i t ( p t h r e a d s p i n l o c k t ∗ l o c k , int p s h a r e d ) ;
int p t h r e a d s p i n d e s t r o y ( p t h r e a d s p i n l o c k t ∗ l o c k ) ;
int p t h r e a d s p i n l o c k ( p t h r e a d s p i n l o c k t ∗ l o c k ) ;
int p t h r e a d s p i n u n l o c k ( p t h r e a d s p i n l o c k t ∗ l o c k ) ;
Example:
pthread spinlock t lock;
pthread spin init(&lock,PTHREAD PROCESS SHARED); //we can use pshared=0 for NULL
setting or PTHREAD PROCESS SHARED
...
pthread spin lock(&lock);
< CS >
pthread spin unlock(&lock);
< RS >
2.3 Semaphore
provided in POSIX semaphore (not PTHREAD)
#include <semaphore . h>
sem t sem ;
Example:
sem t sem;
sem init(&sem,0,5); //we can use pshared=0 for NULL setting or PTHREAD PROCESS SHARED
...
sem wait(&sem);
< CS >
sem post(&sem);
< RS >
5
2.4 Conditional Variable2 PROGRAMMING INTERFACE OF SYNCHRONIZATION TOOLS
Example:
pthread mutex t mtx;
pthread cond t lock;
pthread mutex init(&mtx,NULL);
pthread cond init(&lock,NULL);
...
pthread cond wait(&lock,&mtx); /* May be locked if no signal is triggered */
< CS >
pthread cond signal(&lock);
< RS >
int p t h r e a d r w l o c k r d l o c k ( p t h r e a d r w l o c k t ∗ r w l o c k ) ;
int p t h r e a d r w l o c k w r l o c k ( p t h r e a d r w l o c k t ∗ r w l o c k ) ;
int p t h r e a d r w l o c k u n l o c k ( p t h r e a d r w l o c k t ∗ r w l o c k ) ;
6
2.6 Sequence lock 3 PRACTICE
Although it lacks a userspace implementation, it is widely used in the kernel to protect buffer data in
modern programming patterns with many readers, many writers, .i.e SMP Linux kernel support.
3 Practice
In this section, we work on various ”native” problems to regcognize the ”real” wrong behaviors. All
these experiments are derived from theory slides with a minor modification. We practice the provided
synchronization mechanisms and see how they work to correct the wrong things.
void ∗ f c o u n t ( void ∗ s i d ) {
int i ;
f o r ( i = 0 ; i < MAX COUNT; i ++) {
count = count + 1 ;
}
int main ( ) {
p t h r e a d t thread1 , t h r e a d 2 ;
/∗ C r e a t e i n d e p e n d e n t t h r e a d s each o f which w i l l e x e c u t e f u n c t i o n ∗/
p t h r e a d c r e a t e ( &thread1 , NULL, &f c o u n t , ” 1 ” ) ;
p t h r e a d c r e a t e ( &thread2 , NULL, &f c o u n t , ” 2 ” ) ;
// Wait f o r t h r e a d t h 1 f i n i s h
p t h r e a d j o i n ( thread1 , NULL ) ;
// Wait f o r t h r e a d t h 1 f i n i s h
7
3.2 Bounded buffer problem 3 PRACTICE
p t h r e a d j o i n ( thread2 , NULL ) ;
return 0 ;
}
Step 3.1.2 Recognize the wrong issue and propose a fix mechanism using the provided synchronization
tool.
Hint: pthread mutex lock() and pthread mutex unlock() are useful to protect f count() thread worker
# Implement by your s e l f t h e f i x e d shrdmem mutex program ( i t i s not a v a i l a b l e y e t )
# c o p y c a t may r e s u l t e r r o r g c c : f a t a l e r r o r : no i n p u t f i l e s
$ g c c - p t h r e a d - o shrdmem mutex shrdmem . c
$ . / shrdmem mutex
Thread 2 : h o l d i n g 1001990720
Thread 1 : h o l d i n g 2000000000
// I n i t i a t e s h a r e d b u f f e r
int b u f f e r [ MAX ITEMS ] ;
int f i l l = 0 ;
int u s e = 0 ;
/∗TODO: F i l l i n t h e s y n c h r o n i z a t i o n s t u f f ∗/
void put ( int v a l u e ) ; // p u t d a t a i n t o b u f f e r
int g e t ( ) ; // g e t d a t a from b u f f e r
void ∗ p r o d u c e r ( void ∗ a r g ) {
int i ;
int t i d = ( int ) a r g ;
f o r ( i = 0 ; i < LOOPS; i ++) {
/∗TODO: F i l l i n t h e s y n c h r o n i z a t i o n s t u f f ∗/
8
3.2 Bounded buffer problem 3 PRACTICE
put ( i ) ; // l i n e P2
p r i n t f ( ” Producer %d put data %d\n” , t i d , i ) ;
sleep (1);
/∗TODO: F i l l i n t h e s y n c h r o n i z a t i o n s t u f f ∗/
}
p t h r e a d e x i t (NULL ) ;
}
/∗TODO: F i l l i n t h e s y n c h r o n i z a t i o n s t u f f ∗/
// C r e a t e consumer t h r e a d
p t h r e a d c r e a t e ( & consumers [ i ] , NULL, consumer , ( void ∗ ) t i d [ i ] ) ;
}
/∗TODO: F i l l i n t h e s y n c h r o n i z a t i o n s t u f f ∗/
return 0 ;
}
9
3.3 Dining-Philosopher problem 3 PRACTICE
int g e t ( ) {
int tmp = b u f f e r [ u s e ] ; // l i n e g1
u s e = ( u s e + 1 ) % MAX ITEMS ; // l i n e g2
return tmp ;
}
Step 3.2.2 Recognize the wrong issue and propose a fix mechanism using the provided synchronization
tool.
Hint: sem wait() and sem signal() are useful to protect consumer() and producer() thread worker
# Implement by your s e l f t h e f i x e d pc sem program ( i t i s not a v a i l a b l e y e t )
# c o p y c a t may r e s u l t e r r o r g c c : f a t a l e r r o r : no i n p u t f i l e s
$ . / pc sem
Producer 0 put data 0
Consumer 0 g e t data 0
Producer 0 put data 1
Consumer 0 g e t data 1
Producer 0 put data 2
Consumer 0 g e t data 2
Producer 0 put data 3
Producer 0 put data 4
Consumer 0 g e t data 3
Producer 0 put data 5
Consumer 0 g e t data 4
Consumer 0 g e t data 5
...
10
3.3 Dining-Philosopher problem 3 PRACTICE
#define N 5
p t h r e a d m u t e x t mtx ;
p t h r e a d c o n d t c h o p s t i c k [N ] ;
void ∗ p h i l o s o p h e r ( void ∗ ) ;
void e a t ( int ) ;
void t h i n k ( int ) ;
int main ( )
{
int i , a [N ] ;
p t h r e a d t t i d [N ] ;
// f o r ( i = 0 ; i < N; i ++)
// p t h r e a d c o n d i n i t (& c h o p s t i c k [ i ] , NULL) ;
/∗ END PROTECTION MECHANISM ∗/
f o r ( i = 0 ; i < 5 ; i ++)
{
a[ i ] = i ;
p t h r e a d c r e a t e (& t i d [ i ] , NULL, p h i l o s o p h e r , ( void ∗ ) &a [ i ] ) ;
}
f o r ( i = 0 ; i < 5 ; i ++)
p t h r e a d j o i n ( t i d [ i ] , NULL ) ;
}
while ( 1 )
{
/∗ PROTECTION MECHANISM ∗/
// p t h r e a d c o n d w a i t (& c h o p s t i c k [ p h i l ] , &mtx ) ;
// p t h r e a d c o n d w a i t (& c h o p s t i c k [ ( p h i l + 1) % N] , &mtx ) ;
p r i n t f ( ” P h i l o s o p h e r %d t a k e s f o r k %d and %d\n” ,
phil , phil , ( p h i l + 1) % N) ;
eat ( phil ) ;
sleep (2);
p r i n t f ( ” P h i l o s o p h e r %d p u t s f o r k %d and %d down\n” ,
p h i l , ( p h i l + 1 ) % N, p h i l ) ;
11
3.3 Dining-Philosopher problem 3 PRACTICE
/∗ PROTECTION MECHANISM ∗/
// p t h r e a d c o n d s i g n a l (& c h o p s t i c k [ p h i l ] ) ;
// p t h r e a d c o n d s i g n a l (& c h o p s t i c k [ ( p h i l + 1) % N ] ) ;
think ( phil ) ;
sleep (1);
}
}
void e a t ( int p h i l )
{
p r i n t f ( ” P h i l o s o p h e r %d i s e a t i n g \n” , p h i l ) ;
}
void t h i n k ( int p h i l )
{
p r i n t f ( ” P h i l o s o p h e r %d i s t h i n k i n g \n” , p h i l ) ;
}
Step 3.3.2 Analyze the output and figure out the in-correct execution. Enable the PROTECTION
MECHANISM and compare the output.
Step 3.3.3 With the enabled code, the problem still falls into a deadlock state, refers the illustration
in Figure 3. Use the provided material in the theory background section to explain the experimental
12
3.3 Dining-Philosopher problem 3 PRACTICE
phenomenon.
Deadlock state
wait wait
wait
wait
wait
Recall the experiment to manipulate a running process in the previous lab. Analyze the status of the
working process.
$ ps aux | g r e p d i n
oslab 10532 0 . 0 0 . 0 47492 800 p t s /4 S l+ 1 3 : 4 2 0:00 . / din
oslab 10577 0 . 0 0 . 2 11760 2144 p t s /0 S+ 13:42 0:00 grep - -
$ sudo cat / p r o c/<PID>/ s t a t u s
Name : din
State : S ( sleeping )
...
$ sudo cat / p r o c/<PID>/s t a c k
[< f f f f f f f f 8 1 1 0 1 1 a 4 >] f u t e x w a i t q u e u e m e+0xc4 /0 x120
[< f f f f f f f f 8 1 1 0 2 0 e b >] f u t e x w a i t +0x17b /0 x270
[< f f f f f f f f 8 1 1 0 3 9 b 6 >] d o f u t e x +0xe6 /0 xbc0
[< f f f f f f f f 8 1 1 0 4 5 0 1 >] S y S f u t e x+0x71 /0 x150
[< f f f f f f f f 8 1 8 2 b e d b >] entry SYSCALL 64 fastpath+0x22 /0 xcb
[< f f f f f f f f f f f f f f f f >] 0 x f f f f f f f f f f f f f f f f
From kernel.org
futex wait queue me : queue me and wait f o r wakeup , timeout , o r s i g n a l
13
4 EXERCISE
Step 3.3.5 Advance step It completely wrong from the scratch with an in-appropriate (or garbage)
synchronization mechanism. The last working execution still contains fatal risk. Propose/design and
implement an alternative protection mechanism.
Hint: Through this experiment, you learn by yourself the importance of using the correct synchro-
nization mechanism at the begining; otherwise, we fix something and yield another work of fixing the
workaround mechanism. It is important to choose the right synchronization tool in tackling a real prob-
lem.
4 Exercise
4.1 Problem 1
Design and implement sequence lock API.
#include ” s e q l o c k . h” /∗ TODO: implement t h i s h e a d e r f i l e ∗/
/∗
∗ TODO: Implement t h e s e f o l l o w i n g APIs
∗/
pthread seqlock t lock ;
/∗ I n i t w i t h d e f a u l t NULL a t t r i b u t e a t t r=NULL ∗/
int p t h r e a d s e q l o c k i n i t ( p t h r e a d s e q l o c k t ∗ s e q l o c k ) ;
int p t h r e a d r w l o c k d e s t r o y ( p t h r e a d s e q l o c k t ∗ s e q l o c k ) ;
14
4.2 Problem 2 4 EXERCISE
• If the reader is in critical section, the new reader thread can enter ocasionally, but the writer cannot
enter.
• If there are some readers in the critical section by taking the lock, and there is a writer want to
enter. That writer has to wait if another reader is coming until all of readers have finish. That why
this mechanism is reader prefer.
Sequence lock conflict resolution the conflict resolution mechanism in teader-writer lock can cause
writer starvation. The following policy is implemented by the sequence lock:
• When no one is in the critical section, one writer can enter the critical section and takes the lock,
increasing the sequence number by one to an odd value. When the sequence number is an odd value,
the writing is happening. When the writing has been done, the sequence is back to even value. Only
one writer is allow into critical section.
• When the reader wants to read data, it checks the sequence number which is an odd value, then it
has to wait until the writer finish.
• When the value is even, many readers can enter the critical section to read the value.
• When there are only a reader and no writer in the critical section, if a writer want to enter the
critical section it can take the lock without blocking.
4.2 Problem 2
(Aggregated Sum) Implement the thread-safe program to calculate the sum of a given integer array
using < tnum > number of threads. The size of the given array < arrsz > and the < tnum > value is
provided in the program arguments. You are provided a pre-processed argument program with the usage
as the following description.
aggsum , v e r s i o n 0 . 0 1
Arguments :
15
4.3 Problem 3 4 EXERCISE
The last argument < seednum > is used in srand(), generating a fixed sequence of integer values.
Call the following routine to generate a random array buf of integer with arraysize elements.
int g e n e r a t e a r r a y d a t a ( int ∗ buf , int a r r a y s i z e , int seednum ) ;
/∗
∗ TODO implement a t h r e a d s a f e sum o p e r a t o r works i n t h e range
∗ i . e . f o r ( i=i d x r a n g e . s t a r t ; i<= i d x r a n g e . end ; i ++){}
∗ and w r i t e t h e sum t o s u m b u f f ( i n program g l o b a l d a t a )
∗/
}
int main ( )
{
p t h r e a d t t i d ; /∗ Sample code i s o n l y s i n g l e t h r e a d ∗/
struct r a n g e t h r e a d i d x r a n g e ;
...
/∗ Sample code use f u l l range ∗/
thread idx range . start = 0;
t h r e a d i d x r a n g e . end = a r r s z - 1 ;
...
/∗ TODO: implement m u l t i - t h r e a d mechanism ∗/
p t h r e a d c r e a t e (& t i d , NULL, sum worker , t h r e a d i d x r a n g e ) ) ;
}
You need to implement a single-threaded program to compare the result of aggregated Sum
with the one of the multi-threaded program.
4.3 Problem 3
(Interruptable system logger ) Design and implement a logger support the two operations wrlog() and
f lushlog() to manipulate the log data buffer ”logbuf”
#define MAX LOG LENGTH 10
#define MAX BUFFER SLOT 5
char ∗∗ l o g b u f ;
16
4.3 Problem 3 4 EXERCISE
In this problem, there are many programs or actors that call wrlog() to append the log data to a shared
buffer (i.e., log-file cache) which can be flushed to disk eventually. For simplicity, we assume the buffer
contains 5 (= M AX BU F F ER SLOT ) data slots and the flush event occurs periodically at time out.
We also assume a LOG is a fixed length string, i.e. char new log[M AX LOG LEN GT H].
In practical point of view, the behavior of the system can be illustrated as a sequence of writing log
data and the flush will be periodically triggered when a timeout is reached.
int main ( )
{
w r l o g ( data1 ) ;
w r l o g ( data1 ) ;
w r l o g ( data1 ) ;
...
w r l o g ( datan ) ;
}
wrlog() append data to shared buffer but not exceed the buffer size. If it reaches the limits, it needs to wait
until the buffer is flushed.
flushlog() clean buffer aka. move all the stored items to somewhere (in here is printing to screen) and then
delete all of them. This action runs eventually; for simplicy we just make a periodical call here.
Further development: The interruptable mechanism of flush log can (further) support more unpre-
dict events, i.e., it can handle a signall SIGU SR1, SIGU SR2 which are introduced in Lab 1 appendix.
You are provided a referenced code with non-protected buffer by default, the program’s output is:
$ ./ logbuf
flushlog ()
wrlog ( ) : 0
wrlog ( ) : 1
wrlog ( ) : 2
wrlog ( ) : 3
wrlog ( ) : 4
wrlog ( ) : 7
w r l o g ( ) : 12
w r l o g ( ) : 13
w r l o g ( ) : 16
w r l o g ( ) : 17
w r l o g ( ) : 21
w r l o g ( ) : 24
w r l o g ( ) : 11
w r l o g ( ) : 26
w r l o g ( ) : 14
wrlog ( ) : 6
17
4.3 Problem 3 4 EXERCISE
w r l o g ( ) : 27
w r l o g ( ) : 28
wrlog ( ) : 8
w r l o g ( ) : 20
wrlog ( ) : 9
w r l o g ( ) : 22
w r l o g ( ) : 10
w r l o g ( ) : 19
w r l o g ( ) : 25
w r l o g ( ) : 29
w r l o g ( ) : 18
w r l o g ( ) : 23
wrlog ( ) : 5
w r l o g ( ) : 15
flushlog ()
Slot 0: 0
Slot 1: 1
Slot 2: 2
Slot 3: 3
Slot 4: 4
Slot 5: 7
S l o t 6 : 12
flushlog ()
flushlog ()
flushlog ()
TODO: Implement the protection mechanism for wrlog() and flushlog() routines to make it a safe
data accessing (to buffer). If it has a proper configuration then the program behavior is somehow like
this illustration (comment out the print function name in wrlog() and flushlog() to make this clean
output).
$ ./ logbuf
Slot 0: 0
Slot 1: 1
Slot 2: 2
Slot 3: 3
Slot 4: 4
Slot 5: 5
S l o t 0 : 12
Slot 1: 6
Slot 2: 7
S l o t 3 : 10
Slot 4: 8
S l o t 5 : 11
S l o t 0 : 17
S l o t 1 : 13
18
4.3 Problem 3 4 EXERCISE
Slot 2: 16
Slot 3: 15
Slot 4: 14
Slot 5: 18
Slot 0: 25
Slot 1: 20
Slot 2: 19
Slot 3: 21
Slot 4: 22
Slot 5: 23
Slot 0: 27
Slot 1: 28
Slot 2: 26
Slot 3: 24
Slot 4: 29
Slot 5: 9
19