Conceptually, a semaphore is a data structure that is shared by several processes.[1] Semaphores are most often used to synchronize operations when multiple processes access a common, non-shareable resource. By using semaphores, we attempt to avoid starvation (which occurs when a process is habitually denied access to a resource it needs) and deadlock (which occurs when two or more processes each hold a resource that the other needs while waiting for the other process to release its resource). When used to synchronize the access to a resource, a semaphore is initially set to the number of available resources. Each time a process wants to obtain the resource, the associated semaphore is tested. A positive, nonzero semaphore value indicates the resource is available. To indicate it has gained access to the resource, the process decrements the semaphore. For events to progress correctly, the test and decrement operation on the semaphore must be atomic (i.e., noninterruptible/indivisible). If the tested semaphore is zero, indicating the resource is not available, the requesting process must wait. When a process is finished with a semaphore-associated resource, the process indicates the return of the resource by incrementing the semaphore. Once a resource is returned, other processes that have been waiting for the resource are notified by the system. Semaphores that control access to a single resource, taking the value of 0 (resource is in use) or 1 (resource is available), are often called binary semaphores.[2] Semaphores controlling access to multiple resources, thus assuming a range of nonnegative values, are frequently called counting semaphores.
E. W. Dijkstra (1965) did much of the early work describing the use of semaphores to coordinate access to shared resources. Most college-level operating systems textbooks—for example, Silberschatz and Peterson (1989), Tanenbaum (2001), Nutt (2002), Stallings (2001), and Deitel (1990)—contain excellent discussions on the theory and use of semaphores for process synchronization.
Implementation-wise, a semaphore is a nonnegative integer that is stored in the kernel. Access to the semaphore is provided by a series of semaphore system calls. The semaphore system calls assure the user the test and decrement operations on the semaphore will be atomic. Likewise, the semaphore system calls, by default, cause the invoking process to block if the semaphore value indicates the resource is not available (i.e., the semaphore is a 0). When the resource becomes available and the semaphore becomes nonzero, the system notifies the queued, waiting processes of this event. To increase their flexibility, in UNIX semaphores are generated as sets (arrays) consisting of one or more semaphores. Operations acting upon individual semaphores within the set or upon the entire semaphore set are provided.
Before a semaphore set can be used, it must be created. The creation of the semaphore set generates a unique data structure that the system uses to identify and manipulate the semaphores. The definition of system semaphore data structure is found in the system-dependent include file <bits/sem.h>
. This file is not directly referenced by the programmer, since the standard include file <sys/sem.h>
includes this file.
struct semid_ds { struct ipc_perm sem_perm; /* operation permission struct */ __time_t sem_otime; /* last semop() time */ unsigned long int __unused1; __time_t sem_ctime; /* last time changed by semctl() */ unsigned long int __unused2; unsigned long int sem_nsems; /* number of semaphores in set */ unsigned long int __unused3; unsigned long int __unused4; };
In keeping with its origins, and for System V compatibility, the semid_ds
structure is also defined in <linux/sem.h>
as
struct semid_ds { struct ipc_perm sem_perm; /* permissions .. see ipc.h */ __kernel_time_t sem_otime; /* last semop time */ __kernel_time_t sem_ctime; /* last change time */ struct sem *sem_base; /* ptr to first semaphore in array */ struct sem_queue *sem_pending; /* pending operations to be processed */ struct sem_queue **sem_pending_last;/* last pending operation */ struct sem_undo *undo; /* undo requests on this array */ unsigned short sem_nsems; /* no. of semaphores in array */ };
As with message queues, there is a bit of a disconnect between the way we view and discuss semaphores and the way they may actually be implemented at a system level. Additional system-specific details can be found in the source code for semaphore implementation in the kernel source directory /usr/src/linux-XX.XX.XX/ipc
(where XX
is the appropriate version number). On our system the files sem.c
and util.h
contain additional semaphore implementation details.
Using the <linux/sem.h>
definition as our reference, the system semaphore data structure semid_ds
contains a permission structure of type ipc_perm
, which is used to specify the access permissions for the semaphore set. The access permission structure is followed by two time members. These store the time of the last operation on the semaphore (sem_otime
) and the time of its last modification (sem_ctime
). The next member is a reference, sem_base
, to an array (set) of structures of type sem.
The sem
structure contains the semaphore value and the ID of the last process to operate on the semaphore. Here is the definition of a sem
structure:
struct sem { int semval; /* current value */ int sempid; /* pid of last operation */ };
Following the pointer to the base of the semaphore array are three additional pointers. The sem_pending
member references a linked list (treated as a queue) of pending semaphore operations. Normally, semaphore operations are done immediately, so requests are only added to this list if for some reason the request cannot processed immediately. The sem_pending_last
member references the end of the same list. The sem_undo
member references a list of undo operations. These operations, stored when a semaphore operation sets the SEM_UNDO flag, can be used to undo requested semaphore operations to return the semaphore to its previous state. The kernel uses the undo list information to reverse semaphore operations when a process exits without releasing its allocated semaphores. This action helps reduce the chance of deadlock. The system semaphore data structure also keeps track of the number of semaphores in the set (sem_nsems
).
A conceptual arrangement of a system semaphore structure for a newly allocated set of three semaphores is shown in Figure 7.1.
To create a semaphore or gain access to one that exists, the semget
system call, shown in Table 7.1, is used.
Table 7.1. Summary of the semget
System Call.
Include File(s) |
<sys/types.h> <sys/ipc.h> <sys/sem.h> | Manual Section | 2 | |
Summary |
| |||
Return | Success | Failure | Sets | |
The semaphore identifier | −1 | Yes |
The semget
system call takes three arguments. The first argument, key
, is used by the system to generate a unique semaphore identifier. The second argument, nsems
, is the number of semaphores in the set. The system uses the nsems
value when allocating the array of sem
structures. Remember, as with all arrays in C/C++, the array of sem
structures that represents the set of semaphores is indexed starting at 0. The nsems
value should be less than or equal to the SEMMSL value, which sets the upper boundary for the number of semaphores for a given semaphore ID. The third argument, semflg
, is used to specify access permission and/or special creation conditions. The low-order bits of the semflg
value are used to specify the access permissions for the owner, group, and other. Read and write permissions control reading and alteration of the semaphore; execute permission settings are ignored. The flags IPC_CREAT and IPC_EXCL may be OR
ed with the permission value.
If the semget
system call is successful, a nonnegative integer, the semaphore identifier, is returned. If the value for key
is IPC_PRIVATE or the value for key
does not have a semaphore identifier associated with it, and IPC_CREAT has been specified, a new set of semaphores is created. When created, the semaphore set represented by the array of sem
structures is not initialized. If IPC_CREAT is specified (but not IPC_EXCL) and the semaphore set for the indicated key value already exists, the semget
system call returns the associated semaphore identifier. When using semget
to access an established semaphore set (such as in a client process), the value of nsems
can be set to 0 (a don't-care value).
When the semaphore is created, the system sets, respectively, the semid_ds
members sem_perm.cuid
, sem_perm.uid
, sem_perm.cgid
, and sem_perm.gid
to the effective user and group ID of the invoking process. The member sem_otime
is set to 0, and sem_ctime
is assigned the current time. The nsems
member stores the number of semaphores in the semaphore set.
If the semget
system call fails, it returns a −1 and sets the value stored in errno
. Error messages for semget
are shown in Table 7.2.
Table 7.2. semget
Error Messages.
# | Constant |
| Explanation |
---|---|---|---|
2 | EOENT | No such file or directory | Semaphore identifier does not exist for this |
12 | ENOMEM | Cannot allocate memory | Insufficient system memory to allocate the semaphore set. |
13 | EACCES | Permission denied | Semaphore identifier exists for this |
17 | EEXIST | File exists | Semaphore identifier exists for this |
28 | ENOSPC | No space left on device | System-imposed limit (SEMMNI) for the number of semaphore sets or systemwide maximum number of semaphores (SEMMNS) has been reached. |
43 | EIDRM | Identifier removed | Specified semaphore set is marked for removal. |
Program 7.1 attempts to create several semaphore sets, each containing three semaphores.
Example 7.1. Creating semaphore sets.
File : p7.1.cxx | /* Creating sets of semaphores */ | #include <iostream> | #include <cstdio> | #include <sys/types.h> + #include <sys/ipc.h> | #include <sys/sem.h> | using namespace std; | int | main( ){ 10 int sem1, sem2, sem3; | key_t ipc_key; | ipc_key = ftok(".", 'S'), | if ((sem1 = semget(ipc_key, 3, IPC_CREAT | 0666)) == -1) { | cerr << "semget: IPC_CREAT | 0666" << endl; + } | cout << "sem1 identifier " << sem1 << endl; | if ((sem2 = semget(ipc_key, 3, IPC_CREAT|IPC_EXCL|0666)) == -1) { | cerr << "semget: IPC_CREAT | IPC_EXCL | 0666" << endl; | } 20 cout << "sem2 identifier " << sem2 << endl; | if ((sem3 = semget(IPC_PRIVATE, 3, 0600)) == -1) { | cerr << "semget: IPC_PRIVATE" << endl; | } | cout << "sem3 identifier " << sem3 << endl; + return 0; | }
The first call to semget
, provided the system limits have not been reached, creates a set of three semaphores. The permissions for the set are read
and alter
(write) for the owner, group, and others (world). The value of the semaphore identifier is tied to the key value that is produced by the call to ftok
. The second call to semget
attempts to create a second set of three semaphores. The call uses the same key value as the first but includes the specification IPC_EXCL. With the IPC_EXCL flag set and the previous successful creation of the semaphore set using the same key value, this invocation of semget
will fail. The third call to semget
creates a three-semaphore set used by specifying IPC_PRIVATE instead of using the ftok
key value. The semaphore identifier generated for this set will be private to this process.
When the program is run twice consecutively, the output generated will be similar to that shown in Figure 7.2.
Example 7.2. Two consecutive runs of Program 7.1.
linux$ p7.1 sem1 identifier 9797637 semget: IPC_CREAT | IPC_EXCL | 0666: File exists sem2 identifier -1 sem3 identifier 9830406 linux$ p7.1 sem1 identifier 9797637 semget: IPC_CREAT | IPC_EXCL | 0666: File exists sem2 identifier -1 sem3 identifier 9863175
Notice that when the program is run the second time, the same semaphore identifier (9797637) is returned from the initial call to semget
. Without the IPC_EXCL flag, the semget
system call will not fail if the semaphore set has already been created, but will instead return the associated semaphore identifier. The creation of a second private semaphore set by the second invocation of the program produces another unique semaphore identifier (9863175), which is different from the first private semaphore identifier (9830406). The output of the ipcs
command, shown in Figure 7.3, verifies the presence and permissions of the three semaphore sets that were created by the user gray
. Notice that the key for each of the private semaphore sets is 0.
Example 7.3. ipcs
output.
linux$ ipcs -s -----Semaphore Arrays------ key semid owner perms nsems status 0x53157f08 9797637 gray 666 3 0x00000000 9830406 gray 600 3 0x00000000 9863175 gray 600 3
As written, Program 7.1 does not remove the semaphore sets it creates. Semaphores, like message queues, are a limited resource. In a programming setting, semaphores can be removed with the semctl
system call (see the following section). Semaphores may also be removed at the command-line level using the ipcrm
command (as discussed in Chapter 6, Section 6.1). If there are several semaphores to remove, a shell script, such as that shown in Program 7.2, can be used to automate the removal process.
Example 7.2. A Korn shell script to remove all semaphores for a user.
File : clean | #!/bin/ksh | # | # Korn Shell script to remove all existing semaphores for a user | # + list=$(ipcs -s | grep "$USER" | cut -d' ' -f2) | count=0 | for semaphore in $list | do | ipcrm sem $semaphore > /dev/null 10 ((count=count+1)) | done | print "$count semaphore(s) for $USER removed"
The semget
system call (Table 7.3) is used to create or gain access to a set of semaphores. The semctl
system call allows the user to perform a variety of generalized control operations on the system semaphore structure, on the semaphores as a set, and on individual semaphores. Additional manipulative operations on specific semaphores within a set are covered in the following section on semaphore operations (Section 7.4).
Table 7.3. Summary of the semctl
System Call.
Include File(s) |
<sys/types.h> <sys/ipc.h> <sys/sem.h> | Manual Section | 2 | |
Summary |
int semctl(int semid, int semnum, int cmd, union semun arg); | |||
Return | Success | Failure | Sets | |
0 or the value requested | −1 | Yes |
The semctl
system call takes four arguments. The first argument, semid
, is a valid semaphore identifier that was returned by a previous semget
system call. The second argument, semnum
, is the number of semaphores in the semaphore set. In most cases, this value is greater than 0 but less than the system limit. However, we will see occasions when the value for semnum
is set to 0. These occasions arise when we ask semctl
to perform an operation for which the number of semaphores in the set is not relevant. The third argument to semctl
, cmd
, is an integer command value (usually expressed as one of the symbolic constants found in the header files <sys/ipc.h>
or <sys/sem.h>
). As discussed in detail in Section 7.3.1, “Semaphore Control Details,” the cmd
value directs semctl
to take one of several control actions. Each action requires specific access permissions to the semaphore control structure (i.e., read
or alter
). The fourth argument to semctl
, arg
, is a union of type semun
. Given the action specified by the preceding cmd
argument, the data in arg
can be one of any of the following four values:
An integer used with SETVAL to indicate a specific value for a particular semaphore within the semaphore set.
A reference to a semid_ds
structure where information is returned when IPC_STAT or IPC_SET is specified.
A reference to an array of type unsigned short integers; the array is used either to initialize the semaphore set (such as when stipulating SETALL) or as a return location when specifying GETALL.
A reference to a seminfo
structure when IPC_INFO is requested.
In some versions of UNIX the definition of the semun
union is found in the include files required by semctl
. However, technically the user should define the union. To this end, the manual page for semctl
contains the following cryptic reference.
#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) /* union semun is defined by including <sys/sem.h> */ #else /* according to X/OPEN we have to define it ourselves */ union semun { int val; /* value for SETVAL */ struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */ unsigned short int *array; /* array for GETALL, SETALL */ struct seminfo *__buf; /* buffer for IPC_INFO */ }; #endif
This sequence of preprocessor directives determines whether the programmer defines the semun
union. If __GNU_LIBRARY__ has been defined (is nonzero), and _SEM_SEMUN_UNDEFINED has not been defined, the definition of the union semun
is found in the include file <sys/sem.h>
. Otherwise, the user defines the union. To be safe, this set of preprocessor directives should be placed at the top any program that will make use of the fourth argument of the semctl
call. Its omission may cause the semctl
system call to fail, returning the value EFAULT (bad address). Further, when specifying arg
as the fourth argument to semctl
, the value for arg
should be explicitly assigned (e.g., arg.buf=ptr_to_my_structure
). This assignment must be done prior to the calling of semctl
(see Program 7.3).
The following cmd
values cause semctl
to act upon the system semaphore structure (semid_ds
):
IPC_STAT—. Return the current values of the semid_ds
structure for the indicated semaphore identifier. The returned information is stored in a user-generated structure referenced by the fourth argument to semctl
. To specify IPC_STAT, the process must have read
permission for the semaphore set associated with the semaphore identifier.
IPC_SET—. Modify a restricted number of members in the semid_ds
structure. The members sem_perm.uid
, sem_perm.gid
and sem_perm.mode
(in the permissions structure within semid_ds
) can be changed if the effective ID of the accessing process is that of the superuser or is the same as the ID value stored in sem_perm.cuid
or sem_perm.uid
. To make these changes, a structure of the type semid_ds
must be allocated. The appropriate members' values are then assigned, and a reference to the modified structure is passed as the fourth argument to the semctl
system call.
IPC_RMID—. Remove the semaphore set associated with the semaphore identifier.
When specifying IPC_STAT, IPC_SET, or IPC_RMID, the value for semnum
(the number of semaphores in the set) is not considered and can be set to 0.
The following cmd
values cause semctl
to act upon the entire set of semaphores:
GETALL—. Return the current values of the semaphore set. The values are returned via the array reference passed as the fourth argument to semctl
. The user is responsible for allocating the array of the proper size and type prior to passing its address to semctl
. Read
permission for the semaphore set is required to specify GETALL. When specifying GETALL, the argument semnum
is ignored.
SETALL—. Initialize all semaphores in a set to the values stored in the array referenced by the fourth argument to semctl
. Again, the user must allocate the initializing array and assign values prior to passing the address of the array to semctl
. The process must have alter
access for the semaphore set to use SETALL. When specifying SETALL, the sem_ctime
member of the system semaphore data structure is updated.
The last set of semctl
cmd
values acts upon individual semaphores or upon specific members in the semid_ds
structure. All of these commands require read
permission except for SETVAL, which requires alter
permission:
GETVAL—. Return the current value of the individual semaphore referenced by the value of the semnum
argument (remember, arrays in C/C++ are zero-based; thus, the first semaphore of a set is at index 0).
SETVAL—. Set the value of the individual semaphore referenced by the semnum
argument to the value specified by the fourth argument to semctl
(e.g., the value stored in arg.val
).
GETPID—. Return the PID from the sem_perm
structure within the semid_ds
structure.
GETNCNT—. Return the number of processes waiting for the semaphore referenced by the semnum
argument to increase in value.
GETZCNT—. Return the number of processes waiting for the semaphore referenced by the semnum
argument to become 0.
If semctl
is successfully issues any of these commands, the requested integer value is returned: the value of semncnt
for GETNCNT, the value of sempid
for GETPID, the value of semval
for GETVAL, or the value of semzcnt
for GETZCNT. If semctl
fails, it returns a value of −1 and sets errno
to indicate the specific error. The errors returned by semctl
with an explanation of their meaning are shown in Table 7.4.
Table 7.4. semctl
Error Messages.
# | Constant |
| Explanation |
---|---|---|---|
1 | EPERM | Operation not permitted | Value for |
13 | EACCES | Permission denied | The requested operation is not allowed by the current access permissions for this process. |
14 | EFAULT | Bad address | The fourth argument to |
22 | EINVAL | Invalid argument |
|
34 | ERANGE | Numerical result out of range | The value for |
43 | EIDRM | Identifier removed | Specified semaphore set is marked for removal. |
Program 7.3 uses the semctl
system call to perform a number of semaphore control operations.
Example 7.3. Using semctl
.
File : p7.3.cxx | /* | Using the semctl system call | */ | #include <iostream> + #include <cstdio> | #include <sys/ipc.h> | #include <sys/sem.h> | #include <time.h> | #define NS 3 <-- 1 10 #if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) | // definition in <sys/sem.h> | #else | union semun { // We define: | int val; // value for SETVAL + struct semid_ds *buf; // buffer for IPC_STAT, IPC_SET | unsigned short int *array; // array for GETALL, SETALL | struct seminfo *__buf; // buffer for IPC_INFO | }; | #endif 20 using namespace std; | int | main( ){ | int sem_id, sem_value, i; | key_t ipc_key; + struct semid_ds sem_buf; | unsigned short int sem_array[NS] = {3, 1, 4}; | union semun arg; | ipc_key = ftok(".", 'S'), | 30 if ((sem_id = semget(ipc_key, NS, IPC_CREAT | 0660)) == -1) { | perror("semget: IPC_CREAT | 0660"); | return 1; | } | cout << "Semaphore identifier " << sem_id << endl; + arg.buf = &sem_buf; <-- 2 | if (semctl(sem_id, 0, IPC_STAT, arg) == -1) { | perror("semctl: IPC_STAT"); | return 2; | } 40 cout << "Created " << ctime(&sem_buf.sem_ctime) << endl; | arg.array = sem_array; <-- 3 | if (semctl(sem_id, 0, SETALL, arg) == -1) { | perror("semctl: SETALL"); | return 3; + } | for (i = 0; i < NS; ++i) { | if ((sem_value = semctl(sem_id, i, GETVAL, 0)) == -1) { | perror("semctl: GETVAL"); | return 4; 50 } | cout << "Semaphore " << i << " has value of " << sem_value << endl; | } | if (semctl(sem_id, 0, IPC_RMID, 0) == -1) { | perror("semctl: IPC_RMID"); + return 5; | } | return 0; | }
Program 7.3 creates a set of three semaphores. The semaphore identifier for the set is printed. In line 35, the address of sem_buf
is assigned to the appropriate member of arg
. The union arg
now contains the location where the returned data will be stored. Then, by specifying IPC_STAT and passing the proper address, semctl
obtains the current values of the system semaphore structure. The date and time the semaphore was created are displayed using the library function ctime
. Using similar syntax, other members of the semid_ds
structure could be displayed. However, there is another way to obtain the entire contents of the semid_ds
structure (albeit on a temporary basis). To do this, compile Program 7.3 with the -g
option and then use the debugger, gdb
, to examine the semid_ds
structure. This can be accomplished by invoking gdb
with the executable program name, such as linux$ gdb p7.3
. When in gdb
, direct gdb
to stop at the correct line (say, break 40
). The program is then run, and when gdb
stops at line 40, it is asked to print the contents of the structure using the gdb
command: print sem_buf
. The output of such a sequence will display the contents of the entire sem_buf
structure. On our system, Program 7.3 run in gdb
produces the output shown in Figure 7.4:
Example 7.4. dbx
output of Program 7.3.
linux$ g++ -g -o p7.3 p7.3.cxx <-- 1 linux$ gdb p7.3 GNU gdb 5.0rh-5 Red Hat Linux 7.1 Copyright 2001 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux"... (gdb) break 40 <-- 2 Breakpoint 1 at 0x8048890: file p7.3.cxx, line 40. (gdb) run Starting program: /home/faculty/gray/revision/07/p7.3 Semaphore identifier 10027013 Breakpoint 1, main () at p7.3.cxx:40 40 cout << "Created " << ctime(&sem_buf.sem_ctime) << endl; Current language: auto; currently c++ (gdb) print sem_buf $1 = {sem_perm = {__key = 1393917704, uid = 500, gid = 1000, cuid = 500, cgid = 1000, mode = 432, __pad1 = 0, __seq = 306, __pad2 = 0, __unused1 = 0, __unused2 = 0}, sem_otime = 0, __unused1 = 0, sem_ctime = 1016545082, __unused2 = 0, sem_nsems = 3, __unused3 = 0, __unused4 = 0} (gdb)
Notice, as would be expected, that the number of semaphores in the set, three, has been stored in the sem_nsems
member.
Program 7.3 uses the semctl
system call to initialize the three-semaphore set to the values stored in the array sem_array
. Again, notice that prior to calling semctl
the address of the initializing vector (see line 41) is assigned to the proper member of arg
. Once the values are assigned to the semaphore set, the program uses a loop to display to the screen the value stored in each semaphore. The last action of Program 7.3 is to use the semctl
system call with the IPC_RMID flag to remove the semaphore set.
When run outside of gdb
, the output of Program 7.3 should be similar to that shown in Figure 7.5.
Example 7.5. Output of Program 7.3.
linux$ p7.3 Semaphore identifier 10027013 Created Tue Mar 19 08:38:02 2002 Semaphore 0 has value of 3 Semaphore 1 has value of 1 Semaphore 2 has value of 4
Additional operations on individual semaphores are accomplished by using the semop
system call, shown in Table 7.5.
The semop
system call requires three arguments and returns an integer value. If successful, semop
returns a 0; otherwise it returns a −1 and sets errno
to indicate the source of the error (see Table 7.9 for details). The first argument for semop
, semid
, is the semaphore identifier returned by a previous successful call to semget
. The second argument, sops
, is a reference to the base address of an array of semaphore operations that will be performed on the semaphore set associated with by the semid
value. The semop
system call will attempt to perform, in an all-or-nothing manner, all of the semaphore operations indicated by sops
. The third argument, nsops
, is the number of elements in the array of semaphore operations.
Table 7.5. Summary of the semop
System Call.
Include File(s) |
<sys/types.h> <sys/ipc.h> <sys/sem.h> | Manual Section | 2 | |
Summary |
int semop(int semid, struct sembuf *sops, unsigned nsops); | |||
Return | Success | Failure | Sets | |
0 | −1 | Yes |
Each element of the semaphore operation array is a structure of type sembuf
.
/* User semaphore template for semop system calls. */ struct sembuf { unsigned short int sem_num; // semaphore #: 0 = first short int sem_op; // semaphore operation short int sem_flg; // operation flags };
The first member of the sembuf
structure, sem_num
, is the semaphore number (remember, the first semaphore is 0, the second 1, etc.). The second member of sembuf
, sem_op
, is the operation to be performed on the semaphore. A positive integer value means to increment the semaphore (in general, indicating a release or return of a resource), a negative value for sem_op
means to decrement the semaphore (an attempt to acquire a resource), and a value of 0 means to test if the semaphore is currently at 0 (in use, all resource(s) allocated). Additional details on semaphore operations will be provided in a subsequent section. The third member of sembuf
is an operation flag. These flags are
IPC_NOWAIT—. If the semaphore operation cannot be performed (such as when attempting to decrement a semaphore or test if it is equal to 0), the call returns immediately. No other semaphores in the set are modified if one of the specified semaphore operations fails with the IPC_NOWAIT flag.
SEM_UNDO—. If IPC_NOWAIT has not been indicated, the SEM_UNDO flag allows an operation to be undone if a blocked operation (one waiting for a specific condition) subsequently fails. The system keeps track of the adjustment values needed for each semaphore set. The adjustment values are kept on a per-process basis and actually indicate how many resources are being held, while the systemwide semaphore value indicates how many resources are currently free.
Figure 7.6 shows a relationship of an arbitrary three-element semaphore operation array to an N element set of semaphores.
When the sem_op
value is negative, the process specifying the operation is attempting to decrement the semaphore. The decrement of the semaphore is used to record the acquisition of the resource affiliated with the semaphore. When a semaphore value is to be modified, the accessing process must have alter
permission for the semaphore set. The actions taken by the semop
system call when the value for sem_op
is negative are summarized in Table 7.6.
When the sem_op
value is positive, the process is adding to the semaphore value. The addition is used to record the return (release) of the resource affiliated with the semaphore. Again, when a semaphore value is to be modified, the accessing process must have alter
permission for the semaphore set. The actions taken by the semop
system call when the value for sem_op
is positive are summarized in Table 7.7.
When the sem_op
value is zero, the process is testing the semaphore to determine if it is at 0. When a semaphore is at 0, the testing process can assume that all the resources affiliated with the semaphore are currently allocated (in use). For a semaphore value to be tested, the accessing process must have read
permission for the semaphore set. The action taken by the semop
system call when the value for sem_op
is 0 is summarized in Table 7.8.
Table 7.6. Actions Taken by semop
when the Value for sem_op
is Negative.
Condition | Flag Set | Action Taken by |
---|---|---|
| Subtract | |
| SEM_UNDO | Subtract |
| Increment • • • A signal is caught, then adjust | |
| IPC_NOWAIT | Return −1 immediately and set |
Table 7.7. Actions Taken by semop
when the Value for sem_op
Is Positive.
Condition | Flag Set | Action Taken by |
---|---|---|
Add | ||
SEM_UNDO | Add |
The errors returned by semop
, with an explanation of their meaning, are shown in Table 7.9.
If semop
is successful, for each of the semaphores modified/referenced, semop
sets the value of sempid
to that of the calling process for each semaphore specified in the array referenced by sops
. Additionally, both the sem_otime
and sem_ctime
members are set to the current time.
Program 7.4 demonstrates the use of the semop
system call. Two semaphores are used to coordinate concurrent producer and consumer processes. The producer process generates (at its own pace) an integer value. The value is stored in a non-shareable resource (in this case a file in the local directory). The consumer process, once a new value has been generated, retrieves the value from the same file and displays the value to the screen. Two semaphores are used by the producer process to prevent it from overwriting a previously stored integer value before the consumer process has retrieved it (should the producer process be speedier than the consumer process). The consumer process uses the two semaphores to prevent it from retrieving the same value multiple times (should the producer process be slow in generating new values). The semaphores, which we will arbitrarily call READ and MADE, are treated in a binary manner. By convention, the MADE semaphore is set to 1 by the producer process once the producer has stored its newly created integer value in the file. The READ semaphore is set to 1 by the consumer process once the consumer has read the value stored in the file by the producer. If the number has yet to be made by the producer or the number has not been read by the consumer, the corresponding semaphore value will be 0. The producer will gain access to the file to store the generated number only if the number currently in the file has been consumed. Likewise, the consumer can gain access to the file to read the stored number only if a new value has been made. Figure 7.7 shows the contents of the two semaphores in the producer and consumer processes and their relationship to one another. At the start we indicate that the current stored number has been read (we set READ to 1) and that a new number has not been generated (we set MADE to 0).
Table 7.8. Actions Taken by semop
when the Value for sem_op
is Zero.
Condition | Flag Set | Action Taken by |
---|---|---|
| Return immediately. | |
| IPC_NOWAIT | Return −1 immediately and set |
| Increment
|
Table 7.9. semop
Error Messages.
# | Constant |
| Explanation |
---|---|---|---|
4 | EINTR | Interrupted system call | While in a wait queue for the semaphore, a signal was received by the calling process. |
7 | E2BIG | Argument list too long | The value for |
11 | EAGAIN | Resource temporarily unavailable | The requested operation would cause the calling process to block, but IPC_NOWAIT was specified. |
12 | ENOMEM | Cannot allocate memory | The limit for number of processes requesting SEM_UNDO has been exceeded. |
13 | EACCES | Permission denied | The requested operation is forbidden by the current access permissions. |
14 | EFAULT | Bad address | The value for |
22 | EINVAL | Invalid argument |
|
27 | EFBIG | File too large | The value for |
34 | ERANGE | Numerical result out of range | The requested operation would cause the system semaphore adjustment value to exceed its limit. |
43 | EIDRM | Identifier removed | The semaphore set associated with |
A high-level algorithm for the producer and consumer processes would be as follows:
Producer
While 10 new numbers not generated
Generate a new number
If the current stored number has not been read, then wait
Store the new number in the file
Indicate that a new number has been made
Consumer
Forever
If a new number has not been made, then wait
Retrieve the new number from the file
Indicate new number has been read
Display the retrieved number
For discussion purposes, the program (which actually resides in a single file) has been divided into three sections, shown as Programs 7.4A, 7.4B, and 7.4C. The first part of the program, which establishes the operations that will be performed on the semaphores, creates the set of two semaphores and initializes them, is shown in Program 7.4A.
Example 7.4A. The first section of the producer/consumer problem.
File : p7.4.cxx | /* | The producer/consumer problem | */ | #include <iostream> // Section ONE + #include <cstdio> | #include <unistd.h> | #include <stdlib.h> | #include <sys/types.h> | #include <sys/ipc.h> 10 #include <sys/sem.h> | #define BUFFER "./buffer" | #if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) | // definition in <sys/sem.h> | #else + union semun { // We define: | int val; // value for SETVAL | struct semid_ds *buf; // buffer for IPC_STAT, IPC_SET | unsigned short int *array; // array for GETALL, SETALL | struct seminfo *__buf; // buffer for IPC_INFO 20 }; | #endif | using namespace std; | int | main(int argc, char *argv[ ]) { + FILE *fptr; | static struct sembuf acquire = {0, -1, SEM_UNDO}, <-- 1 | release = {0, 1, SEM_UNDO}; | pid_t c_pid; | key_t ipc_key; 30 static unsigned short start_val[2] = {1, 0}; | int semid, producer = 0, i, n, p_sleep, c_sleep; | union semun arg; | enum { READ, MADE }; | if (argc != 2) { + cerr << argv[0] << " sleep_time" << endl; | return 1; | } | ipc_key = ftok(".", 'S'), | if ((semid=semget(ipc_key, 2, IPC_CREAT|IPC_EXCL|0660)) != -1) { 40 producer = 1; | arg.array = start_val; | if (semctl(semid, 0, SETALL, arg) == -1) { | perror("semctl--producer--initialization"); | return 2; + } | } else if ((semid = semget(ipc_key, 2, 0)) == -1) { | perror("semget--consumer--obtaining semaphore"); | return 3; | } 50 cout << (producer==1 ? "Producer" : "Consumer" ) << " starting" << endl;
The program uses the symbolic constant BUFFER to reference a local file named ./buffer
. This file acts as the non-shareable resource to be accessed by the producer and consumer processes. Following this definition is the declaration of the union semun
as an argument of type semun
is required for the semctl
system call.
Using the sembuf
structure as a template, the program defines two operations—acquire
and release
—that can be used with either of the semaphores. For both operations the value for the member sem_num
has been set to 0. This value acts as a placeholder and will be changed dynamically to indicate which of the two semaphores within the set we are referencing. The sem_op
member of each is set to −1 and 1 for acquire
and release
respectively. The value of −1 is used when we want to acquire a resource that is associated with a semaphore (indicated by decrementing the semaphore). The value 1 is used when we want to indicate the return of the resource (thus incrementing the associated semaphore). In either case, we set the value for sem_flg
to SEM_UNDO to allow rollback. The variable arg
, of type union semun
, is declared and used as the fourth argument to the semctl
system call. The values in the array start_val
(1, 0) are used to set the initial values for the two semaphores. The enumerated constants READ and MADE act as indices to reference which of the two semaphores we are using.
The program begins by checking the command line to determine if an argument has been passed. The program expects a small integer value to be passed. This value is used to indicate the number of seconds the process should sleep
in its processing cycle. The inclusion of sleep
allows the producer and consumer process to progress at different rates, thus providing the user with an easy way to check the integrity of the semaphore arrangement.
The semget
system call is used to create/gain access to the semaphore set. The flag combination IPC_CREAT | IPC_EXCL insures that the first time the program is run it will create the two-semaphore set. As written, the first invocation of the program is considered to be the producer (the process that will generate the integer values). The variable producer
is set to 1 in the producer process to indicate this. Once the semaphore set is successfully created, the program uses the semctl
system call to initialize the semaphore set to the values stored in start_val
.[3] When the program is run a second time, the resulting process is considered to be a consumer (a process that will obtain the stored integer value). In the second program invocation, the initial semget
system call, which is within the if
statement, fails, as the semaphore set has already been generated by the producer. The else
-if
branch of the same if
statement invokes semget
a second time without any flags set. This second invocation of semget
allows the consumer process to gain access to the previously generated semaphore set.
The second section of the program, which contains the logic executed by the producer, is shown in Program 7.4B.
Example 7.4B. The second section of the producer/consumer problem—the producer logic.
| // Section TWO | switch (producer) { | case 1: // The PRODUCER | p_sleep = atoi(argv[1]); + srand((unsigned) getpid()); | for (i = 0; i < 10; i++) { | sleep(p_sleep); | n = rand() % 99 + 1; | cout << "A. The number [" << n <<"] generated by producer" << endl; 60 acquire.sem_num = READ; | if (semop(semid, &acquire, 1) == -1) { | perror("semop -producer- waiting for consumer to read number"); | return 4; | } + if ((fptr = fopen(BUFFER, "w")) == NULL ){ | perror(BUFFER); | return 5; | } | fprintf(fptr, "%d ", n); 70 fclose(fptr); | release.sem_num = MADE; | cout << "B. The number [" << n <<"] deposited by producer" << endl; | if (semop(semid, &release, 1) == -1) { | perror("semop -producer- indicating new number has been made"); + return 6; | } | } | sleep(5); | if (semctl(semid, 0, IPC_RMID, 0) == -1) { 80 perror("semctl –producer-"); | return 7; | } | cout << "Semaphore removed" << endl; | break;
As noted, the first time the program is run, the value of the variable producer
is set to 1. When producer
contains a 1, the case 1
: section of program code, the producer logic, is executed. The small integer value passed on the command line to indicate the number of seconds the process should sleep is converted by the library function atoi
and stored for future reference in the variable p_sleep
. Following this, the random number generator is initialized using the value of the current PID. A for
loop that produces 10 random integer values in the range 1 to 99 is entered. After the program sleeps, a random number is generated and displayed to the screen (this allows the user to verify the activity of the program). Following this, the sem_num
member of the acquire
operation is set to the value READ. This directs the following semop
system call to reference the READ semaphore, which is the first semaphore of the set. We use a value of 1 for the READ semaphore to indicate the current stored number has been read (consumed) and a value of 0 to indicate the number has not been read. As the initial value for the READ semaphore is 1, the very first time the producer tests the READ semaphore with the semop
system call, the producer can acquire the semaphore. Once this occurs, the producer continues on to the next section of code where it opens the file, stores the generated value, and closes the file. In later passes through this code, the producer may or may not find the READ semaphore at 1. If the semaphore is at 0 (indicating the consumer has not read the value), the producer, by default, blocks (waits) for this event to occur. Once the produced value has been written to the file, the producer process, using the release
operation, increments the MADE semaphore. By incrementing the MADE semaphore, the producer indicates a new number is now available for the consumer. When all 10 numbers have been generated, the producer exits the for
loop and, after sleeping 5 seconds to allow for the consumption of the last produced value, it removes the semaphore set with the semctl
system command. If needed, the unlink
call can be used to remove the temporary file.
The logic for the consumer is shown in Program 7.4C.
Example 7.4C. The third section of the producer/consumer problem—the consumer logic.
+ case 0: // Section THREE | c_sleep = atoi(argv[1]); // The CONSUMER | c_pid = getpid(); | while (1) { | sleep(c_sleep); 90 acquire.sem_num = MADE; | if (semop(semid, &acquire, 1) == -1) { | perror("semop -consumer- waiting for new number to be made"); | return 8; | } + if ( (fptr = fopen(BUFFER, "r")) == NULL ){ | perror(BUFFER); | return 9; | } | fptr = fopen(BUFFER, "r"); 100 fscanf(fptr, "%d", &n); | fclose(fptr); | release.sem_num = READ; | if (semop(semid, &release, 1) == -1) { | perror("semop -consumer- indicating number has been read"); + return 10; | } | cout << "C. The number [" << n <<] obtained by consumer " | << c_pid << endl; | } 110 } | return 0; | }
The consumer process, like the producer, converts the value passed on the command line into an integer value by using the library function atoi
. The consumer then obtains its PID using the getpid
system call. The PID is used to identify individual consumer processes when more than one consumer process is present. The consumer then enters an endless loop. It sleeps c_sleep
seconds and then tests the MADE semaphore. To accomplish this, the sem_num
member of the acquire
operation structure is set to MADE. The call to semop
, which is passed the reference to acquire
, causes the consumer to block (wait) if the semaphore is at 0. Once the MADE semaphore becomes 1, the consumer opens the file where the number was written, reads the number, and closes the file. The consumer then indicates that it has read the number. The release
structure member, sem_num
, is set to READ to reference the second semaphore of the set. The following semop
system call causes the contents of the READ semaphore to be incremented. The consumer then displays a short message to the screen indicating the value retrieved and its PID value. The consumer continues to consume values until the call to semop
fails due to the removal of the semaphore set by the producer.
We can run the program to simulate a number of conditions. We begin by making the producer process slower than a single consumer process. The output in Figure 7.8 shows how this is accomplished.
Example 7.8. A single slow producer with a single consumer.
linux$ p7.4 2 & p7.4 0 Producer starting [1] 31223 Consumer starting A. The number [79] generated by producer B. The number [79] deposited by producer C. The number [79] obtained by consumer 31224 A. The number [17] generated by producer B. The number [17] deposited by producer C. The number [17] obtained by consumer 31224 . . . C. The number [53] obtained by consumer 31224 A. The number [15] generated by producer B. The number [15] deposited by producer C. The number [15] obtained by consumer 31224 Semaphore removed semop -consumer- waiting for new number to be made: Identifier removed [1] + Done p7.4 2
In this example the program p7.4
is run twice on the command line. The first invocation of the program, which will be the producer,[4] is passed the value 2. This directs the producer process to sleep 2 seconds each time it cycles through the for
loop. The producer process is placed in the background by specifying &
after the command-line sleep value. In the second invocation of the program, the consumer is passed the value 0 as the sleep value. The system responds to the command sequence by displaying the PID of the commands that were placed in background. The display of
[1] 31223
means that, for this invocation, the producer PID is 31223. As the two processes execute, we can clearly see from the output that the producer must first generate and deposit the value in the file before the consumer can obtain it. As the producing process is slower than the consuming process, the consumer process spends a portion of its time waiting for the producer to deposit a number. When all of the numbers have been produced, the producer process removes the semaphore set. When this happens, the consumer process exits. If we run this command sequence several times, we should find it behaves in a consistent manner. Although the consumer process is faster than the producer process, the consumer should never read the same value twice from the file (unless, by chance, the same number was generated twice by the producer).
We can reverse the conditions and make the producer process faster than the consumer process. The output shown in Figure 7.9 shows how this can be accomplished.
Example 7.9. A producer with a single slow consumer.
linux$ p7.4 0 & p7.4 2 [1] 31229 Producer starting A. The number [28] generated by producer B. The number [28] deposited by producer A. The number [69] generated by producer Consumer starting C. The number [28] obtained by consumer 31230 B. The number [69] deposited by producer A. The number [83] generated by producer C. The number [69] obtained by consumer 31230 . . . A. The number [29] generated by producer C. The number [65] obtained by consumer 31230 B. The number [29] deposited by producer C. The number [29] obtained by consumer 31230 Semaphore removed semop -consumer- waiting for new number to be made: Identifier removed [1] + Done p7.4 0
This output sequence is slightly different from the previous one. Notice, as before, the producer generates and deposits the number. The producer, being faster than the consumer, then goes on to generate another number. However, this number is not deposited until the slower consumer process has read the existing stored value. If we run this command sequence several times, we should again be able to confirm that the producer process never overwrites the existing stored value until the consumer process has read it.
As with message queues, the syntax and manipulation of semaphores is somewhat complex, making them a prime candidate for incorporation into a C++ class. A semaphore class would define the relationships between semaphore data and the functions (methods) that manipulate this data. A declaration of a simplified semaphore class called SSemaphore
[5] is shown in Figure 7.10.
Example 7.10. Header file for a basic semaphore class.
File : SSemaphore.h | /* | A VERY simplified semaphore class for use in a std UNIX | environment. See the text for instructions on how to use | this class. Copyright (c) 2002 J. S. Gray + | Exit codes for class operations: | | 1 - Semaphore allocation failure 2 - Unable remove semaphore | 3 - Unable to LOCK semaphore 4 - Unable to UNLOCK semaphore 10 5 - Failure on wait for ZERO 6 - Unable to assign value | 7 - Unable to return value | */ | | #ifndef SSemaphore_h + #define SSemaphore_h | #define _GNU_SOURCE | #include <iostream> | #include <cstdio> | #include <sys/types.h> 20 #include <sys/ipc.h> | #include <sys/sem.h> | #include <stdlib.h> | #include <unistd.h> | using namespace std; + | class SSemaphore { | public: | SSemaphore ( ); // Constructor | ~SSemaphore( ); // Destructor - remove semaphore 30 int P( ); // LOCK (decrement semaphore) | void V( ); // UNLOCK (increment semaphore) | int Z( ); // WAIT while semaphore is NOT 0 | void Put( const int ); // Assign a value to semaphore | int Get( ); // Return value of the semaphore + private: | #if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) | // definition in <sys/sem.h> | #else | union semun { // We define: 40 int val; // value for SETVAL | struct semid_ds *buf; // buffer for IPC_STAT, IPC_SET | unsigned short int *array; // array for GETALL, SETALL | struct seminfo *__buf; // buffer for IPC_INFO | }; + #endif | union semun arg; // For semctl call | struct sembuf zero,lock, unlock; // hoo ha's for P,V & Z operations | int semid; // ID of semaphore | pid_t my_pid; // PID of creator 50 }; | #endif
As defined, the SSemaphore
class creates a private semaphore set with a single element. There are seven public methods and six private data members in the class. The functionality of each method is shown in Table 7.10.
Table 7.10. SSemaphore
Class Methods.
Method Name | Explanation |
---|---|
| This is the class constructor. This method assigns the proper values to the |
| This method removes the semaphore from the system if the calling function is the process that generated the semaphore. |
| This method atomically tests and decrements the semaphore. It blocks if the semaphore is 0. |
| This method increments the semaphore. |
| This method tests whether or not the semaphore is at 0. If it is not at 0, it blocks. |
|
|
|
|
The C++ code that implements the semaphore class is found in the program file SSemaphore.cxx
(Program 7.5). As shown, the code is bare bones—little is done to handle errors, and only basic semaphore functionality is addressed.
Example 7.5. Program code for the semaphore class.
File : SSemaphore.cxx | /* | SSemaphore implementation - Copyright (c) 2002 J. S. Gray | */ | #include "SSemaphore.h" + // Generate a private semaphore | SSemaphore::SSemaphore( ){ | zero.sem_num = 0, zero.sem_op = 0, zero.sem_flg = SEM_UNDO; | lock.sem_num = 0, lock.sem_op = -1, lock.sem_flg = SEM_UNDO; | unlock.sem_num = 0, unlock.sem_op = 1, unlock.sem_flg = SEM_UNDO; 10 my_pid = getpid( ); | if((semid = semget( IPC_PRIVATE, 1, 0660 )) == -1 ){ | exit( 1 ); | } | Put( 0 ); // Default - set to zero @ start + } | // Remove semaphore if creator | SSemaphore::∼SSemaphore( ) { | if ( getpid( ) == my_pid ) | if ( semctl( semid, 0, IPC_RMID ) == -1 ) 20 exit( 2 ); | } | // LOCK semaphore | int // Atomic test & decrement | SSemaphore::P( ){ + if ( semop( semid, &lock, 1 ) == -1 ) | exit( 3 ); | return 0; | } | // UNLOCK semaphore 30 void // Increment semaphore | SSemaphore::V( ){ | if ( semop( semid, &unlock, 1 ) == -1 ) | exit( 4 ); | } + | int // Wait for semaphore to be 0 | SSemaphore::Z( ){ | if ( semop( semid, &zero, 1 ) == -1 ) | exit( 5 ); 40 return 0; | } | // Assign value to the semaphore | void | SSemaphore::Put( int const value ){ + arg.val = value; | if ( semctl(semid, 0, SETVAL, arg ) == -1 ) | exit( 6 ); | } | // Return value of the semaphore 50 int | SSemaphore::Get( ){ | int sem_value; | if ((sem_value=semctl(semid, 0, GETVAL, 0)) == -1 ) | exit( 7 ); + return sem_value; | }
To use this class, the files SSemaphore.h
and SSemaphore.cxx
should reside locally. The SSemaphore
class is compiled into object code with the command line
linux$ g++ SSemaphore.cxx –c
At the top of the source file that uses a SSemaphore
object, add the statement
#include "SSemaphore.h"
to make the class definition available to the compiler. When compiling the source file, include the message queue object code file
linux$ g++ your_file_name.cxx SSemaphore.o
In 1965 Dijkstra presented what is now considered to be a classic synchronization problem involving a group of dining philosophers. In brief, the group of philosophers is sitting around a table. Each engages in the activity of thinking and eating. To eat, the philosopher must obtain the forks on his or her left and right. Both forks are needed for dining, and once obtained, neither fork is released until the philosopher is done. For N philosophers, there are N forks (not 2 × N). Clearly, if all the philosophers are to eat, some sort of synchronization of their activities is needed.
We can use the recently presented SSemaphore
class to implement a naive solution to the dining philosophers' problem. In essence, each fork will be represented by a single binary semaphore. If a philosopher can obtain both the left and right fork (think semaphore), he or she will eat for a random number of seconds, and when done, return the forks. If either instrument is not available, he or she will wait. Keep in mind that this solution has a very basic flaw. Should all the philosophers pick up their left fork at the same time, we would have deadlock. This could occur, as each left fork is also the right fork of the philosopher to the left. With every philosopher waiting for his or her right fork (the left fork of the philosopher on his or her right), no progress can be made. Program 7.6 implements our less-than-perfect solution.
Example 7.6. A rudimentary dining philosophers' solution using semaphore objects.
File : p7.6.cxx | /* | The dining philosophers | */ | #include <iostream> + #include "SSemaphore.h" // Our basic semaphore class | const int MAX = 5; | SSemaphore Forks[MAX]; | void Philosopher( int ); | void Eat_It( const int,const int, const int ); 10 int | main(int argc, char *argv[] ) { | int i; | if ( argc < 2 ) { | cerr << "Usage: " << argv[0] << " secs_to_wait " << endl; + return 1; | } | for( i=0; i < MAX; ++i ) | Forks[i].Put(true); | for(i = 0; i < MAX; ++i ) 20 Philosopher( i ); | sleep(atoi(argv[1])); // Parent process waits a bit | return 0; | } | void + Philosopher(int number ){ | if (fork() == 0) { // Run in the child | int left, right; | srand(getpid( )); | left = number; 30 right= (number+1) % MAX; | do { | cout << "A. P" << number << " is thinking "; | sleep(rand( ) % 3 + 1); // Take a while to THINK | cout << "B. P" << number << " ASKS to eat with forks " + << left << " & " << right << endl; | Forks[left].P( ); // Acquire left fork | Forks[right].P( ); // Acquire right fork | Eat_It(number, left, right); | Forks[right].V( ); 40 Forks[left].V( ); | } while( true ); | } | } | void + Eat_It(const int number, const int left, const int right) { | cout << "C. P" << number << " is EATING with forks " | << left << " & " << right << endl; | sleep(rand( ) % 3 + 1); // Take a while to EAT | cout << "D. P" << number << " is now DONE with forks " 50 << left << " & " << right << endl; | }
As written, the program expects the user to pass an integer command-line value that will be used for the number of seconds the program should run. A value of 10 seems to produce a reasonable amount of output. In line 7 of the program, an array of five private semaphore objects is instantiated. This array represents the five forks. The loop at line 17 sets each of the semaphores to true (fork available). This is followed by a second loop that calls the Philosopher
function MAX times. Upon each invocation, the Philosopher
function generates a child process to carry on the activities of the philosopher. The philosopher's activities consist of thinking for a random amount of time and eating. To eat, the left and right fork (semaphore) must be acquired. When both semaphores have been procured, the Eat_It
function is called where, for a random amount of time, the eating activity is carried out. While the child processes carry on their activities, the parent process sleeps for a period (see line 21). When the parent process exits, the destructor for the semaphore objects is called. The child processes exit as they encounter an error condition when the attempt to access a removed semaphore.
Figure 7.11 shows a typical run when the value 10 is passed to the program. To save space, the output is displayed as two columns.
Example 7.11. 10 seconds of output from the dining philosophers' program.
linux$ g++ p7.6.cxx SSemaphore.o -o p7.6 A. P2 is thinking D. P0 is now DONE with forks 0 & 1 linux$ p7.6 10 A. P0 is thinking A. P0 is thinking B. P1 ASKS to eat with forks 1 & 2 A. P1 is thinking C. P1 is EATING with forks 1 & 2 A. P2 is thinking B. P3 ASKS to eat with forks 3 & 4 A. P3 is thinking C. P4 is EATING with forks 4 & 0 A. P4 is thinking B. P2 ASKS to eat with forks 2 & 3 B. P1 ASKS to eat with forks 1 & 2 D. P4 is now DONE with forks 4 & 0 C. P1 is EATING with forks 1 & 2 A. P4 is thinking B. P3 ASKS to eat with forks 3 & 4 C. P3 is EATING with forks 3 & 4 C. P3 is EATING with forks 3 & 4 B. P0 ASKS to eat with forks 0 & 1 B. P0 ASKS to eat with forks 0 & 1 D. P1 is now DONE with forks 1 & 2 B. P2 ASKS to eat with forks 2 & 3 A. P1 is thinking B. P4 ASKS to eat with forks 4 & 0 C. P0 is EATING with forks 0 & 1 D. P1 is now DONE with forks 1 & 2 D. P3 is now DONE with forks 3 & 4 A. P1 is thinking C. P2 is EATING with forks 2 & 3 D. P3 is now DONE with forks 3 & 4 A. P3 is thinking A. P3 is thinking B. P1 ASKS to eat with forks 1 & 2 C. P0 is EATING with forks 0 & 1 B. P4 ASKS to eat with forks 4 & 0 C. P2 is EATING with forks 2 & 3 D. P0 is now DONE with forks 0 & 1 D. P2 is now DONE with forks 2 & 3 D. P2 is now DONE with forks 2 & 3 B. P3 ASKS to eat with forks 3 & 4
Semaphores are specialized data structures used to coordinate access to a non-shareable resource (section of code). Cooperating (or possibly competing) processes use the semaphore(s) to determine if a specific resource is available. If the resource is unavailable, by default the system places the requesting process in an associated queue. The system notifies the waiting process when the resource is available. This alleviates the process from using polling to determine the availability of the resource. Semaphores can be categorized as binary or counting. Binary semaphores are used to synchronize access to a single instance of a non-shareable resource, while counting semaphores are used with multiple instances of a non-shareable resource.
The actions needed to manipulate semaphores are provided by a series of system calls. The semget
system call is used to generate a new semaphore/ semaphore set (array) or to gain access to an existing semaphore. The semctl
system call allows the user to set initial semaphore values, obtain their current value, and remove the semaphore. Operations on semaphores are performed with the semop
system call. These operations (which are atomic) are used to decrement (obtain), increment (release), and test for zero-specific semaphores. Sets of operations can be specified if several semaphores are needed to coordinate access to a specific resource. The sets of operations may also be marked as being atomic.
While the syntax for using semaphores is somewhat complex, they do provide a standardized way of implementing classic primitive semaphore operations referenced in most operating system texts. As with many of the previous communication techniques, controlling access to a resource by using semaphores implies all involved processes will follow the rules. Semaphores cannot prevent processes from accessing a controlled resource.
binary semaphore
counting semaphore
critical region
ctime
library function
deadlock
dining philosophers
GETALL
GETNCNT
GETVAL
GETZCNT
IPC_CREAT
IPC_EXCL
IPC_NOWAIT
IPC_PRIVATE
IPC_RMID
IPC_SET
IPC_STAT
sem
structure
SEM_UNDO
semaphore class
semctl
system call
semget
system call
semid_ds
structure
semop
system call
semun
union
SETALL
SETVAL
starvation
[1] In this chapter we concentrate on semaphores as they relate to processes. In Chapter 11, we revisit semaphores and address their use with threads.
[2] In function, binary semaphores are similar to the lock files discussed in Chapter 4. Unfortunately, semaphores can only be used by processes residing on the same system, while, with some stretching, lock files can be implemented in a networked environment. Of course, semaphores are much faster and more reliable than lock files.
[3] Notice that the union member arg.array
is assigned the base address of the array start_val
prior to invoking semctl
.
[4] This may be an invalid assumption on some systems, as process scheduling may allow the program invoked second to be run first and thus become the producer. If your output indicates this is happening, enter the two commands on separate lines—do not forget to add the &
after the first command to place it in the background.
[5] The name SSemaphore
(with the extra 'S'
) was chosen to minimize any conflicts with existing semaphore definitions.
18.224.62.160