Resource sharing is an important feature of UNIX multi-user systems. semaphore is a mechanism to prevent two or more processes from simultaneously accessing shared resources. Before the semaphore mechanism is implemented, the file locking method is usually used. The algorithm is described as follows: (1) Locking Algorithm Int lock (lockfile) /* If the return value is 0, the operation succeeds. If the other value is 0, the operation fails */ Char * lockfile;/* lock file name */ { Intfd, ret = 0; Extern int errno; If (FD = open (lockfile, o_wronly | o_creat | o_excl, 0666) =-1 & Errno = eexist) ret = 1; Return (RET ); } (2) Unlock algorithm Unlock (lockfile) Char * lockfile;/* lock file name */ { Unlink (lockfile ); } This method is feasible for the process with a small number of times to access shared resources, but the overhead of heavy load usage is too large. Moreover, once the lock fails, the process may try again at any time; when the system crashes or restarts, the locked file may be forgotten. The Dekker algorithm published by Dijkstra provides an implementation of semaphores and defines two primitive operations for an integer object: P and V. Its C description is as follows: Void P (SEM) Int * SEM; { While (* SEM <= 0 ); (* SEM )--; } Void V (SEM) Int * SEM; { (* SEM) ++; } However, the above algorithms cannot be programmed in the user space, because ① semaphore variables pointed to by SEM cannot be shared among processes, and they have their own data segments; ② functions are not atomic, the kernel can interrupt a process at any time. ③ if the SEM is 0, the process does not release the CPU. Therefore, the semaphore must be provided by the kernel. It can share data between processes and perform atomic operations (that is, a group of operations can be either performed in full or not ), CPU can be allocated to another process when a process is blocked. Unixsystemv uses a long integer as the unique identifier of a set of semaphores. semaphores are usually composed of the following elements: ① Semaphore value, ② Process ID of the last process that operates the semaphore, ③ Number of processes waiting to increase the semaphore Value, ④ Number of processes waiting for the semaphore value to be 0. The related system calls are as follows: # Include <sys/types. h> # Include <sys/IPC. h> # Include <sys/SEM. h> Int semget (Key, Count, flags) /* Get the identifier of the semaphore Set */ Key_tkey;/* the key of the semaphore Set */ Intcount;/* Number of elements in the semaphore Set */ Intflags;/* optional parameter */ /* Return the semaphore collection identifier. If an error occurs,-1 */ Int semop (SID, OPS, NOPs)/* semaphore operation */ Int Sid;/* semaphore collection identifier */ Struct sembuf * OPS;/* pointer to the semaphore operation structure */ Intnops;/* Number of semaphore operation structures */ /* Return the value of the last calculated semaphore in the set of semaphores before the computation is complete. Value. If an error occurs,-1 */is returned */ Int semctl (SID, semnum, CMD, ARG) /* Control semaphore operations */ Intsid;/* semaphore collection identifier */ Intsemnum;/* semaphore element number */ Intcmd;/* Control Command */ Union semun { Intval; Struct semid_ds * Buf; Ushort * array;} ARG;/* command parameter */ The system calls semget to translate the key value of the semaphores set into the identifier representing the semaphores set. This set has count elements, and its access permission definition is the same as that of the file, which is defined by flags. If the flags ipc_creat bit is set, the system creates it when the set does not exist. Therefore, each process can use the flags parameter with the ipc_creat bit to obtain the identifier of the semaphore set, which does not need to be created by a process in advance. If flags is ipc_pridvate, the system establishes the flags regardless of whether the semaphore set with the same key value exists and returns the next available identifier. The system calls semctl to perform various control operations on a set of semaphores, such as the initialization, deletion, and status query of semaphores. Common Operations and related command formats are as follows: ① Cancel the semaphores set Int semctl (SID, Count, ipc_rmid, 0) Int Sid;/* semaphore collection identifier */ Int count;/* Number of elements in the semaphore Set */ ② Set the initial value of the semaphore set (initialization) When the semaphore set is set up, the initial values of each semaphore are uncertain and the initial values need to be set. The setall or setval command can be used to set the initial values. If you use the setall command, the format is: Int semctl (SID, Count, setall, ARG) Int Sid;/* semaphore collection identifier */ Int count;/* Number of elements in the semaphore Set */ Ushort * ARG;/* command parameter */ This command assigns the first count values in the array Arg to the semaphores in the Set in sequence. The initial values of multiple semaphores can be set at a time. If you use the setval command, the format is: Int semctl (SID, semnum, setval, ARG) Int Sid;/* semaphore collection identifier */ Int semnum;/* semaphore element number */ Int ARG;/* command parameter */ This command assigns the ARG value to the semnum semaphore in the set. Only the initial value of one semaphore can be set at a time. ③ Query the current value of the semaphore set You can use the getall or getval command to query the current value of the semaphore set. If you use the getall command, the format is: Int semctl (SID, Count, getall, ARG) Int Sid;/* semaphore collection identifier */ Int count;/* Number of elements in the semaphore Set */ Ushort * ARG;/* command parameter */ This command returns the current values of all semaphores in the semaphores set to the ARG array. If you use the getval command, the format is: Int semctl (SID, semnum, getval, 0) Int Sid;/* semaphore collection identifier */ Int semnum;/* semaphore element number */ This command uses the current value of the semnum semaphore in the set as the return value of the call. ④ Query the number of waiting processes of a semaphore When a process is required to perform a semaphore operation, if the conditions are not met, the number of waiting processes for the semaphore changes accordingly. The getncnt command can be used to query the number of processes waiting for the semaphore value-added. The format is as follows: Int semctl (SID, semnum, getncnt, 0) Int Sid;/* semaphore collection identifier */ Int semnum;/* semaphore element number */ This command takes the number of processes waiting for the value-added semnum semaphores as the return value of the call. The getzcnt command can be used to query the number of processes whose waiting signal value is 0. The format is as follows: Int semctl (SID, semnum, getzcnt, 0) Int Sid;/* semaphore collection identifier */ Int semnum;/* semaphore element number */ This command takes the number of processes that wait for the semnum signal to be 0 as the return value of the call. Other control commands are not often used. The system calls semop to operate one or more semaphores in the semaphores set. The operation commands are defined by the user-provided operation structure array. The structure is as follows: Struct sembuf { Short sem_num;/* subscript of the semaphore in the Set */ Short sem_op;/* operation value */ Short sem_flg;/* operation flag */ }; The System reads the semaphore operation structure array from the user address space and verifies the validity of the semaphore and whether the process has the necessary permissions to read or modify the semaphore. If the permission is insufficient, the call fails. If the process must be sleep, it restores the operated semaphore to the value at the beginning of the system call, and then it goes to sleep, wait until the system call is re-executed when the wait event occurs. Because the system saves the Operation Array in a Global Array, if it must re-execute the call, it must re-read the array from the user space. In this way, the operation is executed in primitive mode-or once done or not done at all. The system changes the semaphore value based on the Operation Value: ① if the operation value is positive, the system increases the semaphore value and wakes up all processes waiting for the semaphore value-added; ② If the operation value is 0, the system checks the semaphore value. If it is 0, it continues other operations in the array; otherwise, it adds 1 to the number of sleeping processes whose waiting semaphore value is 0, and then goes to sleep; ③ if the operation value is negative and its absolute value does not exceed the semaphore value, the system adds the operation value (a negative number) to the signal value, if the result is 0, the system will wake up all sleep processes whose waiting semaphore value is 0; ④ if the semaphore value is smaller than the absolute value of the operation value, the system allows the process to sleep on the "Wait For the semaphore value-added" event. When a process is sleep during the semaphore operation, it is sleep at an interruption level, so it is awakened when it receives a soft interrupt signal. You can set the ipc_nowait flag in the Operation flag to prevent the process from sleeping. If a process executes a semaphore operation, locks some resources, but does not restore the semaphore value, it exits (such as receiving the kill signal), then there may be a dangerous situation. To avoid such problems, you can set the sem_undo flag in the Operation flag. When a process exits, the system removes the impact of each semaphore operation performed by the process. It is worth noting that when you use two or more semaphores, deadlocks are always possible, and the system cannot check deadlocks between multiple semaphores. The algorithms and call formats used in this article have been run on scounix3.2, scoopensever3.x, and 5.x. |