The Semaphore API


As we noted before, the semaphore API handles not only the case of managing a single semaphore, but also groups (or arrays) of semaphores. We ll investigate their use in this section. For a quick review, Table 16.1 shows the API functions and describes their uses. In the following discussion, we ll continue to use the term semaphore , but this could refer instead to a semaphore array.

Table 16.1: Semaphore API Functions and Their Uses

API Function

Uses

semget

Create a new semaphore

 

Get an existing semaphore

semop

Acquire or release a semaphore

semctl

Get info about a semaphore

 

Set info about a semaphore

 

Remove a semaphore

We ll address each of these functions in the following sections using both simple examples (single semaphore) and the more complex uses (semaphore arrays).

semget

The semget API function serves two fundamental roles. Its first use is in the creation of new semaphores. The second use is identifying an existing semaphore. In both cases, the response from semget is a semaphore identifier (a simple integer value representing the semaphore). The prototype for the semget API function is defined as:

 int  semget  (key_t key, int nsems, int semflg); 

The key argument specifies a system-wide identifier that uniquely identifies this semaphore. The key must be nonzero or the special symbol IPC_PRIVATE . The IPC_PRIVATE variable tells semget that no key is provided and to simply make one up. Since no key exists, there s no way for other processes to know about this semaphore. Therefore, it s a private semaphore for this particular process.

The developer can create a single semaphore (with an nsems value of 1) or multiple semaphores. If we re using semget to get an existing semaphore, this value can simply be zero.

Finally, the semflg argument allows the developer to alter the behavior of the semget API function. The semflg argument can take on three basic forms, depending upon what is desired by the caller. In the first form, the developer desires to create a new semaphore. In this case, the semflg argument must be the IPC_CREAT value OR d with the permissions (see Table 16.2). The second form also provides for semaphore creation, but with the constraint that if the semaphore already exists, an error is generated. This second form requires the semflg argument to be set to ( IPC_CREAT IPC_EXCL ) along with the permissions. If the second form is used and the semaphore already exists, the call will fail “1 return) with errno set to EEXIST . The third form takes a zero for semflg and identifies that an existing semaphore is being requested .

Table 16.2: Semaphore Permissions for the semget semflg Argument

Symbol

Value

Meaning

S_IRUSR

0400

User has read permission

S_IWUSR

0200

User has write permission

S_IRGRP

0040

Group has read permission

S_IWGRP

0020

Group has write permission

S_IROTH

0004

Other has read permission

S_IWOTH

0002

Other has write permission

Let s now look at a few examples of semget , used in each of the three scenarios defined above. In the examples shown below, assume semid is an int value, and mySem is of type key_t . In the first example, we ll create a new semaphore (or access an existing one) of the private type.

 semid =  semget  (IPC_PRIVATE, 1, IPC_CREAT  0666); 

Once the semget call completes, our semaphore identifier is stored in semid . Otherwise, if an error occurred, a “1 would be returned. Note that in this example (using IPC_PRIVATE ), semid is all we have to identify this semaphore. If semid were somehow lost, there would be no way to find this semaphore again.

In the next example, we ll create a semaphore using a system-wide unique key value ( 0x222 ). We also desire that if the semaphore already exists, we don t simply get its value, but instead fail the call. Recall that this is provided by the IPC_EXCL command, as:

 // Create a new semaphore     semid =  semget  (0x222, 1, IPC_CREAT  IPC_EXCL  0666);     if (semid == -1) {       printf("Semaphore already exists, or error\n");     } else {       printf("Semaphore created (id %d)\n", semid);     } 

If we didn t want to rely on the fact that 0x222 may not be unique in our system, we could use the ftok system function. This function provides the means to create a new unique key in the system. It does this by using a known file in the filesystem and an integer number. The file in the filesystem will be unique by default (considering its path ). Therefore, by using the unique file (and integer), it s an easy task to then create a unique system-wide value. Let s look at an example of the use of ftok to create a unique key value. We ll assume for this example that our file and path are defined as /home/mtj/semaphores/mysem .

 key_t mySem;     int   semid;     // Create a key based upon the defined path and number     myKey =  ftok  ("/home/mtj/semaphores/mysem", 0);     semid =  semget  (myKey, 1, IPC_CREAT  IPC_EXCL  0666); 

Note that each time ftok is called with those parameters, the same key is generated (which is why this method works at all!). As long as each process that needs access to the semaphore knows about the file and number, the key can be recalculated and then used to identify the semaphore.

