Keywords: lpg

Next: semtool: An interactive semaphore Up: 6.4.3 Semaphores Previous: SYSTEM CALL: semop()

### SYSTEM CALL: semctl()

      SYSTEM CALL: semctl();
      PROTOTYPE: int semctl ( int semid, int semnum, int cmd, union semun arg );
        RETURNS: positive integer on success
                 -1 on error: errno = EACCESS (permission denied)
                                      EFAULT (invalid address pointed to by arg argument)
                                      EIDRM (semaphore set was removed)
                                      EINVAL (set doesn't exist, or semid is invalid)
                                      EPERM (EUID has no privileges for cmd in arg)
                                      ERANGE (semaphore value out of range)
      NOTES: Performs control operations on a semaphore set

The semctl system call is used to perform control operations on a semaphore set. This call is analogous to the msgctl system call which is used for operations on message queues. If you compare the argument lists of the two system calls, you will notice that the list for semctl varies slightly from that of msgctl. Recall that semaphores are actually implemented as sets, rather than as single entities. With semaphore operations, not only does the IPC key need to be passed, but the target semaphore within the set as well.

Both system calls utilize a cmd argument, for specification of the command to be performed on the IPC object. The remaining difference lies in the final argument to both calls. In msgctl, the final argument represents a copy of the internal data structure used by the kernel. Recall that we used this structure to retrieve internal information about a message queue, as well as to set or change permissions and ownership of the queue. With semaphores, additional operational commands are supported, thus requiring a more complex data type as the final argument. The use of a union confuses many neophyte semaphore programmers to a substantial degree. We will dissect this structure carefully, in an effort to prevent any confusion.

The first argument to `semctl()` is the key value (in our case returned by a call to `semget`). The second argument (`semun`) is the semaphore number that an operation is targeted towards. In essence, this can be thought of as an index into the semaphore set, with the first semaphore (or only one) in the set being represented by a value of zero (0).

The `cmd` argument represents the command to be performed against the set. As you can see, the familiar IPC_STAT/IPC_SET commands are present, along with a wealth of additional commands specific to semaphore sets:

IPC_STAT

  Retrieves the semid_ds structure for a set, and stores it in the address of the buf argument in the semun union.

IPC_SET

  Sets the value of the ipc_perm member of the semid_ds structure for a set. Takes the values from the buf argument of the semun union.

IPC_RMID

  Removes the set from the kernel.

GETALL

  Used to obtain the values of all semaphores in a set. The integer values are stored in an array of unsigned short integers pointed to by the array member of the union.

GETNCNT

  Returns the number of processes currently waiting for resources.

GETPID

  Returns the PID of the process which performed the last semop call.

GETVAL

  Returns the value of a single semaphore within the set.

GETZCNT

  Returns the number of processes currently waiting for 100% resource utilization.

SETALL

  Sets all semaphore values with a set to the matching values contained in the array member of the union.

SETVAL

  Sets the value of an individual semaphore within the set to the val member of the union.

The `arg` argument represents an instance of type `semun`. This particular union is declared in `linux/sem.h` as follows:

            /* arg for semctl system calls. */
            union semun {
                    int val;                /* value for SETVAL */
                    struct semid_ds *buf;   /* buffer for IPC_STAT & IPC_SET */
                    ushort *array;          /* array for GETALL & SETALL */
                    struct seminfo *__buf;  /* buffer for IPC_INFO */
                    void *__pad;
            };

`val`

  Used when the SETVAL command is performed. Specifies the value to set the semaphore to.

`buf`

  Used in the IPC_STAT/IPC_SET commands. Represents a copy of the internal semaphore data structure used in the kernel.

`array`

  A pointer used in the GETALL/SETALL commands. Should point to an array of integer values to be used in setting or retrieving all semaphore values in a set.

The remaining arguments __buf and __pad are used internally in the semaphore code within the kernel, and are of little or no use to the application developer. As a matter of fact, these two arguments are specific to the Linux operating system, and are not found in other UNIX implementations.

Since this particular system call is arguably the most difficult to grasp of all the System V IPC calls, we'll examine multiple examples of it in action.

The following snippet returns the value of the passed semaphore. The final argument (the union) is ignored when the GETVAL command is used:

    int get_sem_val( int sid, int semnum )
    {
            return( semctl(sid, semnum, GETVAL, 0));
    }

To revisit the printer example, let's say the status of all five printers was required:

            #define MAX_PRINTERS 5

            printer_usage()
            {
                    int x;

                    for(x=0; x<MAX_PRINTERS; x++)
                            printf("Printer %d: %d\n\r", x, get_sem_val( sid, x ));
            }

Consider the following function, which could be used to initialize a new semaphore value:

    void init_semaphore( int sid, int semnum, int initval)
    {
            union semun semopts;

            semopts.val = initval;
            semctl( sid, semnum, SETVAL, semopts);
    }

Note that the final argument of semctl is a copy of the union, rather than a pointer to it. While we're on the subject of the union as an argument, allow me to demonstrate a rather common mistake when using this system call.

Recall from the msgtool project that the IPC_STAT and IPC_SET commands were used to alter permissions on the queue. While these commands are supported in the semaphore implementation, their usage is a bit different, as the internal data structure is retrieved and copied from a member of the union, rather than as a single entity. Can you locate the bug in this code?

    /* Required permissions should be passed in as text (ex: "660") */

    void changemode(int sid, char *mode)
    {
            int rc;
            struct semid_ds mysemds;

            /* Get current values for internal data structure */
            if((rc = semctl(sid, 0, IPC_STAT, semopts)) == -1)
            {
                    perror("semctl");
                    exit(1);
            }

            printf("Old permissions were %o\n", semopts.buf->sem_perm.mode);

            /* Change the permissions on the semaphore */
            sscanf(mode, "%o", &semopts.buf->sem_perm.mode);

            /* Update the internal data structure */
            semctl(sid, 0, IPC_SET, semopts);

            printf("Updated...\n");
    }

The code is attempting to make a local copy of the internal data structure for the set, modify the permissions, and IPC_SET them back to the kernel. However, the first call to semctl promptly returns EFAULT, or bad address for the last argument (the union!). In addition, if we hadn't checked for errors from that call, we would have gotten a memory fault. Why?

Recall that the IPC_SET/IPC_STAT commands use the buf member of the union, which is a pointer to a type semid_ds. Pointers are pointers are pointers are pointers! The buf member must point to some valid storage location in order for our function to work properly. Consider this revamped version:

    void changemode(int sid, char *mode)
    {
            int rc;
            struct semid_ds mysemds;

            /* Get current values for internal data structure */

            /* Point to our local copy first! */
            semopts.buf = &mysemds;

            /* Let's try this again! */
            if((rc = semctl(sid, 0, IPC_STAT, semopts)) == -1)
            {
                    perror("semctl");
                    exit(1);
            }

            printf("Old permissions were %o\n", semopts.buf->sem_perm.mode);

            /* Change the permissions on the semaphore */
            sscanf(mode, "%o", &semopts.buf->sem_perm.mode);

            /* Update the internal data structure */
            semctl(sid, 0, IPC_SET, semopts);

            printf("Updated...\n");
    }

Next: semtool: An interactive semaphore Up: 6.4.3 Semaphores Previous: SYSTEM CALL: semop()