Semaphores ---------- - used to coordinate access to shared resources - require disciplined use by programmers; a programmer must use them properly or the shared resource will not be protected. - a semaphore is an integer with a restricted set of possible operations - operations - creation - must be done in practice in many implementations - initialization - giving a semaphore its initial value - must be done before the processes/threads attempt to apply the wait and signal operations - major operations - wait (or acquire) - if the semaphore value is big enough, reduce it by one - a process/thread does a wait before accessing a shared resource or to wait for any other signal from another process/thread - signal (or release) - increase the semaphore's value by one - a process/thread does a signal after accessing a shared resource or to send any other signal to another process/thread - algorithms for wait and signal on semaphore S wait(S) while (S <= 0) do no-op S := S - 1 signal(S) S := S + 1 - the wait and signal operations must be executed "atomically" in the following sense: - for wait, the value seen for S to leave the while loop must be the same value for S as is used in the S - 1 and no other access to S is permitted before the result is stored back in S - for signal, no other access to the S variable is permitted while S is being incremented. - types of semaphores - binary semaphore (also called mutexes) - has two possible values: 0 and 1 0: resource is in use 1: resource is not in use - counting semaphore values range from n down to 0 n: maximum number of instances of the resource available 0: no more instances of the resources available, i.e., all are in use - producer/consumer problem can be solved with semaphores, as follows Initialization -------------- empty = n, full = 0, mutex = 1 Producer Consumer -------- -------- repeat repeat produce an item wait(full) wait(empty) wait(mutex) wait(mutex) remove item from buffer add item to the buffer signal(mutex) signal(mutex) signal(empty) signal(full) consume item until forever until forever - if the size of the buffer is 1, a simplified version of the producer and consumer algorithms can be used, as shown below: - the two sections of code between the wait(mutex)/signal(mutex) statements form a "critical section" which requires that at most one process be in the critical section at the same time. The mutex semaphore provides the required mutual exclusion for the processes. Initialization -------------- empty = 1, full = 0 Producer Consumer -------- -------- repeat repeat produce an item wait(full) wait(empty) remove item from buffer add item to the buffer signal(empty) signal(full) consume item until forever until forever - the two processes are restricted to strict turn taking. since both can never both be between their wait and signal at the same time, the extra mutex semaphore is not needed. - in the UNIX example given below, empty is renamed "READ" (meaning "buffer has been read") and "MADE" (meaning "a new item has been produced an placed in the buffer") Initialization -------------- READ = 1, MADE = 0 Producer Consumer -------- -------- repeat repeat produce an item wait(MADE) wait(READ) remove item from buffer add item to the buffer signal(READ) signal(MADE) consume item until forever until forever System V UNIX Semaphore Facility -------------------------------- - part of the IPC library; sometimes called IPC semaphores - this library provides semaphores, message queues, and shared memory segments - to see all semaphores, message queues, and shared memory segments, enter hercules[1]% ipcs - a semaphore must be explicitly created using the semget function - a semaphore can be removed using the semctl function with the IPC_RMID option specified (details later) or by using the UNIX command hercules[1]% ipcrm -s where the '-s' option says that a semaphore is to be removed and is a small integer indentifying the semaphore. - in fact, these operations apply to a set of semaphores created at the same time, rather than individual semaphores. Thus, for the first version of the producer-consumer problem, three semaphores would be placed together in a set System Semaphore Table ---------------------- - contains one entry per semaphore set - each entry contains permission information, a pointer to an array (set) of semaphores, the number of semaphores in the set, the time of the last operation on the semaphore set, and the time of the last change to the semaphore set - creating a semaphore set adds an entry to this table - done with semget with the IPC_CREAT option - accessing a semaphore set means looking up the entry in this table - done with semget - in either case, semget returns a semid, which is analogous to a file descriptor - in the implemenation, semid is an index into the system semaphore table, which may actually be part of a combined system IPC table (not sure) - the implementation also uses keys - a key is a value of type key_t, which is actually a long int - an application chooses a unique key for a semaphore set, so that the cooperationg processes will all know about the same semaphore set - the key is mapped into a semid by the semget operation - it is apparently done by some kind of hashing, i.e., semget does not allocate the next available semid (different from open which allocates the next available file descriptor) - creating a key with ftok - ftok is a function that maps a filename (pathname) and integer combination into a key - given the same pathname and integer, the same key is always produced - example #include #include key_t key; key = ftok(".", (int) 'a'); - personally, I can see no benefit from using ftok rather than just choosing an integer key value Semaphore Set - each semaphore in the set has 4 fields: semval - value of the semaphore (integer) sempid - process id of the last process to access the semaphore semncnt - count of process waiting for the semaphore to increase beyond zero. This is the number of processes that have done a wait but not yet been able to continue. semzcnt - count of processes waiting for the semaphore to go to zero. The purpose is obscure. To create a semaphore set ========================= - create a set with a specified key int sem1; key_t ipc_key; ipc_key = ftok(".", 'S'); sem1 = semget(ipc_key, 3, IPC_CREAT | 0666); /* where 3 = number of semaphores in the set */ /* and IPC_CREAT says that the semaphore is to be created */ /* and 0666 says that the protection mode is to be rw-rw-rw- */ /* which is written as ra-ra-ra- for IPC stuff */ - or let the system choose a new key int sem3; sem3 = semget(IPC_PRIVATE, 3, 0600); /* where 3 = number of semaphores in the set */ /* and we don't specify IPC_CREAT because IPC_PRIVATE */ /* always requires the creation of a new set */ - flags (see /usr/include/sys/ipc.h): IPC_CREAT #define IPC_CREAT 0001000 - create a new semaphore set, if one doesn't exist - when used alone, it accesses an existing set with the same key if one already exists (doesn't fail) IPC_EXCL #define IPC_EXCL 0002000 - semget fails if the semaphore set already exists IPC_CREAT | IPC_EXCL 0003000 - create a new semaphore set unless one already exists, in which case semget fails - thus, it is an error to use this to clobber an existing semaphore Obtaining access to a semaphore set =================================== - if the semaphore set was created with a specified key int sem1; key_t ipc_key; ipc_key = ftok(".", 'S'); sem1 = semget(ipc_key, 3, 0); /* the flags are set to 0 because they don't matter */ /* since the semaphore isn't being created */ - if at creation time, we let the system choose a new key using semget(IPC_PRIVATE ...) then the key value can be obtained by the creating process and stored in a file. Later, other processes can obtain the key from the file. struct semid_ds sem_buf; union semun arg; arg.buf = &sem_buf; semctl(sem_id, 0, IPC_STAT, arg); save sem_buf.sem_perm.key in a file