Let s start our discussion with a whirlwind tour of the GNU/Linux semaphore API. We ll look at code examples illustrating each of the API capabilities such as creating a new semaphore, finding a semaphore, acquiring a semaphore, releasing a semaphore, configuring a semaphore, and finally, removing a semaphore. Once we ve finished the quick overview, we ll dig deeper into the semaphore API.
Note | Semaphores in GNU/Linux are actually semaphore arrays . A single semaphore can represent an array of 64 semaphores. This unique feature of GNU/Linux permits atomic operations over numerous semaphores at the same time. In the early discussions of GNU/Linux semaphores, we ll explore single semaphore uses. In the detailed discussions that follow, we ll look at the more complex semaphore array examples. |
Using the semaphore API requires that the function prototypes and symbols be available to the application. This is done by including the following three header files:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>
To create a semaphore (or get an existing semaphore), we use the semget API function. This function takes a semaphore key, a semaphore count, and a set of flags. The count represents the number of semaphores in the set. In this case, we ll specify the need for one semaphore. The semaphore flags, argument 3 as shown in Listing 16.1, specify that the semaphore is to be created ( IPC_CREAT ). We also specify the read/write permissions to use (in this case 0666 for read/write for the user , group , and system in octal). An important item to consider is that when a semaphore is created, its value is zero. This suits us for this example, but we ll investigate later how to initialize the semaphore s value.
1: #include <stdio.h> 2: #include <sys/sem.h> 3: #include "common.h" 4: 5: int main() 6: { 7: int semid; 8: 9: /* Create the semaphore with the id MY_SEM_ID */ 10: semid = semget ( MY_SEM_ID, 1, 0666 IPC_CREAT ); 11: 12: if (semid >= 0) { 13: 14: printf( "semcreate: Created a semaphore %d\n", semid ); 15: 16: } 17: 18: return 0; 19: }
Upon completion of this simple application, a new globally available semaphore would be available with a key identified by MY_SEM_ID . Any process in the system could use this semaphore.
Now let s look at an application that attempts to acquire an existing semaphore and also another application that releases it. Recall that our previously created semaphore (in Listing 16.1) was initialized with a value of zero. This is identical to a binary semaphore already having been acquired .
1: #include <stdio.h> 2: #include <sys/sem.h> 3: #include <stdlib.h> 4: #include "common.h" 5: 6: int main() 7: { 8: int semid; 9: struct sembuf sb; 10: 11: /* Get the semaphore with the id MY_SEM_ID */ 12: semid = semget ( MY_SEM_ID, 1, 0 ); 13: 14: if (semid >= 0) { 15: 16: sb.sem_num = 0; 17: sb.sem_op = -1; 18: sb.sem_flg = 0; 19: 20: printf( "semacq: Attempting to acquire semaphore %d\n", semid ); 21: 22: /* Acquire the semaphore */ 23: if ( semop ( semid, &sb, 1 ) == -1 ) { 24: 25: printf( "semacq: semop failed.\n" ); 26: exit(-1); 27: 28: } 29: 30: printf( "semacq: Semaphore acquired %d\n", semid ); 31: 32: } 33: 34: return 0; 35: }
We begin by identifying the semaphore identifier with semget at line 12. If this is successful, we build our semaphore operations structure (identified by the sembuf structure). This structure contains the semaphore number, the operation to be applied to the semaphore, and a set of operation flags. Since we have only one semaphore, we use the semaphore number zero to identify it. To acquire the semaphore, we specify an operation of -1. This subtracts one from the semaphore, but only if it s greater than zero to begin with. If the semaphore is already zero, the operation (and the process) will block until the semaphore value is incremented.
With our sembuf created (variable sb ), we use this with the API function semop to acquire the semaphore. We specify our semaphore identifier, our sembuf structure, and then the number of sembufs that were passed in (in this case, one). This implies that we can provide an array of sembuf s, which we ll investigate later. As long as the semaphore operation can finish (semaphore value is nonzero), then it returns with success (a non “1 value). This means that the process performing the semop has acquired the semaphore.
Let s now look at a release example. In this example, we ll demonstrate the semop API function from the perspective of releasing the semaphore.
Note | In many cases, the release would follow the acquire in the same process. This usage allows synchronization between two processes. The first process attempts to acquire the semaphore and then blocks when it s not available. The second process, knowing that another process is sitting blocked on the semaphore, releases it, allowing the process to continue. This provides a lock-step operation between the processes and is practical and useful. |
1: #include <stdio.h> 2: #include <sys/sem.h> 3: #include <stdlib.h> 4: #include "common.h" 5: 6: int main() 7: { 8: int semid; 9: struct sembuf sb; 10: 11: /* Get the semaphore with the id MY_SEM_ID */ 12: semid = semget ( MY_SEM_ID, 1, 0 ); 13: 14: if (semid >= 0) { 15: 16: printf( "semrel: Releasing semaphore %d\n", semid ); 17: 18: sb.sem_num = 0; 19: sb.sem_op = 1; 20: sb.sem_flg = 0; 21: 22: /* Release the semaphore */ 23: if ( semop ( semid, &sb, 1 ) == -1) { 24: 25: printf("semrel: semop failed.\n"); 26: exit(-1); 27: 28: } 29: 30: printf( "semrel: Semaphore released %d\n", semid ); 31: 32: } 33: 34: return 0; 35: }
At line 12 of Listing 16.3, we first identify our semaphore of interest using the semget API function. Having our semaphore identifier, we build our sembuf structure to release the semaphore at line 23 using the semop API function. In this example, our sem_op element is 1 (compared to the “1 in Listing 16.2). In this example, we re releasing the semaphore, which means that we re making it nonzero (and thus available).
Let s now look at a sample application of each of the functions discussed thus far. Listing 16.4 illustrates execution of Listing 16.1 semcreate , Listing 16.2 semacq , and Listing 16.3 semrel .
1: # ./semcreate 2: semcreate: Created a semaphore 1376259 3: # ./semacq & 4: [1] 12189 5: semacq: Attempting to acquire semaphore 1376259 6: # ./semrel 7: semrel: Releasing semaphore 1376259 8: semrel: Semaphore released 1376259 9: # semacq: Semaphore acquired 1376259 10: 11: [1]+ Done ./semacq 12: #
At line 1, we create the semaphore. We emit the identifier associated with this semaphore, 1376259 (which is shown at line 2). Next , at line 3, we perform the semacq application, which acquires the semaphore. We run this in the background (identified by the trailing & symbol) because this application will immediately block as the semaphore is unavailable. At line 4, we see the creation of the new subprocess (where [1] represents the number of subprocesses and 12189 is its process ID, or pid). The semacq application prints out its message, indicating that it s attempting to acquire the semaphore, but then it blocks. We then execute the semrel application to release the semaphore (line 6). We see two messages from this application; the first at line 7 indicates that it is about to release the semaphore, and then at line 8, we see that it was successful. Immediately thereafter, we see the semacq application was able to acquire the newly released semaphore, given its output at line 9. Finally, at line 11, we see the semacq application subprocess finish. Since it unblocked (based upon the presence of its desired semaphore), the semacq s main function reached its return, and thus the process finished.
While there are a number of elements that can be configured for a semaphore, let s look here specifically at reading and writing the value of the semaphore (the current count).
In the first example, Listing 16.5, we ll demonstrate reading the current value of the semaphore. We achieve this using the semctl API function.
1: #include <stdio.h> 2: #include <sys/sem.h> 3: #include <stdlib.h> 4: #include "common.h" 5: 6: int main() 7: { 8: int semid, cnt; 9: 10: /* Get the semaphore with the id MY_SEM_ID */ 11: semid = semget ( MY_SEM_ID, 1, 0 ); 12: 13: if (semid >= 0) { 14: 15: /* Read the current semaphore count */ 16: cnt = semctl ( semid, 0, GETVAL ); 17: 18: if (cnt != -1) { 19: 20: printf("semcrd: current semaphore count %d.\n", cnt); 21: 22: } 23: 24: } 25: 26: return 0; 27: }
Reading the semaphore count is performed at line 16. We specify the semaphore identifier, the index of the semaphore (0), and the command ( GETVAL ). Note that the semaphore is identified by an index because it could represent an array of semaphores (rather than one). The return value from this command is either “1 for error or the count of the semaphore.
We can configure a semaphore with a count using a similar mechanism (as shown in Listing 16.6).
1: #include <stdio.h> 2: #include <sys/sem.h> 3: #include <stdlib.h> 4: #include "common.h" 5: 6: int main() 7: { 8: int semid, ret; 9: 10: /* Get the semaphore with the id MY_SEM_ID */ 11: semid = semget ( MY_SEM_ID, 1, 0 ); 12: 13: if (semid >= 0) { 14: 15: /* Read the current semaphore count */ 16: ret = semctl ( semid, 0, SETVAL, 6 ); 17: 18: if (ret != -1) { 19: 20: printf( "semcrd: semaphore count updated.\n" ); 21: 22: } 23: 24: } 25: 26: return 0; 27: }
As with retrieving the current semaphore value, we can set this value using the semctl API function. The difference here is that along with the semaphore identifier ( semid ) and semaphore index (0), we specify the set command ( SETVAL ) and a value. In this example (line 16 of Listing 16.6), we re setting the semaphore value to 6. Setting the value to 6, as shown here, changes the binary semaphore to a counting semaphore. Six semaphore acquires would be permitted before an acquiring process would block.
Removing a semaphore is also performed through the semctl API function. After retrieving the semaphore identifier (line 10 in Listing 16.7), we use this to remove the semaphore using the semctl API function and the IPC_RMID command (at line 14).
1: #include <stdio.h> 2: #include <sys/sem.h> 3: #include "common.h" 4: 5: int main() 6: { 7: int semid, ret; 8: 9: /* Get the semaphore with the id MY_SEM_ID */ 10: semid = semget ( MY_SEM_ID, 1, 0 ); 11: 12: if (semid >= 0) { 13: 14: ret = semctl ( semid, 0, IPC_RMID); 15: 16: if (ret != -1) { 17: 18: printf( "Semaphore %d removed.\n", semid ); 19: 20: } 21: 22: } 23: 24: return 0; 25: }
As you can probably see, the semaphore API probably is not the simplest that you ve used before.
That s it for our whirlwind tour; next we ll explore the semaphore API in greater detail and look at some of its other capabilities.