Semaphore in System V

Semaphore in System V is maintained in the kernel. It works like what stated in Pthread: to provide a locking protection over limited resources (critical section) when accessing by multiple processes simultaneously. If the resource is being locked, the process needs to wait in queue. It will be notified if the resource becomes available again.

In Unix system, Semaphores are kept in an array which could be configured as a group. The data structure for them are shown as C code below:

struct semid_ds {
  struct ipc_perm sem_perm; // operation permission structure
  struct sem* sem_base;     // pointer to the first semaphore in the set
  ushort sem_nsems;         // number of semaphores in the set
  time_t sem_otime;         // time of last semop()
  time_t sem_ctime;         // last semaphore change time
};

struct sem {
  ushort_t semval;          // non negative semaphore value
  ushort sempid;            // pid of last successful semop() with flags SETVAL,
                            // SETALL
  ushort_t semncnt;         // number of processes waiting semval > current
                            // value
  ushort_t semzcnt;         // number of processes waiting semval = 0
}

We can access (with flag values of 0) or create (with flag IPC_CREAT) a semaphore with semget() System Call# with parameters of IPC Facilities Key# the number of semaphores to be created, and the flags (including semaphore’s permission). semop() is used to release (increment) and obtain (decrement) semaphore or test for zero. It includes using a sembuf struct to specify the operation to be performed on each semaphore in the semaphore set identified by its ID. The detail are shown below:

#define SEMPERM 0x600   // owner can read and write, while group and others can't

key_t key = ftok(...);  // key initialisation
int semid;

struct sembuf {
  short sem_num;  // specify which semaphore to be performed according to its
                  // position in the set
  short sem_op;   // specify the operation to be performed
  short sem_flg;  // operation flags: 0, IPC_NOWAIT, SEM_UNDO
};

struct sembuf oper;

semid = semget(key, 1, SEMPERM | IPC_CREAT | IPC_EXCL); // or
semid = semget(IPC_PRIVATE, 1, SEMPERM | IPC_CREAT);    // to create a unique semaphore set
                                                        // containing one semaphore with SEMPERM permissions
semid = semget(key, 1, 0); // just to access the semaphore set

if (semid == -1) {    // error handling
  if (errno == ...) {
    ...
  }
}

// aquiring a semaphore
oper.sem_num = 0;
oper.sem_op = -1; // decrement 1 from the semaphore, and if semval is less than
                  // |-1|, then the process needs to wait
oper.sem_flag = SEM_UNDO;
semop(semid, &oper, 1);

... // Critical Section

// release a semaphore
oper.sem_num = 0;
oper.sem_op = 1;  // increment 1
oper.sem_flag = SEM_UNDO;
semop(semid, &oper, 1);

The flag for semop() are 0, IPC_NOWAIT, and SEM_UNDO. For flag value of 0, semop() will simply suspend the process execution and keep it wait in queue for the semaphore set if the operation is not successful until it can perform it successfully. IPC_NOWAIT changes that, so it will return immediately if the operation fails. SEM_UNDO tells semop() to undo all operations done on the semaphore upon process exit which could be useful to prevent Deadlocks#.

We could execute some control operations on the semaphore with the function semctl(). Parameters needed are the semaphore ID, number of semaphore to be involved, command issued, and additional arguments to be passed (in type of union). Several commands could be used to control the semaphore:

  • GETVAL returns the semval within the set
  • SETVAL sets the semval within the set
  • GETPID returns the sem.sempid
  • GETNCNT returns the sem.semncnt
  • GETZCNT returns the sem.semzcnt
  • GETALL returns the value of all semaphore in the set
  • SETALL sets all semaphore values in the set
  • IPC_STAT returns the set’s status information
  • IPC_SET sets the effective user/group ID and operation permission for the set
  • IPC_RMID removes the semaphore
union semun {
  int val;              // used for SETVAl only
  struct semid_ds* buf; // used for IPC_SET and IPC_STAT
  ushort* array;        // used for GETALL and SETALL
};

// Example usage:
semun arg;
arg.val = 1;
status = semctl(semid, 0, SETVAL, arg); // set semval as 1

status = semctl(semid, 0, IPC_RMID, arg); // remove a semaphore, arg not used
                                          // here

pid = semctl(semid, 0, GETPID, arg);  // return the PID of process that
                                      // performed the last operation on
                                      // the semaphore
#operating-system #parallelism #resource #unix