Lab3 Synchronization

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

HCMC University Of Technology

Faculty of Computer Science & Engineering

Lab 3
Synchronization
Course: Operating Systems

April 1, 2024

Goal This lab helps the student practice synchronization in the operating system, and figure out why
we need the synchronization.

Contents In detail, this lab requires the student practice experiments using synchronization techniques
to solve the problem called race condition. The synchronization is performed on Thread using the
following mechanisms:

• mutual exclusion (mutex)

• semaphore

• conditional variable

Besides, the practices also introduce and include some locking variants, i.e. spinlock, read/write spinlock,
sequence lock.

Result After doing this lab, student can understand the definition of synchronization and write a
program without the race condition using the techniques above.

Requirements Student need to review the theory of synchronization.

1
CONTENTS CONTENTS

Contents
1 Background 3
1.1 Race condition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.1 Critical section problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.2 Race condition caused by atomicity . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.1.3 Race condition caused by in-correct ordering . . . . . . . . . . . . . . . . . . . . . 4

2 Programming Interfaces 6
2.1 Mutex lock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2 Spin lock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3 Semaphore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.4 Conditional Variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.5 Reader-writer lock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.6 Sequence lock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

3 Practices 9
3.1 Shared buffer problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.2 Bounded buffer problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

4 Exercise 12

2
1 BACKGROUND

1 Background
1.1 Race condition
Race condition is the condition of software where the systems’substain behaviors is dependent on the
sequences of uncontrollable events. Therefore, we need mechanisms that provide synchronization ability.

1.1.1 Critical section problem

Consider the system includes n tasks, represented by threads or processes with shared memory, denoted
by {P0 ,P1 ,...,Pn−1 }
Each process has a critical section (i.e, segment of code) which has the following properties:

• data is shared among task

• the performed task modifying the shared data

• system employs the preemptive scheduling approach

In this work, we investigate the case of multi-thread process in which the shared data segment is
natively encountered the problem of race condition. The structure of multi-thread process is illustrated
in Figure 1

code data files code data file

register PC stack
register register register

stack stack stack

PC PC PC

Figure 1: The structure of multi-thread process

Critical-section problem needs to design a protocol to solve the raised problem of race condition. To
propose the solution, we need first able to figure out/identify critical section in each program code, then
we employs the synchronization mechanisms which are introduced/practiced in this work.

3
1.1 Race condition 1 BACKGROUND

1.1.2 Race condition caused by atomicity

In high level programming language, we can note that a statement is always implemented in machine
language (on a typical machine) by many instuctions as the following example:
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 tasks (threads/or shared-memory processes) 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 task 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.
STORAGE

thread 1
data
thread 2

thread 1 thread 2 thread 1 thread 2


lock {
PROCESSING

instruction 1 instruction 1
instruction 2
instruction 1 ...
} lock {
instruction 2 instruction 1
instruction 2
instruction 2 ...
}

RACE CONDITION

Figure 2: The race condition caused by atomicity

1.1.3 Race condition caused by in-correct ordering

In the previous chapter of process memory sharing, 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 which results a loosing data. Another example of system
behavior happens when a consumer retrieves a garbage (uninitialized) 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.

Semaphore is suited cleanly to a producer-consumer model. A semaphore is basically an object that


includes an internal counter, a waiting list of threads, and supports two different operations, i.e., wait
and signal. The internal counter with a proper initialization provides a good mechamism to manage the
limited number of storage slot in bounded buffer.

4
1.1 Race condition 1 BACKGROUND

counting_semaphore counting_semaphore
slot = N item=0

many-pros wait(slot) many-cons wait(item)

Producer Producer

Consumer Consumer

Producer Producer

Consumer Consumer

Producer Producer

signal(item) signal(slot)

Figure 3: BoundedBuffer or Producer-Consumer problem

Although semaphore is perfect fit for bounded buffer problem, it constrains on the equal number of
consumers(/readers) and producers(/writer). Some updated models such as few writers many readers is
not a good application for semaphore. It relaxes the matching of internal counter with the number of
slot then it helps in the unbalancing context between actors who access the buffer. These following tools
provide the ordering mechanism with more relaxation on the number of system actors:

