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