In the examples discussed thus far, we ve created a single semaphore. We can create an array of semaphores by simply specifying an nsems value greater than one, such as:

 semarrayid =  semget  (myKey, 10, IPC_CREAT  0666); 

The result is a semaphore array being created that consists of 10 semaphores. The return value ( semarrayid ) represents the entire set of semaphores. We ll look at how individual semaphores can be addressed in the semctl and semop discussions.

In our last example of semget , we ll simply use it to get the semaphore identifier of an existing semaphore. In this example, we specify our key value and no command:

 semid =  semget  (0x222, 0, 0);     if (semid == -1) {       printf("Semaphore does not exist...\n");     } 

One final note on semaphores is that, just like message queues, a set of defaults is provided to the semaphore as it s created. The parameters that are defined are shown in Table 16.3. Later on in the discussion of semctl , we ll demonstrate how some of the parameters can be changed.

Table 16.3: Semaphore Internal Values

Parameter

Default Value

sem_perm.cuid

Effective User ID of the calling process (creator)

sem_perm.uid

Effective User ID of the calling process (owner)

sem_perm.cgid

Effective Group ID of the calling process (creator)

sem_perm.gid

Effective Group ID of the calling process (owner)

sem_perm.mode

Permissions (lower 9 bits of semflg )

sem_nsems

Set to the value of nsems

sem_otime

Set to zero (last semop time)

sem_ctime

Set to the current time (create time)

The process can override some of these parameters. We ll explore this later in our discussion of semctl .

semctl

The semctl API function provides a number of control operations on semaphores or semaphore arrays. Example functionality ranges from setting the value of the semaphore (as shown in Listing 16.6) to removing a semaphore or semaphore array (see Listing 16.7). We ll look at these and other examples in this section.

The function prototype for the semctl call is shown below:

 int  semctl  (int semid, int semnum, int cmd, ...); 

The first argument defines the semaphore identifier, the second, the semaphore number of interest, the third the command to be applied, and then potentially another argument (usually defined as a union). The operations that can be performed are shown in Table 16.4.

We ll now look at some examples of each of these operations in semctl , focusing on semaphore array examples where applicable . In our first example, we ll illustrate the setting of a semaphore value and then returning its value. In this example, we first set the value of the semaphore to 10 (using the command SETVAL ) and then read it back out using GETVAL . Note that the semnum argument (argument 2) defines an individual semaphore. Later on, we ll look at the semaphore array case with GETALL and SETALL .

Table 16.4: Operations That Can Be Performed Using semctl

Command

Description

Fourth Argument

GETVAL

Return the semaphore value.

 

SETVAL

Set the semaphore value.

int

GETPID

Return the pid that last operated on the semaphore (semop).

 

GETNCNT

Return the number of processes awaiting the defined semaphore to increase in value.

int

GETZCNT

Return the number of processes awaiting the defined semaphore to become zero.

int

GETALL

Return the value of each semaphore in a semaphore array.

u_short*

SETALL

Set the value of each semaphore in a semaphore array.

u_short*

IPC_STAT

Return the effective user, group, and permission for a semaphore.

struct semid_ds*

IPC_SET

Set the effective user, group, and permissions for a semaphore.

struct semid_ds*

IPC_RMID

Remove the semaphore or semaphore-array.

 
 int semid, ret, value;     ...     /* Set the semaphore to 10 */     ret =  semctl  (semid, 0, SETVAL, 10);     ...     /* Read the semaphore value (return value) */     value =  semctl  (semid, 0, GETVAL); 

The GETPID command allows us to identify the last process that performed a semop on the semaphore. The process identifier is the return value, and argument 4 is not used in this case.

 int semid, pid;     ...     pid =  semctl  (semid, 0, GETPID); 

If no semop has been performed on the semaphore, the return value will be zero.

To identify the number of semaphores that are currently awaiting a semaphore to increase in value, we can use the GETNCNT command. We can also identify the number of processes that are awaiting the semaphore value to become zero using GETZCNT . Both of these commands are illustrated below for the semaphore numbered zero:

 int semid, count;     /* How many processes are awaiting this semaphore to increase */     count =  semctl  (semid, 0, GETNCNT);     /* How many processes are awaiting this semaphore to become zero */     count =  semctl  (semid, 0, GETZCNT); 