Conditional variable Conditional variable is a synchronization primitive that allows threads to wait
until a particular condition occur. It just allows a thread to be signaled when something of interest to
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)

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.

5
2 PROGRAMMING INTERFACES

2 Programming Interfaces
POSIX Thread library provides a standard based thread API for C/C++. The functions and declara-
tions of many common APIs in this library are conformed to POSIX.1 standards through many versions
(2003, 2017 etc.). In Linux, the library is not integrated by default, so it requires an explicit linking
declaration with “-pthread”. Semaphore is belong to POSIX (not pthread) standard. Sequence lock is
not implemented in the both libraries but has been added to the kernel since Linux 2.5.x.

2.1 Mutex lock


provided in POSIX Thread (pthread) library
#include <p t h r e a d . h>
pthread mutex t lock ;

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 ) ;

An example:
pthread mutex t lock;
pthread mutex init(&lock,NULL);
...
pthread mutex lock(&lock);
< CS >
pthread mutex unlock(&lock);
< RS >

2.2 Spin lock


provided in POSIX Thread (pthread) library
#include <p t h r e a d . h>
pthread spinlock t lock ;
int p s h a r e ; /∗ PTHREAD PROCESS SHARED
∗ or PTHREAD PROCESS PRIVATE ( c r e a t o r o n l y )
∗/

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 ) ;

An example:
pthread spinlock t lock;

6
2.3 Semaphore 2 PROGRAMMING INTERFACES

//we can use pshared=0 for NULL setting or pshared=PTHREAD PROCESS SHARED
pthread spin init(&lock,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 ;

int s e m i n i t ( sem t ∗sem , int pshared , unsigned int v a l u e )


int s e m d e s t r o y ( sem t ∗sem ) ;

int sem wait ( sem t ∗sem ) ;


int s e m p o s t ( sem t ∗sem ) ;

An example:
sem t sem;
//we can use pshared=0 for NULL setting or pshared=PTHREAD PROCESS SHARED
//The 3rd argument is the default value of the semaphore
sem init(&sem,0,5);
...
sem wait(&sem);
< CS >
sem post(&sem);
< RS >

2.4 Conditional Variable


provided in POSIX Thread (pthread) library
#include <p t h r e a d . h>
pthread cond t cv count ;

int p t h r e a d c o n d i n i t ( p t h r e a d c o n d t ∗ cond , const p t h r e a d c o n d a t t r t ∗ a t t r ) ;


int p t h r e a d c o n d d e s t r o y ( p t h r e a d c o n d t ∗ cond ) ;

int p t h r e a d c o n d w a i t ( p t h r e a d c o n d t ∗ cond , p t h r e a d m u t e x t ∗mutex ) ;


int p t h r e a d c o n d s i g n a l ( p t h r e a d c o n d t ∗ cond ) ;

An example:
pthread mutex t mtx;
pthread cond t lock;

7
2.5 Reader-writer lock 2 PROGRAMMING INTERFACES

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 >

2.5 Reader-writer lock


provided in POSIX Thread (pthread) library
#include <p t h r e a d . h>

pthread rwlock t lock ;

int p t h r e a d r w l o c k i n i t ( p t h r e a d r w l o c k t ∗ rwlock ,
const p t h r e a d r w l o c k a t t r t ∗ a t t r ) ;
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 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 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 ) ;

An example:
pthread rwlock init(&lock,NULL);
...
pthread rwlock rdlock(&lock);
< CS >
pthread rwlock unlock(&lock);
< RS >
...
pthread rwlock rdlock(&lock);
< CS >
pthread rwlock wrunlock(&lock);
< RS >

2.6 Sequence lock


has not provided in POSIX Thread library yet. Its implementation has existed in kernel (and hence, can
be used only in kernel space) since 2.5.60.
i s N/A Not a v a i l a b l e , implement a s a f l a v o r ( e x e r c i s e ) a s an API i n u s e r s p a c e

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.

8
3 PRACTICES

