multi-process systems: synchronizationhomes.di.unimi.it/~boccignone/giuseppeboccignone... · }...
TRANSCRIPT
Operating Systems and Distributed Systems
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
• A nonnegative integer variable that can only be changed or tested by these two indivisible (atomic) functions:
- verhoog (increment)
= up = signal =post
- prolaag (probeer te verlagen = try to decrement) = down = wait
Operating Systems and Distributed Systems
Dijkstra Semaphore Definition
V(s): [s = s + 1]
P(s): [while(s == 0) {wait}; s = s - 1]
wake up (signal to get ready) a process blocked on s queue
wait (block) on the s queue
Operating Systems and Distributed Systems
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
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
Implementing Semaphores (1):interrupts
void mos_sem_wait(mos_sem_t *s)
{
handle_t int_handle;
int_handle = mos_disable_ints();
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
mos_enable_ints(int_handle);
}
void mos_sem_post(mos_sem_t *s)
{
handle_t int_handle;
mos_thread_t *thread;
int_handle = mos_disable_ints();
// Post a unit and wake-up the next waiting thread
s->val++;
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);
}
}
Operating Systems and Distributed Systems
Real stuff: Semaphoreswith interrupts in MANTIS OS
P(s) V(s)
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
void mysem_post(mysem_t *s)
{
! pthread_mutex_lock(&s->mutex);
!
! if (!(s->counter++))
! ! pthread_cond_signal(&s->cond);
!
! pthread_mutex_unlock(&s->mutex);
}
Operating Systems and Distributed Systems
Implementing Semaphores (2):mutexes and condition variables
V(s): [s = s + 1]
Analyze mysemaphore.h and mysemaphore.c!
void unlock_mutex(void *m)
{
! //cleanup handler to unlock the mutex
! pthread_mutex_unlock((pthread_mutex_t *)m);
}
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
P(s): [while(s == 0) {wait}; s = s - 1]
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);
}
Operating Systems and Distributed Systems
Implementing Semaphores (2):mutexes and condition variables
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);
}
}
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
Critical Section with Semaphores
Com
pile a
nd r
un p_thraddmysem.c!
Operating Systems and Distributed Systems14
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...
B and D atbarrier
A
B
C
D
All atbarrier
A
B
C
D
Barrier releasesall processes
A
B
C
D
Processes approachingbarrier
A
B
C
D
Operating Systems and Distributed Systems
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
. . .
. . .
V(ready1);
P(ready2);
. . .
. . .
Thread #1
. . .
. . .
V(ready2);
P(ready1);
. . .
. . .
Thread #2
semaphore ready1 = 0;
semaphore ready2 = 0;
Global Variables
#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
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
Com
pile a
nd r
un p_mysembarr.c!
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)
. . .
. . .
V(busy);
P(done);
. . .
. . .
Device driver
. . .
. . .
P(busy);
V(done);
. . .
. . .
Controller
semaphore busy = 0;
semaphore done = 0;
Global Variables
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().
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 toExample 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
#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
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.
28
The producer-consumer problem using Semaphores
#define BSIZE 4 //number of slots in the buffer
#define NUMITEMS 30 //max number of items
#define NUM_THREADS 2
pthread_t tid[NUM_THREADS]; // 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
The producer-consumer problem using Semaphores
Operating Systems and Distributed Systems
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.
• The function semop is used for changing the value of the semaphore (atomic function)
– 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:
int semop(int semid, struct sembuf *sops, unsigned int nsops);
int semget(key_t key, int nsems, int semflg);
struct sembuf {
ushort sem_num; short sem_op;
short sem_flg;};
creating a key:see shared memory
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.
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. This takes on different meanings, depending on whether sem_op is positive, negative, or zero, as shown in the following table:
struct sembuf {
ushort sem_num; short sem_op;
short sem_flg;};
sem_op < 0
sem_op > 0
sem_op == 0
Note that when sem op = "1 the operation is equivalent to a conventional P. Whensem 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 suggests, causes the call to semop() to return with error EAGAIN if it encounters a situation where it would 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.
struct sembuf {
ushort sem_num; short sem_op;
short sem_flg;};
Operating Systems and Distributed Systems
Unix semaphores• The semctl function allows direct control of
semaphore information:
– 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:
int semctl(int semid, int semnum, int command, ... /*arg*/);
union semun {
int val; /* used for SETVAL only */
struct semid_ds *buf; /* used for IPC_STAT and IPC_SET */
ushort *array; /* used for GETALL and SETALL */
};
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 semid_ds structure
pointed to by the buf member of the union semun.
Operating Systems and Distributed Systems
Unix semaphores– The command parameter
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);
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");
}
Operating Systems and Distributed Systems41
Implementing Dijkstra semaphores in Unix
int main( )
{
! int i;
! pid_t pid;
! key_t ! semaphoreKey;
! int semaphoreCount;
!int semaphoreFlag;
! int semaphoreIdent;!
! struct! sembuf * semaphoreOpList;
!
! semaphoreKey = ftok("semsimple.c", 'X')!
! semaphoreCount = 1;
! semaphoreFlag = IPC_CREAT | 0666;
! semaphoreIdent = semget(semaphoreKey, semaphoreCount, semaphoreFlag);
!
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
Example 1: father&son race
creating a key
get semaphore
remove semaphore
} father & child CS
child creation
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 1: father&son raceCom
pile a
nd r
un semsimple.c!
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);
}
Operating Systems and Distributed Systems
Example 2: father&son race
creating a key
get semaphore
remove semaphore
{child CS
father CS {
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
.....
Operating Systems and Distributed Systems
Example: father&son race
Execution without semaphores:
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 straight-forward
• 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 footprint-wise) 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 47
• 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
Message-Based Communication and Synchronisation
Operating Systems and Distributed Systems
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
send message
receive 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
receive message
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
receive message
time time
blocked M
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 Process P2
send message
receive message
time time
blocked
M
reply
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 many-to-many structure
– a one-to-one 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 Systems60
Unix Message passing
Operating Systems and Distributed Systems 61
Unix Message passing
int msgsnd(int msqid, struct msgbuf *msgp,
size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, struct msgbuf *msgp,
size_t msgsz, long msgtyp, int msgflg);
appends a copy of the message pointed to by
msgp to the message queue whose identifier is
specified by msqid.
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.
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
struct msgbuf {
long mtype; /* msg type > 0*/
char mtext[1];/* msg data */!
};
struct msgbuf {
long mtype; /* msg type > 0*/
char mtext[1];/* msg data */!
};
62
The producer-consumer problem with N messages
Producer Consumer
Empty Pool
Full Pool
msgid2
msgid1
63
The producer-consumer problem with N messages
Producer Consumer
struct msgformat {
long pid;
char buf[BSIZE];
! int occupied;
! int nextin, nextout;
} msg;
struct msgformat {
long pid;
char buf[BSIZE];
! int occupied;
! int nextin, nextout;
} msg;
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, msgid2;
pid_t pid;
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, msg.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
}
}66
The producer-consumer problem with N messages
67
The producer-consumer problem with N messages
Compile and run msgproducer.c
and msgconsumer.c!
Operating Systems and Distributed Systems
Some classical problems in process synchronization
• Dinining philosophers
• Readers and writers
• Sleepy barber
Operating Systems and Distributed Systems69
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
Operating Systems and Distributed Systems70
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
71
A nonsolution to the dining philosophers problem
Dining Philosophers: solution
72Solution to dining philosophers problem (part 1)
Dining Philosophers: solution
73
Dining Philosophers: solution
Operating Systems and Distributed Systems
Readers-Writers Problem
Readers
Writers
Operating Systems and Distributed Systems
Readers-Writers Problem (2)
Reader
Shared Resource
ReaderReaderReaderReaderReaderReaderReader
WriterWriterWriterWriterWriterWriterWriter
Operating Systems and Distributed Systems
Readers-Writers Problem (3)
Reader
Shared Resource
ReaderReaderReaderReaderReaderReaderReader
WriterWriterWriterWriterWriterWriterWriter
Operating Systems and Distributed Systems
Readers-Writers Problem (4)
Reader
Shared Resource
ReaderReaderReaderReaderReaderReaderReader
WriterWriterWriterWriterWriterWriter
Writer
78
The readers and writers problem
Operating Systems and Distributed Systems79
The Sleepy Barber Problem
Operating Systems and Distributed Systems
The Sleepy Barber
Waiting Room
Entrance to Waiting
Room (sliding door)
Entrance to Barber’s
Room (sliding door)
Shop Exit
• Barber can cut one person’s hair at a time
• Other customers wait in a waiting room
Operating Systems and Distributed Systems81
The Sleepy Barber Problem