Now let s look at an example of some semaphore array operations. Listing 16.8 illustrates both the SETVAL and GETVAL commands with semctl .

In this example, we ll create a semaphore array of 10 semaphores. The creation of the semaphore array is performed at lines 20 “21 using the semget API function. Note that since we re going to create and remove the semaphore array within this same function, we use no key and instead use the IPC_PRIVATE key. Our MAX__SEMAPHORES symbolic defines the number of semaphores that we ll create, and we finally specify that we re creating the semaphore array ( IPC_CREAT ) with the standard permissions.

Next, we initialize our semaphore value array (lines 26 “30). While this is not a traditional example, we initialize each semaphore to one plus its semnum (so semaphore zero has a value of one, semaphore one has a value of two, and so on). We do this so that we can inspect the value array later and know what we re looking at. At line 33, we set our arg.array parameter to the address of the array ( sem_array ). Note that we re using the semun union, which defines some commonly used types for semaphores. In this case, we use the unsigned short field to represent an array of semaphore values.

At line 36, we use the semctl API function and the SETALL command to set the semaphore values. We provide our semaphore identifier, semnum as zero (unused in this case), the SETALL command, and finally our semun union. Upon return of this API function, the semaphore array identified by semid has the values as defined in sem_array .

Next, we explore the GETALL command, which is used to retrieve the array of values for the semaphore array. We first set our arg.array to a new array (just to avoid reusing our existing array that has the contents that we re looking for), at line 41. At line 44, we call semctl again with our semid , zero for semnum (unused here, again), the GETALL command, and our semun union.

To illustrate what we ve read, we loop through the sem_read_array and emit each value for each semaphore index within the semaphore array (lines 49 “53).

While GETALL allows us to retrieve the entire semaphore array in one call, we could have performed the same action using the GETVAL command, calling semctl for each semaphore of the array. This is illustrated at lines 56 “62. This also applies to the SETVAL command to mimic the SETALL behavior.

Finally, at line 65, we use the semctl API function with the IPC_RMID command to remove the semaphore array.

Listing 16.8: Creating and Manipulating Semaphore Arrays (on the CD-ROM at ./source/ch16/semall.c )
start example
  1:  #include <stdio.h>  2:  #include <sys/types.h>  3:  #include <sys/sem.h>  4:  #include <errno.h>  5:   6:  #define MAX_SEMAPHORES  10  7:   8:  int main()  9:  {  10:  int i, ret, semid;  11:  unsigned short sem_array[MAX_SEMAPHORES];  12:  unsigned short sem_read_array[MAX_SEMAPHORES];  13:   14:  union semun {  15:  int val;  16:  struct semid_ds *buf;  17:  unsigned short *array;  18:  } arg;  19:   20:  semid =  semget  (IPC_PRIVATE, MAX_SEMAPHORES,  21:  IPC_CREAT  0666);  22:   23:  if (semid != -1) {  24:   25:  /* Initialize the sem_array */  26:  for (i = 0 ; i < MAX_SEMAPHORES ; i++) {  27:   28:  sem_array[i] = (unsigned short)(i+1);  29:   30:  }  31:   32:  /* Update the arg union with the sem_array address */  33:  arg.array = sem_array;  34:   35:  /* Set the values of the semaphore-array */  36:  ret =  semctl  (semid, 0, SETALL, arg);  37:   38:  if (ret == -1) printf("SETALL failed (%d)\n", errno);  39:   40:  /* Update the arg union with another array for read */  41:  arg.array = sem_read_array;  42:   43:  /* Read the values of the semaphore array */  44:  ret =  semctl  (semid, 0, GETALL, arg);  45:   46:  if (ret == -1) printf("GETALL failed (%d)\n", errno);  47:   48:  /* print the sem_read_array */  49:  for (i = 0 ; i < MAX_SEMAPHORES ; i++) {  50:   51:  printf("Semaphore %d, value %d\n", i, sem_read_array[i]);  52:   53:  }  54:   55:  /* Use GETVAL in a similar manner */  56:  for (i = 0 ; i < MAX_SEMAPHORES ; i++) {  57:   58:  ret =  semctl  (semid, i, GETVAL);  59:   60:  printf("Semaphore %d, value %d\n", i, ret);  61:   62:  }  63:   64:  /* Delete the semaphore */  65:  ret =  semctl  (semid, 0, IPC_RMID);  66:   67:  } else {  68:   69:  printf("Could not allocate semaphore (%d)\n", errno);  70:   71:  }  72:   73:  return 0;  74:  } 
