Multi-Process Systems: Synchronization Semaphores

advertisement
Multi-Process Systems:
Synchronization
Operating Systems and Distributed Systems
Semaphores
Operating Systems and Distributed Systems
Dijkstra Semaphore
• Invented in the 1960s
• Conceptual OS mechanism, with no specific
implementation defined (could be
enter()/exit())
• Basis of all contemporary OS
synchronization mechanisms
Operating Systems and Distributed Systems
Dijkstra Semaphore Definition
• A nonnegative integer variable that can only be
changed or tested by these two indivisible (atomic)
functions:
- verhoog (increment)
= up = signal =post
V(s): [s = s + 1]
wake up (signal to get
ready) a process blocked
on s queue
- prolaag (probeer te verlagen = try to decrement)
= down = wait
P(s): [while(s == 0) {wait};
s = s - 1]
Operating Systems and Distributed Systems
wait (block) on the s queue
Types of Semaphores
• Binary Semaphore:
– Takes values 0 and 1
– Can be used for mutual exclusion (mutex)
– Can be used for signaling
• Counting Semaphores:
– Takes any nonnegative value
– Typically used to count resources and block
resource consumers when all resources are
busy
Operating Systems and Distributed Systems
Implementing Semaphores (1):
interrupts
class semaphore {
! int counter;
public:
! semaphore(int v = 1) { counter = v;};
! void P(){
! !
disableInterrupts();
! !
while(counter == 0) {
! !
!
enableInterrupts();
! !
!
disableInterrupts();
! !
}
! !
counter--;
! !
enableInterrupts();
! };
!
!
!
!
!
};
void V(){
!
disableInterrupts();
!
counter++;
!
enableInterrupts();
};
Operating Systems and Distributed Systems
Real stuff: Semaphores
with interrupts in MANTIS OS
void mos_sem_wait(mos_sem_t *s)
{
handle_t int_handle;
void mos_sem_post(mos_sem_t *s)
{
handle_t int_handle;
mos_thread_t *thread;
int_handle = mos_disable_ints();
int_handle = mos_disable_ints();
s->val--;
// Post a unit and wake-up the next waiting thread
s->val++;
// If no resources are available then we
wait in the queue
if(s->val < 0) {
mos_thread_t *id;
id = mos_thread_current();
mos_tlist_add(&s->q, id);
mos_thread_suspend_noints(int_handle);
} else
if((thread = mos_tlist_remove(&s->q)) != NULL) {
mos_thread_resume_noints_nodispatch(thread, int_handle);
mos_enable_ints(int_handle);
} else {
mos_enable_ints(int_handle);
}
mos_enable_ints(int_handle);
}
}
V(s)
P(s)
Operating Systems and Distributed Systems
Implementing Semaphores (2):
mutexes and condition variables
typedef struct {
! pthread_mutex_t mutex;
! pthread_cond_t cond;
! int counter;
} mysem_t;
/*_____Function prototypes of a semaphore_*/
void unlock_mutex(void *);
void mysem_init(mysem_t *, int);
void mysem_wait(mysem_t *);
void mysem_post(mysem_t *);
/*____________________________________*/
Operating Systems and Distributed Systems
Implementing Semaphores (2):
mutexes and condition variables
V(s): [s = s + 1]
void mysem_post(mysem_t *s)
{
! pthread_mutex_lock(&s->mutex);
!
! if (!(s->counter++))
! !
pthread_cond_signal(&s->cond);
!
! pthread_mutex_unlock(&s->mutex);
}
Analyze
mysemaphore.h
and
mysemaphore.c!
Operating Systems and Distributed Systems
Implementing Semaphores (2):
mutexes and condition variables
void unlock_mutex(void *m)
{
! //cleanup handler to unlock the mutex
! pthread_mutex_unlock((pthread_mutex_t *)m);
}
P(s): [while(s == 0)
{wait};
s = s - 1]
void mysem_wait(mysem_t *s)
{
! pthread_mutex_lock(&s->mutex);
! while (!s->counter) {
! !
//free the resources that a thread may hold at the time it
//terminates
! !
//PUSH: just before locking the mutex,
//install a cleanup handler to unlock the mutex
! !
pthread_cleanup_push(unlock_mutex,(void *)&s->mutex);
! !
pthread_cond_wait(&s->cond, &s->mutex);
! !
//POP: removes the most recently installed cleanup handler,
//0 does not execute the handler
! !
pthread_cleanup_pop(0);
! }
!
! s->counter--;
!
! pthread_mutex_unlock(&s->mutex);
}
Operating Systems and Distributed Systems
Implementing Semaphores (2):
mutexes and condition variables
void mysem_init(mysem_t *s, int num)
{
! pthread_mutexattr_t m;
! pthread_condattr_t c;
!
! //initializes the semaphore count
! s->counter = num;
!
! //initializes the mutex attribute object m and
//fills it with default values for the attributes
! pthread_mutexattr_init(&m);
! pthread_mutex_init(&s->mutex, &m);
! //destroys a mutex attribute object, which must not be reused
//until it is reinitialized.
! pthread_mutexattr_destroy(&m);
!
! pthread_condattr_init(&c);
! pthread_cond_init(&s->cond, &c);
! pthread_condattr_destroy(&c);
}
Analyze
mysemaphore.h
and
mysemaphore.c!
Operating Systems and Distributed Systems
Critical Section with Semaphores
• Doing a critical section with a semaphore is as
simple as with a lock
semaphore_t mutex = 1;
int shared_variable;
void worker() {
while(1) {
P(mutex);
shared_variable++;
V(mutex);
}
}
Operating Systems and Distributed Systems
Compile and run
p_thraddmysem.c!
Critical Section with Semaphores
int count = 0;
mysem_t mymutex; //Declare
the
semaphore global (outside of any function)
void * ThreadAdd(void *); //function executed by threads
int main(int argc, char * argv[]){
pthread_t tid1, tid2;
!
//Initialize the unnamed semaphore in the main function: initial value is set to 1.
!
mysem_init(&mymutex, 1);
!
pthread_create(&tid1, NULL, ThreadAdd, NULL);!
pthread_create(&tid2, NULL, ThreadAdd, NULL);!
pthread_join(tid1, NULL);!
/* wait for the thread 1 to finish */
pthread_join(tid2, NULL) ;
/* wait for the thread 2 to finish */
if (count < 2 * NITER) printf("\n BOOM! count is [%d], should be %d\n", count, 2*NITER);
else printf("\n OK! count is [%d]\n", count);
!
pthread_exit(NULL);
}
void * ThreadAdd(void * a)
{
int i, tmp;
!
int value;
for(i = 0; i < NITER; i++){!
!
!
!
//entering the critical section: wait on semaphore
!
mysem_wait(&mymutex);!
tmp = count;
/* copy the global count locally */
tmp = tmp+1;
/* increment the local copy */
count = tmp;
/* store the local value into the global count */
!
//exiting the critical section: increments the value of the semaphore and
//wakes up a blocked process waiting
on the semaphore, if any.
!
mysem_post(&mymutex);!
!
!
}
}
Operating Systems and Distributed Systems
Signaling Semaphores: Barriers
•
•
•
•
Used for synchronizing multiple processes
Processes wait at a “barrier” until all in the group arrive
After all have arrived, all processes can proceed
May be implemented using locks and condition variables or...
A
A
B
B
C
C
D
D
Processes approaching
barrier
B and D at
barrier
14
A
A
B
B
C
C
D
D
All at
barrier
Operating Systems and Distributed Systems
Barrier releases
all processes
Signaling Semaphores
• Using a binary semaphore is to signal some event
– A thread waits for an event by calling P
– A thread signals the event by calling V
• This creates a barrier between two threads
Thread #1
Thread #2
...
...
V(ready1);
P(ready2);
...
...
...
...
V(ready2);
P(ready1);
...
...
Global Variables
semaphore ready1 = 0;
semaphore ready2 = 0;
Operating Systems and Distributed Systems
Signaling Semaphores
#define NUM_THREADS 2
mysem_t ready1, ready2;
void *simple1(void *);
void *simple2(void *);
pthread_t tid[NUM_THREADS];
/* array of thread IDs */
int main( int argc, char *argv[] ) {
!
int i, ret;!
!
mysem_init(&ready1, 0);
!
mysem_init(&ready2, 0);!
!
pthread_create(&tid[0], NULL, simple1, NULL);
!
pthread_create(&tid[1], NULL, simple2, NULL);
!
for ( i = 0; i < NUM_THREADS; i++)
!
!
pthread_join(tid[i], NULL);!
!
printf("\nmain() reporting that all %d threads have terminated\n", i);!
!
pthread_exit(NULL);!
} /* main */
void *simple1(void * parm){
!
printf("Thread 1 here, before the barrier.\n");
!
mysem_post(&ready1);
!
mysem_wait(&ready2);
!
printf("Thread 1 after the barrier.\n");
}
void *simple2(void * parm){
!
printf("Thread 2 here, before the barrier.\n");
!
mysem_post(&ready2);
!
mysem_wait(&ready1);
!
printf("Thread 2 after the barrier.\n");
}
Operating Systems and Distributed Systems
Compile and run
p_mysembarr.c!
Signaling Semaphores
host:pthreadsync Bebo$ ./p_mysembarr
Thread 2 here, before the barrier.
Thread 1 here, before the barrier.
Thread 1 after the barrier.
Thread 2 after the barrier.
main() reporting that all 2 threads have
terminated
Operating Systems and Distributed Systems
Signaling Semaphores: Driver/
Controller synchronization
• The semaphore principle is logically used with the busy and done
flags in a controller
• Driver signals controller with a V(busy), then waits for completion
with P(done)
• Controller waits for work with P(busy), then announces completion
with V(done)
Device driver
Controller
...
...
V(busy);
P(done);
...
...
...
...
P(busy);
V(done);
...
...
Global Variables
semaphore busy = 0;
semaphore done = 0;
Operating Systems and Distributed Systems
Signaling Semaphores
• We can use them for requesting a specific sequence of operation
Operating Systems and Distributed Systems
POSIX semaphores
• POSIX semaphores come in two forms:
– named semaphores
– unnamed semaphores.
• A named semaphore is identified by a name of the form /somename.
– Two processes can operate on the same named semaphore by passing the
same name to sem_open().
• creates a new named semaphore or opens an existing named semaphore.
– After the semaphore has been opened, it can be operated on using
sem_post() and sem_wait().
– When a process has finished using the semaphore, it can use sem_close()
to close the semaphore.
– When all processes have finished using the semaphore, it can be removed
from the system using sem_unlink()
•
POSIX named semaphores have kernel persistence: if not removed by
sem_unlink(), a semaphore will exist until the system is shut down.
Operating Systems and Distributed Systems
POSIX semaphores
• POSIX semaphores come in two forms:
– named semaphores
– unnamed semaphores.
• An unnamed semaphore does not have a name. Instead the
semaphore is placed in a region of memory that is shared between
multiple threads (a thread-shared semaphore) or processes (a
process-shared semaphore).
• A thread-shared semaphore is placed in an area of memory shared between
by the threads of a process, for example, a global variable.
• A process-shared semaphore must be placed in a shared memory region (e.g.,
a System V shared memory segment created using semget(), or a POSIX
shared memory object built created using shm_open()).
– Before being used, an unnamed semaphore must be initialised using
sem_init().
– It can then be operated on using sem_post() and sem_wait().
– When the semaphore is no longer required, and before the memory in
which it is located is deallocated, the semaphore should be destroyed
using sem_destroy().
Operating Systems and Distributed Systems
POSIX semaphores
All POSIX semaphore functions and types are prototyped or defined in
semaphore.h. To define a semaphore object, use
sem_t sem_name;
To initialize a semaphore, use sem_init():
int sem_init(sem_t *sem, int pshared, unsigned int value);
!•! sem points to a semaphore object to initialize
!•! pshared is a flag indicating whether or not the semaphore should be shared
with fork()ed processes. LinuxThreads does not currently support shared
semaphores
!•! value is an initial value to set the semaphore to
Example of use:
sem_init(&sem_name, 0, 10);
Operating Systems and Distributed Systems
POSIX semaphores
To wait on a semaphore, use sem_wait:
int sem_wait(sem_t *sem);
Example of use:
sem_wait(&sem_name);
! •! If the value of the semaphore is negative, the calling process blocks; one of
the blocked processes wakes up when another process calls sem_post.
To increment the value of a semaphore, use sem_post:
int sem_post(sem_t *sem);
Example of use:
sem_post(&sem_name);
! •! It increments the value of the semaphore and wakes up a blocked process
waiting on the semaphore, if any.
Operating Systems and Distributed Systems
POSIX semaphores
To find out the value of a semaphore, use
int sem_getvalue(sem_t *sem, int *valp);
! •! gets the current value of sem and places it in the location pointed to by
valp
Example of use:
int value;
sem_getvalue(&sem_name, &value);
printf("The value of the semaphors is %d\n", value);
To destroy a semaphore, use
int sem_destroy(sem_t *sem);
! •! destroys the semaphore; no threads should be waiting on the semaphore if
its destruction is to succeed.
Example of use:
sem_destroy(&sem_name);
Operating Systems and Distributed Systems
POSIX semaphores: example
#define NITER 1000000
int count = 0;
sem_t mymutex; //Declare the semaphore global (outside of any function)
void * ThreadAdd(void *); //function executed by threads
int main(int argc, char * argv[]){
pthread_t tid1, tid2;!
!
//Initialize the unnamed semaphore in the main function: initial value is set to 1.
// Note the second argument: passing zero denotes that the semaphore is shared between threads (and
// not processes).
!
sem_init(&mymutex, 0, 1);
!
pthread_create(&tid1, NULL, ThreadAdd, NULL);
pthread_create(&tid2, NULL, ThreadAdd, NULL);
pthread_join(tid1, NULL);!
/* wait for the thread 1 to finish */
pthread_join(tid2, NULL);
/* wait for the thread 2 to finish */
if (count < 2 * NITER) printf("\n BOOM! count is [%d], should be %d\n", count, 2*NITER);
else printf("\n OK! count is [%d]\n", count);
!
// destroys the semaphore; no threads should be waiting on the semaphore if its destruction is to succeed
!
sem_destroy(&mymutex);
pthread_exit(NULL);
}
void * ThreadAdd(void * a){
int i, tmp;
!
int value;
for(i = 0; i < NITER; i++){!
!
!
!
//entering the critical section: wait on semaphore
!
sem_wait(&mymutex);!
!
tmp = count;
/* copy the global count locally */
tmp = tmp+1;
/* increment the local copy */
count = tmp;
/* store the local value into the global count */
!
//exiting the critical section:
sem_post(&mymutex);!
!
}
}
Operating Systems and Distributed Systems
POSIX semaphores: example
Compile and run p_thraddsem.c!
Or try (for MacOS) the
platform independent code in
p_thraddsem2.c!
Operating Systems and Distributed Systems
Critical Section with Semaphores
• General semaphores represent "available
resources" that can be acquired by multiple threads
at the same time until the resource pool is empty.
• Additional threads must then wait until the
required number of resources are available again.
• Semaphores are very efficient, as they allow
simultaneous access to resources.
• But improper use often leads to thread starvation,
or to deadlock---where two threads block each
other indefinitely, each one waiting for a resource
the other thread has currently locked.
Operating Systems and Distributed Systems
The producer-consumer problem using Semaphores
28
The producer-consumer problem using Semaphores
#define BSIZE 4
#define NUMITEMS 30
#define NUM_THREADS 2
pthread_t tid[NUM_THREADS];
//number of slots in the buffer
//max number of items
// array of thread IDs
typedef struct {
! char buf[BSIZE];
! int occupied;
! int nextin, nextout;
#ifdef __APPLE__
! semaphore_t mutex ;//control access to critical region
! semaphore_t more ; //counts full buffer slots
! semaphore_t less ; //counts empty buffer slots
#else
! sem_t mutex;//control access to critical region
! sem_t more; //counts full buffer slots
! sem_t less; //counts empty buffer slots
#endif
!
} buffer_t;
buffer_t buffer;
29
The producer-consumer problem using Semaphores
int main( int argc, char *argv[] )
{
! int i;
!
! _sem_create(&(buffer.mutex), 1);
! _sem_create(&(buffer.more), 0);
! _sem_create(&(buffer.less), BSIZE);
!
! pthread_create(&tid[1], NULL, consumer, NULL);
! pthread_create(&tid[0], NULL, producer, NULL);
! for ( i = 0; i < NUM_THREADS; i++)
! !
pthread_join(tid[i], NULL);
!
! printf("\nmain() reporting that all %d threads have terminated\n", i);
!
! _sem_destroy(&(buffer.mutex));
! _sem_destroy(&(buffer.more));
! _sem_destroy(&(buffer.less));
! return 0;
!
} /* main */
30
The producer-consumer problem using Semaphores
void *producer(void * parm)
{
!
char item[NUMITEMS]="IT'S A SMALL WORLD, AFTER ALL."; // items to be put in buffer
!
int i;
!
!
printf("producer started.\n");
!
!
for(i=0;i<NUMITEMS;i++) { // produce an item, one character from item[]
!
!
!
!
if (item[i] == '\0') break; // Quit if at end of string.
!
!
!
!
if (buffer.occupied >= BSIZE) printf("producer waiting.\n");
!
!
_sem_wait(&(buffer.less));!
//decrement empty count
!
!
_sem_wait(&(buffer.mutex));!
//enter critical region
!
!
printf("producer executing.\n");
!
!
!
!
buffer.buf[buffer.nextin++] = item[i];//put new item in buffer
!
!
buffer.nextin %= BSIZE;
!
!
buffer.occupied++;!
!
//items in buffer
!
!
!
!
_sem_signal(&(buffer.mutex));!
//leave critical region
!
!
_sem_signal(&(buffer.more));!
//signals the consumer and increments full count
}
!
printf("producer exiting.\n");
!
pthread_exit(0);
}
31
The producer-consumer problem using Semaphores
void *consumer(void * parm)
{
!
char item;
!
int i;
!
!
printf("consumer started.\n");
!
!
for(i=0;i<NUMITEMS;i++){ // consume an item, one character from buffer
!
!
if (buffer.occupied <= 0) printf("consumer waiting.\n");
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
}
!
_sem_wait(&(buffer.more));!
//decrement full count
!
_sem_wait(&(buffer.mutex));!
//enter critical region
!
!
printf("consumer executing.\n");
!
!
item = buffer.buf[buffer.nextout++];//take item from buffer!
!
!
buffer.nextout %= BSIZE;
!
buffer.occupied--;
!
!
_sem_signal(&(buffer.mutex));!
//leave critical region
!
_sem_signal(&(buffer.less));!
//signals the producer and increments empty count
!
!
printf("%c\n",item);! !
//prints the item
!
}
printf("consumer exiting.\n");
pthread_exit(0);
32
POSIX semaphores: example
Compile and run p_boundsem.c!
Operating Systems and Distributed Systems
Unix semaphores
• All the UNIX semaphore functions operate
on arrays of general semaphores, rather
than a single binary semaphore
• The semaphore function definitions are:
#include <sys/sem.h>
int semctl(int semid, int semnum,
int cmd, ... /*arg*/);
int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, unsigned int nsops);
Operating Systems and Distributed Systems
Unix semaphores
• The semget function creates a new semaphore or obtains the
semaphore key of an existing semaphore.
creating a key:
int semget(key_t key, int nsems, int semflg);
• The function semop is used for changing the value of the
semaphore (atomic function)
see shared
memory
int semop(int semid, struct sembuf *sops, unsigned int nsops);
– The first parameter, sem_id, is the semaphore identifier, as
returned from semget
– The second parameter, sops, is a pointer to an array of
structures, each of which will have at least the following
members:
struct sembuf {
ushort sem_num;
short sem_op;
short sem_flg;
};
Operating Systems and Distributed Systems
Unix semaphores
struct sembuf {
ushort sem_num;
short sem_op;
short sem_flg;
};
– sem_num is the number of the semaphore in the set that
you want to manipulate.
– sem_op is what you want to do with that semaphore. This
takes on different meanings, depending on whether sem_op
is positive, negative, or zero, as shown in the following table:
sem_op < 0
sem_op > 0
sem_op == 0
Allocate resources. Block the calling process until the value of the
semaphore is greater than or equal to the absolute value of sem_op.
(That is, wait until enough resources have been freed by other
processes for this one to allocate.) Then add (effectively subtract,
since it's negative) the value of sem_op to the semaphore's value.
Release resources. The value of sem_op is added to the semaphore's
value.
This process will wait until the semaphore in question reaches 0.
Note that when sem op = "1 the operation is equivalent to a conventional P. When
sem op = +1 it is equivalent to a conventional V . Hence the capabilities of UNIX semaphores
are a superset of those of Dijkstra!s semaphores
Operating Systems and Distributed Systems
Unix semaphores
– sem_num is the number of the semaphore in the set that
you want to manipulate.
– sem_op is what you want to do with that semaphore.
– sem_flg field which allows the program to specify flags the
further modify the effects of the semop() call.
• One of these flags is IPC_NOWAIT which, as the name
struct sembuf {
suggests, causes the call to semop() to return with error
ushort sem_num;
short sem_op; EAGAIN if it encounters a situation where it would
short sem_flg; normally block. This is good for situations where you
};
might want to "poll" to see if you can allocate a
resource.
• Another very useful flag is the SEM_UNDO flag. This
causes semop() to record, in a way, the change made to
the semaphore. When the program exits, the kernel will
automatically undo all changes that were marked with
the SEM_UNDO flag.
Operating Systems and Distributed Systems
Unix semaphores
• The semctl function allows direct control of
semaphore information:
int semctl(int semid, int semnum,
int command, ... /*arg*/);
– The command parameter is the action to take
and a fourth parameter, if present, is a union
semun, which must have at least the following
members:
union semun {
int val;
struct semid_ds *buf;
ushort *array;
};
/* used for SETVAL only */
/* used for IPC_STAT and IPC_SET */
/* used for GETALL and SETALL */
Operating Systems and Distributed Systems
Unix semaphores
– The command parameter
SETVAL
Set the value of the specified semaphore to the value in the val member of the passed-in
union semun.
GETVAL
Return the value of the given semaphore.
SETALL
Set the values of all the semaphores in the set to the values in the array pointed to by the
array member of the passed-in union semun. The semnum parameter to semctl() isn't
used.
GETALL
Gets the values of all the semaphores in the set and stores them in the array pointed to
by the array member of the passed-in union semun. The semnum parameter to semctl()
isn't used.
IPC_RMID
Remove the specified semaphore set from the system. The semnum parameter is ignored.
IPC_STAT
Load status information about the semaphore set into the struct
pointed to by the buf member of the union semun.
semid_ds
structure
Operating Systems and Distributed Systems
Unix semaphores
• The semctl function is used to destroy a
sem
int semid;
.
.
semid = semget(...);
.
.
semctl(semid, 0, IPC_RMID);
Operating Systems and Distributed Systems
Implementing Dijkstra
semaphores in Unix
void V(int sid){
! struct sembuf sb;
! sb.sem_num=0;
!sb.sem_op=1; //Release resources. The value of sem_op is added to the
//semaphore's value.
! sb.sem_flag =SEM_UNDO; //record the change made to the semaphore.
! if((semop(sid, &sb,1)) == -1)
! !
puts("semop error");
}
void P(int sid){
! struct sembuf sb;
! sb.sem_num=0;
! sb.sem_op= -1;//wait until enough resources have been freed to allocate
! sb.sem_flag =SEM_UNDO; //record the change made to the semaphore.
! if((semop(sid, &sb,1)) == -1)
! !
puts("semop error");
}
41
Operating Systems and Distributed Systems
Example 1: father&son race
int
{
!
!
!
!
!
!
!
!
!
!
!
!
main( )
int i;
pid_t pid;
key_t ! semaphoreKey;
int
semaphoreCount;
!int
semaphoreFlag;
int
semaphoreIdent;!
struct! sembuf * semaphoreOpList;
semaphoreKey =
semaphoreCount
semaphoreFlag
semaphoreIdent
ftok("semsimple.c", 'X')!
= 1;
= IPC_CREAT | 0666;
= semget(semaphoreKey, semaphoreCount, semaphoreFlag);
creating a key
get semaphore
child creation
pid=fork( );
!/***********************/
!
P(semaphoreIdent);
!
printf("\n");
!
for(i=1;i<100; i++){
!
!
printf(". ");
!
}
!
printf("\n\n");
! V(semaphoreIdent);
/***********************/
!
if(pid){
!
!
/* The father created the semaphore, the father destroys it! */!
!
!
wait(NULL);
!
semctl(semaphoreIdent, 0, IPC_RMID,0);
!
!
printf("Father process destroyed semaphore\n");
}
!
return 0;
}
Operating Systems and Distributed Systems
}
father & child CS
!
remove semaphore
Compile and run
semsimple.c!
Example 1: father&son race
host:Lez20_ex Bebo$ ./semsimple
Successful semaphore creation
Semaphore identity: 458758
Process 939 gained lock on semaphore
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . .
. . . . . .
. . . . . .
.
Process 939 released lock on semaphore
Semaphore identity: 458758
Process 940 gained lock on semaphore
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . .
. . . . . .
. . . . . .
.
Process 940 released lock on semaphore
Father process destroyed semaphore
Operating Systems and Distributed Systems
Example 2: father&son race
void main(int argc, char *argv[]){
!
int i, j, status, num_comm;
!
key_t key;
!
!
if(argc<2) perror("Error"); exit(1);!
!
num_comm= atoi(argv[1]);
!
if ((key = ftok("semdemo.c", 'J')) == -1) {
perror("ftok"); exit(1);
}!
!
!
sid = semget(key, 1, 0660 | IPC_CREAT);
!
V(sid);
!
if(fork()==0){
!
!
for(i=0; i<num_comm;i++){
!
!
!
/* race */
!
!
!
P(sid);
!
!
!
printf("Child_Communication %d\n",i);
!
!
!
for(j=0; j<20; j++) printf("in child critical session %d\n",j);
!
!
!
V(sid);!
!
!
!
!
}
!
!
exit(0);
!
}!
!
for(i=0; i<num_comm;i++){
!
!
P(sid);
!
!
printf("Father_Communication %d\n",i);
!
!
for(j=0; j<20; j++) printf("in father critical session %d\n",j);
!
!
V(sid);
!
}
!
wait(&status);
!
semctl(sid, 0, IPC_RMID,0);
}
creating a key
get semaphore
child CS
father CS
{
{
remove semaphore
Operating Systems and Distributed Systems
Example: father&son race
Execution without semaphores:
host:Lez20_MP_IPC Bebo$ ./semnodemo 30
....
Father_Communication 3
in father critical session 0
in father critical session 1
in father critical session 2
Child_Communication 0
in father critical session 3
in child critical session 0
in father critical session 4
in child critical session 1
in father critical session 5
in child critical session 2
in father critical session 6
.....
Compile and run
semdemo.c
and
semnodemo.c!
Operating Systems and Distributed Systems
Difference between the System V and
POSIX semaphore implementations
•
One marked difference is that in System V you can control how much the semaphore count can be
increased or decreased; whereas in POSIX, the semaphore count is increased and decreased by 1.
•
POSIX semaphores do not allow manipulation of semaphore permissions, whereas System V
semaphores allow you to change the permissions of semaphores to a subset of the original
permission.
•
Initialization and creation of semaphores is atomic (from the user's perspective) in POSIX
semaphores.
•
From a usage perspective, System V semaphores are clumsy, while POSIX semaphores are straightforward
•
The scalability of POSIX semaphores (using unnamed semaphores) is much higher than System V
semaphores. In a user/client scenario, where each user creates her own instances of a server, it would
be better to use POSIX semaphores.
•
System V semaphores, when creating a semaphore object, creates an array of semaphores whereas
POSIX semaphores create just one. Because of this feature, semaphore creation (memory footprintwise) is costlier in System V semaphores when compared to POSIX semaphores.
•
POSIX semaphores provide a mechanism for process-wide semaphores rather than system-wide
semaphores. So, if a developer forgets to close the semaphore, on process exit the semaphore is
cleaned up. In simple terms, POSIX semaphores provide a mechanism for non-persistent semaphores.
Operating Systems and Distributed Systems
Message-Based Communication
and Synchronisation
• Synchronize by exchanging messages
• Two primitives:
– Send: send a message
– Receive: receive a message
– Both may specify a “channel” to use
• Issue: how does the sender know the
receiver got the message?
• Issue: authentication
Operating Systems and Distributed Systems
47
Message-Based Communication and
Synchronisation
• Use of a single construct for both synchronisation and
communication
• Three issues:
– the model of synchronisation
– the method of process naming
– the message structure
Process P1
Process P2
receive message
send message
time
time
Operating Systems and Distributed Systems
Process Synchronisation
• Variations in the process synchronisation model
arise from the semantics of the send operation
• Asynchronous (or no-wait) (e.g. POSIX)
– Requires buffer space. What happens when the buffer is
full?
Process P1
Process P2
send message
message
receive message
time
time
Operating Systems and Distributed Systems
Process Synchronisation
• Synchronous (e.g. CSP, occam2)
– No buffer space required
– Known as a rendezvous
Process P1
Process P2
send message
blocked
time
M
receive message
time
Operating Systems and Distributed Systems
Process Synchronisation
• Remote invocation (e.g. Ada)
– Known as an extended rendezvous
• Analogy:
– The posting of a letter is an asynchronous send
– A telephone is a better analogy for synchronous
communication
Process P1
send message
Process P2
M
receive message
blocked
reply
time
time
Operating Systems and Distributed Systems
Asynchronous and Synchronous
Sends
• Asynchronous communication can implement
synchronous communication:
P1! !
!
!
P2
asyn_send (M)! !
!
wait (M)
wait (ack)!
!
!
!
asyn_send (ack)
• Two synchronous communications can be used to
construct a remote invocation:
P1 ! !
!
!
P2
syn_send (message)
wait (message)
wait (reply)!
!
!
...
! ! !
!
!
!
construct reply
! ! !
!
!
!
...
Operating Systems and Distributed Systems
Disadvantages of Asynchronous
Send
• Potentially infinite buffers are needed to store unread
messages
• More communications are needed with the asynchronous
model, hence programs are more complex
• It is more difficult to prove the correctness of the
complete system
• Where asynchronous communication is desired with
synchronised message passing then buffer processes can
easily be constructed; however, this is not without cost
Operating Systems and Distributed Systems
Process Naming
• Two distinct sub-issues
– direction versus indirection
– symmetry
• With direct naming, the sender explicitly names the receiver:
! ! send <message> to <process-name>
• With indirect naming, the sender names an intermediate entity
(e.g. a channel, mailbox, link or pipe):
! ! send <message> to <mailbox>
• With a mailbox, message passing can still be synchronous
• Direct naming has the advantage of simplicity, whilst indirect
naming aids the decomposition of the software; a mailbox can
be seen as an interface between parts of the program
Operating Systems and Distributed Systems
Process Naming
• A naming scheme is symmetric if both sender and
receiver name each other (directly or indirectly)
send <message> to <process-name>
wait <message> from <process-name>
send <message> to <mailbox>
wait <message> from <mailbox>
• It is asymmetric if the receiver names no specific source
but accepts messages from any process (or mailbox)
wait <message>
• Asymmetric naming fits the client-server paradigm
• With indirect the intermediary could have:
– a many-to-one structure
– a one-to-one structure
– a many-to-many structure
– a one-to-many
Operating Systems and Distributed Systems
Message Structure
• A language usually allows any data object of
any defined type (predefined or user) to be
transmitted in a message
• Need to convert to a standard format for
transmission across a network in a
heterogeneous environment
• OS allow only arrays of bytes to be sent
Operating Systems and Distributed Systems
POSIX/Unix Message Queues
• POSIX/Unix supports asynchronous, indirect message
passing through the notion of message queues
• A message queue can have many readers and many writers
• Priority may be associated with the queue
• Intended for communication between processes (not
threads)
• Message queues have attributes which indicate their
maximum size, the size of each message, the number of
messages currently queued etc.
• An attribute object is used to set the queue attributes when
the queue is created
Operating Systems and Distributed Systems
POSIX Message Queues
• Message queues are given a name when they are created
• To gain access to the queue, requires an mq_open name
• mq_open is used to both create and open an already existing
queue (also mq_close and mq_unlink)
• Sending and receiving messages is done via mq_send and
mq_receive
• Data is read/written from/to a character buffer.
• If the buffer is full or empty, the sending/receiving process is
blocked unless the attribute O_NONBLOCK has been set for the
queue (in which case an error return is given)
• If senders and receivers are waiting when a message queue
becomes unblocked, it is not specified which one is woken up
unless the priority scheduling option is specified
Operating Systems and Distributed Systems
POSIX Message Queues
• A process can also indicate that a signal should be sent to
it when an empty queue receives a message and there are
no waiting receivers
• In this way, a process can continue executing whilst waiting
for messages to arrive or one or more message queues
• It is also possible for a process to wait for a signal to
arrive; this allows the equivalent of selective waiting to be
implemented
• If the process is multi-threaded, each thread is considered
to be a potential sender/receiver in its own right
Operating Systems and Distributed Systems
Unix Message passing
60
Operating Systems and Distributed Systems
Unix Message passing
appends a copy of the message pointed to by
msgp to the message queue whose identifier is
specified by msqid.
int msgsnd(int msqid, struct msgbuf *msgp,
size_t msgsz, int msgflg);
reads a message from the message queue specified by
msqid into the msgbuf pointed to by the msgp
argument, removing the read message from the queue.
ssize_t msgrcv(int msqid, struct msgbuf *msgp,
size_t msgsz, long msgtyp, int msgflg);
struct msgbuf {
long mtype; /* msg type > 0*/
char mtext[1];/* msg data */!
};
struct msgbuf {
long mtype; /* msg type > 0*/
char mtext[1];/* msg data */!
};
int msqid
= msgget(key_t key, int msgflg);
returns the message queue identifier associated to the value of the key argument.
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
performs the control operation specified by cmd on the message queue with identifier msqid
Operating Systems and Distributed Systems
61
The producer-consumer problem with N messages
Empty Pool
Producer
Consumer
Full Pool
62
The producer-consumer problem with N messages
msgid1
struct msgformat {
long pid;
char buf[BSIZE];
!
int occupied;
!
int nextin, nextout;
} msg;
Producer
Consumer
struct msgformat {
long pid;
char buf[BSIZE];
!
int occupied;
!
int nextin, nextout;
} msg;
msgid2
63
The producer-consumer problem with N messages
64
The producer-consumer problem with N messages
int main() {
!
char item[NUMITEMS]="IT'S A SMALL WORLD, AFTER ALL."; // items to be put in buffer
!
int i;
pid_t pid;
struct msqid_ds *buf;
!
int msgid1, msgid2;
!
if((msgid1=msgget(MSGKEY, 0777|IPC_CREAT))==-1) perror("Error queue1");
!
if((msgid2=msgget(MSGKEY2, 0777|IPC_CREAT))==-1) perror("Error queue2");
!
!
pid=getpid();
!
!
for(i=0;i<NUMITEMS;i++) { // produce an item, one character from item[]
!
!
if (item[i] == '\0') break; /* Quit if at end of string. */
!
!
!
!
//wait for an empty to arrive
!
!
msgrcv(msgid1,&msg,sizeof(msg),0,0);
!
!
printf("PRODUCER %d: message received from process %ld\n", pid, msg.pid);
!
!
!
!
//construct a message to send
!
!
msg.buf[msg.nextin++] = item[i];!
//put new item in buffer
!
!
msg.nextin %= BSIZE;
!
!
msg.occupied++;
!
!
msg.pid=pid;
!
!
!
!
!
}
!
//send message
!
msgsnd(msgid2,&msg,sizeof(msg),0);
!
printf("PRODUCER %d: message sent \n", pid);
}
msgctl(msgid1,IPC_RMID,buf); //remove queue1
msgctl(msgid2,IPC_RMID,buf); //remove queue2!
65
The producer-consumer problem with N messages
int main() {
!
char item;
!
int i;
int msgid1,
pid_t pid;
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
!
}
msgid2;
if((msgid1=msgget(MSGKEY, 0777))==-1) perror("Errore coda1");
if((msgid2 = msgget(MSGKEY2, 0777))==-1) perror("Errore coda2");
pid=getpid();
!
//construct an empty message to send
msg.nextout = 0; msg.occupied = 0; msg.pid=pid;!
msgsnd(msgid1,&msg,sizeof(msg),0);
printf("CONSUMER %d: empty message sent \n",pid);
for(i=0;i<NUMITEMS;i++){ //
!
//get message containing item
!
msgrcv(msgid2,&msg,sizeof(msg),0,0);
!
printf("CONSUMER %d: message received from process %ld\n",pid,
!
!
//extract item from message!
!
!
item = msg.buf[msg.nextout++];!
//take item from buffer!
!
msg.nextout %= BSIZE;
!
msg.occupied--;
!
!
//send message
!
msg.pid=pid;
!
msgsnd(msgid1,&msg,sizeof(msg),0);
!
printf("CONSUMER %d: message sent \n",pid);
!
!
printf("%c\n",item);!
//prints the item
}
msg.pid);
!
66
The producer-consumer problem with N messages
Compile and run
msgproducer.c
and
msgconsumer.c!
67
Some classical problems in
process synchronization
• Dinining philosophers
• Readers and writers
• Sleepy barber
Operating Systems and Distributed Systems
Dining Philosophers
• N philosophers around a
table
– All are hungry
– All like to think
• N chopsticks available
– 1 between each pair of
philosophers
• Philosophers need two
chopsticks to eat
• Philosophers alternate
between eating and thinking
• Goal: coordinate use of
chopsticks
69
Operating Systems and Distributed Systems
Dining Philosophers: solution
• Use a semaphore for each chopstick
• A hungry philosopher
– Gets lower, then higher numbered chopstick
– Eats
– Puts down the chopsticks
• Potential problems?
– Deadlock
– Fairness
70
Operating Systems and Distributed Systems
Dining Philosophers: solution
A nonsolution to the dining philosophers problem
71
Dining Philosophers: solution
Solution to dining philosophers problem (part 1)
72
Dining Philosophers: solution
73
Readers-Writers Problem
Writers
Readers
Operating Systems and Distributed Systems
Readers-Writers Problem (2)
Reader
Reader
Reader
Reader
Reader
Reader
Reader
Reader
Writer
Writer
Writer
Writer
Writer
Writer
Writer
Shared Resource
Operating Systems and Distributed Systems
Readers-Writers Problem (3)
Writer
Writer
Writer
Writer
Writer
Writer
Writer
Reader
Reader
Reader
Reader
Reader
Reader
Reader
Reader
Shared Resource
Operating Systems and Distributed Systems
Readers-Writers Problem (4)
Reader
Reader
Reader
Reader
Reader
Reader
Reader
Reader
Writer
Writer
Writer
Writer
Writer
Writer
Writer
Shared Resource
Operating Systems and Distributed Systems
The readers and writers problem
78
The Sleepy Barber Problem
79
Operating Systems and Distributed Systems
The Sleepy Barber
• Barber can cut one person’s hair at a time
• Other customers wait in a waiting room
Entrance to Waiting
Room (sliding door)
Shop Exit
Waiting Room
Entrance to Barber’s
Room (sliding door)
Operating Systems and Distributed Systems
The Sleepy Barber Problem
81
Operating Systems and Distributed Systems
Download