3 Practices
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.

3.1 Shared buffer problem


#include <s t d i o . h>
#include < s t d l i b . h>
#include <p t h r e a d . h>

i n t MAX COUNT = 1 e 9 ;
s t a t i c int count = 0 ;

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 ;
}

p r i n t f ( ” Thread %s : h o l d i n g %d \n” , ( char ∗ ) s i d , count ) ;


}

i n t main ( ) {
p t h r e a d t thread1 , thread2 ;

/∗ C r e a t e i n d e p e n d e n t t h r e a d s e a c h o f w h i c h w i l l execute f u n c t i o n ∗/
p t h r e a d c r e a t e ( &t h r e a d 1 , NULL, &f c o u n t , ” 1 ” ) ;
p t h r e a d c r e a t e ( &t h r e a d 2 , 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 ( t h r e a d 1 , NULL ) ;

// 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 ( t h r e a d 2 , NULL ) ;

return 0 ;
}

Step 3.1.1 Compile and execute the program


# The program name must match your own code ,
# 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 shrdmem . c
$ . / shrdmem
Thread 1 : h o l d i n g 1990079976
Thread 2 : h o l d i n g 1991664743

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

9
3.2 Bounded buffer problem 3 PRACTICES

3.2 Bounded buffer problem


#include <s t d i o . h>
#include < s t d l i b . h>
#include <semaphore . h>
#include <p t h r e a d . h>

#d e f i n e BUF SIZE 2
#d e f i n e THREADS 1 // 1 p r o d u c e r and 1 c o n s u m e r
#d e f i n e LOOPS 3 ∗ BUF SIZE // v a r i a b l e

// I n i t i a t e s h a r e d b u f f e r
i n t b u f f e r [ BUF SIZE ] ;
int f i l l = 0 ;
int use = 0 ;

/∗TODO: Fill in the synchronization stuff ∗/

void put ( i n t v a l u e ) ; // p u t data into buffer


int get ( ) ; // g e t d a t a from buffer

void ∗ p r o d u c e r ( void ∗ a r g ) {
int i ;
int t i d = ( int ) arg ;
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 ∗/
put ( i ) ; // l i n e P2
p r i n t f ( ” P r o d u c e r %d put d a t a %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 ) ;
}

void ∗ consumer ( void ∗ a r g ) {


i n t i , tmp = 0 ;
int t i d = ( int ) arg ;
while ( tmp != - 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 ∗/
tmp = g e t ( ) ; // l i n e C2
p r i n t f ( ” Consumer %d g e t d a t a %d\n” , t i d , tmp ) ;
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 ) ;
}

i n t main ( i n t a r g c , char ∗∗ a r g v ) {
int i , j ;
i n t t i d [THREADS ] ;
p t h r e a d t p r o d u c e r s [THREADS ] ;
p t h r e a d t c o n s u m e r s [THREADS ] ;

/∗TODO: Fill in the synchronization stuff ∗/

f o r ( i = 0 ; i < THREADS; i ++) {


tid [ i ] = i ;
// C r e a t e p r o d u c e r t h r e a d
p t h r e a d c r e a t e ( & p r o d u c e r s [ i ] , NULL, producer , ( void ∗ ) tid [ i ] ) ;

// C r e a t e c o n s u m e r t h r e a d
p t h r e a d c r e a t e ( & c o n s u m e r s [ i ] , NULL, consumer , ( void ∗ ) tid [ i ] ) ;
}

f o r ( i = 0 ; i < THREADS; i ++) {


p t h r e a d j o i n ( p r o d u c e r s [ i ] , NULL ) ;
p t h r e a d j o i n ( c o n s u m e r s [ i ] , NULL ) ;
}

/∗TODO: Fill in the synchronization stuff destroy ( if n e e d e d ) ∗/

return 0 ;
}

void put ( i n t v a l u e ) {
buffer [ f i l l ] = value ; // l i n e f 1
f i l l = ( f i l l + 1 ) % BUF SIZE ; // l i n e f 2
}

int get ( ) {
i n t tmp = b u f f e r [ u s e ] ; // l i n e g 1
b u f f e r [ use ] = - 1 ; // c l e a n t h e item
u s e = ( u s e + 1 ) % BUF SIZE ; // l i n e g 2
return tmp ;
}

10
3.2 Bounded buffer problem 3 PRACTICES

Step 3.2.1 Compile and execute the program

# The program name must match your own code


$ g c c - p t h r e a d - o pc pc . c
$ . / pc
Consumer 0 g e t data 0
Producer 0 put data 0
Consumer 0 g e t data 0
Producer 0 put data 1
Consumer 0 g e t data 1
Consumer 0 g e t data 1
Consumer 0 g e t data 1
...

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
...

11
4 EXERCISE

4 Exercise
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 ) ;