end example
 

Executing this application (called semall ) produces the output shown in Listing 16.9. Not surprisingly, the GETVAL emits identical output as that shown for the GETALL .

Listing 16.9: Output from the semall Application Shown in Listing 16.8
start example
 #  ./semall  Semaphore 0, value 1     Semaphore 1, value 2     Semaphore 2, value 3     Semaphore 3, value 4     Semaphore 4, value 5     Semaphore 5, value 6     Semaphore 6, value 7     Semaphore 7, value 8     Semaphore 8, value 9     Semaphore 9, value 10     Semaphore 0, value 1     Semaphore 1, value 2     Semaphore 2, value 3     Semaphore 3, value 4     Semaphore 4, value 5     Semaphore 5, value 6     Semaphore 6, value 7     Semaphore 7, value 8     Semaphore 8, value 9     Semaphore 9, value 10     # 
end example
 

The IPC_STAT command is used to retrieve the current information about a semaphore or semaphore array. The data is retrieved into a structure called semid_ds and contains a variety of parameters. The application that reads this information is shown in Listing 16.10. We read the semaphore information at line 18 using the semctl API function and the IPC_STAT command. The information captures is then emitted at lines 27 “49.

Listing 16.10: Reading Semaphore Information Using IPC_STAT (on the CD-ROM at ./source/ch16/semstat.c )
start example
  1:  #include <stdio.h>  2:  #include <sys/sem.h>  3:  #include <time.h>  4:  #include "common.h"  5:   6:  int main()  7:  {  8:  int semid, ret;  9:  struct semid_ds sembuf;  10:   11:  union semun {  12:  int val;  13:  struct semid_ds *buf;  14:  unsigned short *array;  15:  } arg;  16:   17:  /* Get the semaphore with the id MY_SEM_ID */  18:  semid =  semget  (MY_SEM_ID, 1, 0);  19:   20:  if (semid >= 0) {  21:   22:  arg.buf = &sembuf;  23:  ret =  semctl  (semid, 0, IPC_STAT, arg);  24:   25:  if (ret != -1) {  26:   27:  if (sembuf.sem_otime) {  28:  printf("Last semop time %s",  29:  ctime(&sembuf.sem_otime));  30:  }  31:   32:  printf("Last change time %s",  33:  ctime(&sembuf.sem_ctime));  34:   35:  printf("Number of semaphores %ld\n",  36:  sembuf.sem_nsems);  37:   38:  printf("Owners user id %d\n",  39:  sembuf.sem_perm.uid);  40:  printf("Owners group id %d\n",  41:  sembuf.sem_perm.gid);  42:   43:  printf("Creators user id %d\n",  44:  sembuf.sem_perm.cuid);  45:  printf("Creators group id %d\n",  46:  sembuf.sem_perm.cgid);  47:   48:  printf("Permissions 0%o\n",  49:  sembuf.sem_perm.mode);  50:   51:  }  52:   53:  }  54:   55:  return 0;  56:  } 
end example
 

Three of the fields shown can be updated through another call to semctl using the IPC_SET call. The three updateable parameters are the effective user ID ( sem_perm.uid ), the effective group ID ( sem_perm.gid ), and the permissions ( sem_perm.mode ). The following code snippet illustrates modifying the permissions:

 /* First, read the semaphore information */     arg.buf = &sembuf;     ret =  semctl  (semid, 0, IPC_STAT, arg);     /* Next, update the permissions */     sembuf.sem_perm.mode = 0644;     /* Finally, update the semaphore information */     ret =  semctl  (semid, 0, IPC_SET, arg); 

Once the IPC_SET semctl has completed, the last change time ( sem_ctime ) is updated to the current time.

Finally, the IPC_RMID command permits us to remove a semaphore or semaphore array. A code snippet demonstrating this process is shown below:

 int semid;     ...     semid =  semget  (the_key, NUM_SEMAPHORES, 0);     ret =  semctl  (semid, 0, IPC_RMID); 

Note that if any processes were currently blocked on the semaphore, they would be immediately unblocked with an error return and errno would be set to EIDRM .

semop

