1. An overview of the
-
System V semaphores are not used to transfer data between processes. Instead, they are used to synchronize the actions of the process. A common use of semaphores is to synchronize access to a block of shared memory to prevent one process from accessing the shared memory while another process updates the block.
-
A semaphore is an integer maintained by the kernel that is limited to a value greater than or equal to 0. Various operations (i.e., system calls) can be performed on a semaphore:
- Set the semaphore to an absolute value;
- Add a quantity to the current value of the semaphore;
- Subtract a quantity from the current value of the semaphore;
- The waiting semaphore is equal to 0;
The last two operations above can block the call. Because the kernel blocks any attempt to lower the semaphore below zero. Similarly, if the current value of the semaphore is not 0, the calling process waiting for the value of the semaphore to be equal to 0 will block.
- System V semaphores are allocated in group units, so the number of semaphores in the set needs to be specified when creating a semaphore set.
- System V semaphore creation (with an initial value of 0) and initialization are done in separate steps, so a race condition occurs when two processes attempt to perform both steps at the same time.
2. Create a semaphore set
#include<sys/types.h>
#include<sys/sem.h>
int semget(key_t key,int nsems,int semflg);
//return semaphore set identifier on success,or -1 on error
Copy the code
-
Key: A key returned by ftok() using the value IPC_PRIVATE
-
Nsems: Specifies the number of semaphores in the collection, and the value must be greater than 0. If semget() is used to obtain the identifier of an existing set, nsEMS must be less than or equal to the size of the collection (otherwise EINVAL errors occur). Cannot modify the number of semaphores in an existing set.
-
Semflg: Parameter is a bitmask that specifies the permissions imposed on a new semaphore set or on an existing set to be checked.
- IPC_CREAT: If there is no semaphore set associated with the specified key, a new set is created.
- IPC_EXCL: used together with IPC_CREAT. If the semaphore set associated with the specified key already exists, an EEXIST error is returned.
Create a System V semaphore
int semid=semget(IPC_PRIVATE,1,S_IRUSR | S_IWUSR); // Create a semaphore set of 1, the owner can read and write using IPC_PRIVATE, can not display IPC_CREATif (semid == -1)
errExit("semid");
Copy the code
3. Semaphore control operation
#include<sys/types.h>
#include<sys/sem.h>int semctl(int semid,int semnum,int cmd,... /*union semun arg*/);Copy the code
- Semid: Parameter is the identifier of the semaphore set applied by the operation.
- Semnum: Operations performed on a single semaphore that specify the semaphore in the set. For other operations this parameter is ignored and can be set to 0.
- . Semun Union requires the program to define the union.
/* The user should define a union like the following to use it for arguments
for `semctl`.
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
};
Previous versions of this file used to define this union but this is
incorrect. One can test the macro _SEM_SEMUN_UNDEFINED to see whether
one must define the union or not. */
Copy the code
-
The CMD: parameter specifies the action to be performed
General control operations:
-
IPC_RMID: Immediately removes the semaphore set and its associated Semid_DS data structure. All processes blocked by the semop() call will wake up immediately and return an EIDRM error. No ARG argument is required for this operation.
-
IPC_STAT: Places a copy of the semid_DS data structure associated with this semaphore set in the buffer pointed to by arg.buf.
-
IPC_SET: Updates selected fields in the semid_DS data structure associated with this semaphore set with values in the buffer pointed to by arg.buf.
Gets and initializes the signal quantile value
-
GETVAL: semctl() returns the semaphore of the semaphore set specified by semID. No ARG arguments are required for this operation.
-
SETVAL: Changes the semaphore of the semaphore set specified by semid to arg. Val.
-
GETALL: Gets the values of all semaphores in the semaphore set pointed to by semid and places them in the array pointed to by arg. Array. The programmer must make sure that the array has enough space.
-
SETALL: Modifies all semaphores in the collection pointed to by semid using the values in the array pointed to by arg. Array.
Gets information about a single semaphore
The following operation returns information about the semaphore of the first semaphore in the set referenced by semid. All of these operations require read permissions in the semaphore set and do not require arG arguments.
-
GETPID: Returns the process ID of the last process that executed semop() on this semaphore; This value is called the semPID value. If no process has yet executed semop() on the semaphore, then 0 is returned.
-
GETNCNT: Returns the number of processes currently waiting for the semaphore’s value to grow; This value is called the semNCNT value.
-
GETZCNT: Returns the number of processes currently waiting for the semaphore value to become 0; This value is called the semzcnT value.
-
3.1 Data structure associated with semaphore
/* Data structure describing a set of semaphores. */
struct semid_ds
{
struct ipc_perm sem_perm; /* operation permission struct */
__time_t sem_otime; /* last semop() time */
__syscall_ulong_t __glibc_reserved1;
__time_t sem_ctime; /* last time changed by semctl() */
__syscall_ulong_t __glibc_reserved2;
__syscall_ulong_t sem_nsems; /* number of semaphores in set */
__syscall_ulong_t __glibc_reserved3;
__syscall_ulong_t __glibc_reserved4;
};
/* Data structure used to pass permission information to IPC operations. */
struct ipc_perm
{
__key_t __key; /* Key. */
__uid_t uid; /* Owner's user ID. */ __gid_t gid; /* Owner's group ID. */
__uid_t cuid; /* Creator's user ID. */ __gid_t cgid; /* Creator's group ID. */
unsigned short int mode; /* Read/write permission. */
unsigned short int __pad1;
unsigned short int __seq; /* Sequence number. */
unsigned short int __pad2;
__syscall_ulong_t __glibc_reserved1;
__syscall_ulong_t __glibc_reserved2;
};
Copy the code
3.2 Semaphore initialization
-
The programmer must explicitly initialize the semaphore using the semctl() system call. (On Linux, the semaphore returned by semget() is actually initialized to 0, but this cannot be relied upon for portability.)
-
Because the creation and initialization of the semaphore are done separately, there is a race when multiple processes create and initialize the same semaphore, and the initial value of the semaphore is determined by the process that last invoked the initialization.
Solution: initialization of the SEM_otime field in the semid_DS data structure associated with the semaphore set. When a semaphore set is first created, the sem_otime field is initialized to 0, and only subsequent semop() calls change the value of this field. This feature can therefore be used to eliminate race conditions. That is, you just need to insert extra code to force the second process (the one that did not create the semaphore) to wait until the first process initializes the semaphore and executes a semop() call that updates the sem_otime field without changing the semaphore’s value.
semid = semget(key, 1, IPC_CREAT | IPC_EXCL | perms); if(semid ! = -1) { /* Successfully created the semaphore */ union semun arg; struct sembuf sop; sleep(5);printf("%ld: created semaphore\n", (long) getpid()); arg.val = 0; /* So initialize it to 0 */ if (semctl(semid, 0, SETVAL, arg) == -1) errExit("semctl 1"); printf("%ld: initialized semaphore\n", (long) getpid()); /* Perform a "no-op" semaphore operation - changes sem_otime so other processes can see we`ve initialized the set. */ sop.sem_num = 0; /* Operate on semaphore 0 */ sop.sem_op = 0; /* Wait for value to equal 0 */ sop.sem_flg = 0; if (semop(semid, &sop, 1) == -1) errExit("semop"); printf("%ld: completed dummy semop()\n", (long) getpid()); } else { /* We didn`t create the semaphore set* /if(errno ! = EEXIST) { /* Unexpected error from semget() */ errExit("semget 1"); } else { /* Someone else already created it */ const int MAX_TRIES = 10; int j; union semun arg; struct semid_ds ds; semid = semget(key, 1, perms); /* So just get ID */ if (semid == -1) errExit("semget 2"); printf("%ld: got semaphore key\n", (long) getpid()); /* Wait until another process has called semop() */ arg.buf = &ds; for (j = 0; j < MAX_TRIES; j++) { printf("Try %d\n", j); if (semctl(semid, 0, IPC_STAT, arg) == -1) errExit("semctl 2"); if(ds.sem_otime ! = 0) /* Semop() performed? * /break; /* Yes, quit loop */ sleep(1); /* If not, wait and retry */ } if (ds.sem_otime == 0) /* Loop ran to completion! */ fatal("Existing semaphore not initialized"); }}Copy the code
4. Semaphore operation
#include<sys/types.h>
#include<sys/sem.h>
int semop(int semid,struct sembuf *sops,unsigned int nsops);
//return 0 on succes,or -1 on error
struct sembuf
{
unsigned short int sem_num; /* semaphore number */
short int sem_op; /* semaphore operation */
short int sem_flg; /* operation flag */
};
Copy the code
-
The sOPS argument is a pointer to an array containing the operations that need to be performed.
-
The NSOPS parameter gives the size of the array (it must contain at least one element). The operations will be performed atomically in the order in the array.
-
The sem_num field identifies which semaphore in the collection to perform the operation on.
-
The sem_op field specifies the operation to be performed.
- If sem_op is greater than 0, the value of sem_OP is added to the semaphore value, with the result that other processes waiting for the reduced semaphore value may wake up and perform their operations. The calling process must have modify (write) permission on the semaphore.
- If sem_op is equal to 0, the semaphore value is checked to see if it is currently equal to 0. If equal to 0, the operation will end immediately, otherwise semop() will block until the signal quantity value becomes zero. The calling process must have read permission on the semaphore.
- If sem_op is less than 0, the sem_op is subtracted from the sem_op value. If the current value of the semaphore is greater than or equal to the absolute value of sem_op, the operation ends immediately. Otherwise it blocks until the current value of the semaphore is greater than or equal to 0. The calling process must have modify (write) permission on the semaphore.
When semop() calls a blocking event, the process remains blocked until one of the following occurs.
- Another process modifies the semaphore value so that the operation to be performed can continue forward.
- A signal interrupts the semop() call. An EINTR error is returned when this happens.
- Another process removed the semaphore referenced by semID. Semop () returns an EIDRM error when this happens
-
Sem_flg: Parameter is a bitmask.
- The IPC_NOWAIT flag prevents semop() from blocking. Semop () returns an EAGAIN error if it was supposed to block.
- The SEM_UNDO flag is used to undo all operations performed by a process before it terminates, namely, subtracting the sum from the current value of the semaphore (the sum of operations performed by a process on a semaphore, known as semadj).
It should be noted that semop() is an atomic operation that either performs all operations at once or blocks until it can perform all operations at once.
The semtimedop() system call performs the same task as semop(), but with an extra timeout parameter that specifies the upper limit of how long the call can block.
#define _GNU_SOURCE
#include<sys/types.h>
#include<sys/sem.h>
int semtimedop(int semid,struct sembuf *sops,unsigned int nsops,struct timespec *timeout);
//return 0 on success, or -1 on error
Copy the code
5. Implement binary semaphore using System V semaphore
Boolean bsUseSemUndo = FALSE;
Boolean bsRetryOnEintr = TRUE;
int /* Initialize semaphore to 1 (i.e., "available") */
initSemAvailable(int semId, int semNum)
{
union semun arg;
arg.val = 1;
return semctl(semId, semNum, SETVAL, arg);
}
int /* Initialize semaphore to 0 (i.e., "in use") */
initSemInUse(int semId, int semNum)
{
union semun arg;
arg.val = 0;
return semctl(semId, semNum, SETVAL, arg);
}
/* Reserve semaphore (blocking), return 0 on success, or -1 with 'errno'
set to EINTR if operation was interrupted by a signal handler */
int /* Reserve semaphore - decrement it by 1 */
reserveSem(int semId, int semNum)
{
struct sembuf sops;
sops.sem_num = semNum;
sops.sem_op = -1;
sops.sem_flg = bsUseSemUndo ? SEM_UNDO : 0;
while (semop(semId, &sops, 1) == -1)
if(errno ! = EINTR || ! bsRetryOnEintr)return- 1;return 0;
}
int /* Release semaphore - increment it by 1 */
releaseSem(int semId, int semNum)
{
struct sembuf sops;
sops.sem_num = semNum;
sops.sem_op = 1;
sops.sem_flg = bsUseSemUndo ? SEM_UNDO : 0;
return semop(semId, &sops, 1);
}
Copy the code
6. Restrictions on obtaining semaphores
union semun arg; struct seminfo buf; arg.__buf=&buf; Semctl (0, 0, IPC_INFO, arg); struct seminfo { int semmap; int semmni; Int semmns; // System-level restriction that limits the number of semaphore identifiers that can be created // System-level limit, which limits the number of semaphores in all semaphore sets. int semmnu; // System-level limit, which limits the total number of semaphore undo structures. int semmsl; // The maximum number of semaphores in a semaphore set int semopm; // The maximum number of operations each semop() call can perform. (semop(),E2BIG) int semume; // The maximum number of undo entries per semaphore undo structure. int semusz; int semvmx; // The maximum value a semaphore can take. (semop(),ERANGE) int semaem; // The maximum value that can be recorded in the semadj sum. (semop (), ERANGE)};Copy the code