int pthread seqlock rdlock ( pthread seqlock t ∗ seqlock ) ;


int pthread seqlock rdunlock ( pthread seqlock t ∗ seqlock ) ;
int pthread seqlock wrlock ( pthread seqlock t ∗ seqlock ) ;
int pthread seqlock wrunlock ( pthread seqlock t ∗ seqlock ) ;

The reader/writer conflict resolution following the description:


Reader-writer lock conflict resolution

• When there is no thread in the critical section, any reader or writer can enter into a critical section.
But only one thread can enter.

• If the reader is in critical section, the new reader thread can enter ocasionally, but the writer cannot
enter.

• If the writer is in critical section, no other reader or writer can 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.

12
4 EXERCISE

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

usage : aggsum a r r s z tnum [ seednum ]

Generate randomly i n t e g e r a r r a y s i z e <a r r s z > and c a l c u l a t e sum p a r a l l e l l y


u s i n g <tnum> t h r e a d s . The o p t i o n a l <seednum> v a l u e u s e t o c o n t r o l t h e
randomization of the generated array .

Arguments :

arrsz s p e c i f i e s the s i z e of array .


tnum number o f p a r a l l e l t h r e a d s .
seednum i n i t i a l i z e t h e s t a t e o f t h e randomized g e n e r a t o r .

The last argument < seednum > is an already implemented mechanism. This value is used to generate
the integer values in the array and we don’t touch it to keep it for later validated testcase generation.
The data generation mechanism is also provided. Call the following routine to fill in the ”buf” shared
memory buffer.
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: You have to implement the following thread routine.


struct r a n g e {
int s t a r t ;
int end ;
};

void ∗ sum worker ( struct r a n g e i d x r a n g e ) {


// p r i n t f (” In worker from %d t o %d\n ” , i d x r a n g e . s t a r t , i d x r a n g e . end ) ;

/∗
∗ 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 ;

13
4 EXERCISE

...
/∗ 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 ) ) ;
}

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 ;

int w r l o g ( char ∗∗ l o g b u f , char∗ new data ) ;


int f l u s h l o g ( char ∗∗ l o g b u f ) ;

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 (wrlog()) and the flush (flushlog()) will be periodically triggered when a timeout is occured.
int main ( )
{
w r l o g ( data1 ) ;
w r l o g ( data2 ) ;
w r l o g ( data3 ) ;
...
w r l o g ( dataN ) ;
}

In summary, the draft operations of the logger are:


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 un-
predict events, i.e., it can handle a signall SIGU SR1, SIGU SR2 which are introduced in the previous
lab.

14
4 EXERCISE

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
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 ()

15
4 EXERCISE

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
S l o t 2 : 16
S l o t 3 : 15
S l o t 4 : 14
S l o t 5 : 18
S l o t 0 : 25
S l o t 1 : 20
S l o t 2 : 19
S l o t 3 : 21
S l o t 4 : 22
S l o t 5 : 23
S l o t 0 : 27
S l o t 1 : 28
S l o t 2 : 26
S l o t 3 : 24
S l o t 4 : 29
Slot 5: 9

16
Revision History 4 EXERCISE

Revision History

Revision Date Author(s) Description


... ... ...
2 10.2022 PD Nguyen Update lab content, practices and exercises
3 10.2023 PD Nguyen update practices and exercises

17

You might also like