The semop API function provides the means to acquire and release a semaphore or semaphore array. The basic operations provided by semop are to decrement a semaphore (acquire one or more semaphores) or to increment a semaphore (release one or more semaphores). The API for the semop function is defined as:

 int  semop  (int semid, struct sembuf *sops, unsigned int nsops); 

The semop takes three parameters: a semaphore identifier ( semid ), a sembuf structure, and the number of semaphore operations to be performed ( nsops ). The semaphore structure defines the semaphore number of interest, the operation to perform, and a flags word that can be used to alter the behavior of the operation. The sembuf structure is shown below:

 struct sembuf {         unsigned short sem_num;         short sem_op;         short sem_flg;     }; 

As you can imagine, the sembuf array can produce very complex semaphore interactions. We can acquire one semaphore and release another in a single semop operation.

Let s look at a simple application that acquires 10 semaphores in one operation. This application is shown in Listing 16.11.

An important difference to notice here is that rather than specify a single sembuf structure (as we did in single semaphore operations), we specify an array of sembufs (line 9). We identify our semaphore array at line 12; note again that we specify the number of semaphores ( nsems , or number of semaphores, as argument 2). We build out our sembuf array as acquires (with a sem_op of “1), and also initialize the sem_num field with the semaphore number. This specifies that we want to acquire each of the semaphores in the array. If one or more aren t available, the operation blocks until all semaphores can be acquired .

At line 26, we perform the semop API function to acquire the semaphores. Upon acquisition (or error), the semop function returns to the application. As long as the return value is not -1, we ve successfully acquired the semaphore array. Note that we could specify -2 for each sem_op , which would require that two counts of the semaphore would be required for successful acquisition.

Listing 16.11: Acquiring an Array of Semaphores Using semop (on the CD-ROM at ./source/ch16/semaacq.c )
start example
  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, i;  9:  struct sembuf sb[10];  10:   11:  /* Get the semaphore with the id MY_SEM_ID */  12:  semid =  semget  (MY_SEMARRAY_ID, 10, 0);  13:   14:  if (semid >= 0) {  15:   16:  for (i = 0 ; i < 10 ; i++) {  17:  sb[i].sem_num = i;  18:  sb[i].sem_op = -1;  19:  sb[i].sem_flg = 0;  20:  }  21:   22:  printf("semaacq: Attempting to acquire semaphore %d\n",  23:  semid);  24:   25:  /* Acquire the semaphores */  26:  if (  semop  (semid, &sb[0], 10) == -1) {  27:   28:  printf("semaacq: semop failed.\n");  29:  exit(-1);  30:   31:  }  32:   33:  printf("semaacq: Semaphore acquired %d\n", semid);  34:   35:  }  36:   37:  return 0;  38:  } 
end example
 

Next, let s look at the semaphore release operation. We ll include only the changes from Listing 16.11, as they re very similar (on the CD-ROM at ./source/_ch16/semarel.c ). In fact, the only difference is the sembuf initialization:

 for (i = 0 ; i < 10 ; i++) {       sb[i].sem_num = i;       sb[i].sem_op = 1;       sb[i].sem_flg = 0;     } 

In this example, we increment the semaphore (release) instead of decrementing it (as was done in Listing 16.11).

The sem_flg within the sembuf structure permits us to alter the behavior of the semop API function. Two flags are possible, as shown in Table 16.5.

Table 16.5: Semaphore Flag Options ( sembuf.sem_flg )

Flag

Purpose

SEM_UNDO

Undo the semaphore operation if the process exits.

IPC_NOWAIT

Return immediately if the semaphore operation cannot be performed (if the process would block) and return an errno of EAGAIN .

Another useful operation that can be performed on semaphores is the wait-for-zero operation. In this case, the process is blocked until the semaphore value becomes zero. This is performed by simply setting the sem_op field to zero, as:

 struct sembuf sb;     ...     sb.sem_num = 0;        // semaphore 0     sb.sem_op = 0;        // wait for zero     sb.sem_flg = 0;        // no flags     ... 

As with previous semop s, setting sem_flg with IPC_NOWAIT causes semop to return immediately if the operation would block with an errno of EAGAIN .

Finally, if the semaphore is removed while a process is blocked on it (via a semop operation), the process becomes immediately unblocked and an errno value is returned as EIDRM .




GNU/Linux Application Programming
GNU/Linux Application Programming (Programming Series)
ISBN: 1584505680
EAN: 2147483647
Year: 2006
Pages: 203
Authors: M. Tim Jones

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net