Use of Linux POSIX Semaphore

Purpose

Semaphores are not used to transfer data between processes. Instead, they allow processes to synchronize their actions. One common use of a semaphore is to synchronize access to a block of shared memory in order to prevent one process from accessing the shared memory at the same time as another process is updating it.

---- Chapter 47: System V Semaphore <<The Linux Programming Interface>>

In short, the use of semaphores is for process synchronization purposes, preventing a process from being updated by another process when accessing a shared memory.

For POSIX semaphore, semaphore can allow processes and threads to synchronize and access shared resources.

principle

The semaphore is an integer maintained by the kernel, and its value is strictly limited to be greater than or equal to 0 . Semaphores are non-negative integers. So we can think about what needs to be done to maintain a number? Assignment, increase, decrease, judgment, then the corresponding semaphore operation

  • set it to a positive integer value
  • increase this number
  • reduce this number
  • wait for this number to become 0

In some books, the operation of the semaphore is called the PV operation, why is it P, because this is invented by the Dutch computer scientist Dijkstra, who is the creator of the shortest path traversal algorithm. In Dutch, P means Prolaag means decrease, and V means Verhoog means increase. P means Wait, V means Signal.

type

SUSv3 defines two POSIX semaphores:

named semaphores, named semaphores

This semaphore has a name, and the sem_open() function is called by its name, and unrelated processes can also access the same semaphore.

Unnamed semaphores, unnamed semaphores

This semaphore does not have a name, they exist in an agreed memory space . Unnamed semaphores can be shared between processes, or a group of threads. When shared by processes, the semaphore exists in an area of ​​shared memory. But when shared by a process, the semaphore exists in the memory area shared by the process.


About Named Semaphores

operate

  • The sem_open() function opens or creates a semaphore, initializes the semaphore
    if it is created by the call, and returns a handle for use in later calls.
  • The sem_post(sem) and sem_wait(sem) functions respectively increment and decrement
    a semaphore’s value.
  • The sem_getvalue() function retrieves a semaphore’s current value.
  • The sem_close() function removes the calling process’s association with a semaphore
    that it previously opened.
  • The sem_unlink() function removes a semaphore name and marks the semaphore
    for deletion when all processes have closed it.

sem_open

sem_t *sem_open(const char *name, int oflag, mode_t mode, ..., unsigned int value */ ); //Returns pointer to semaphore on success, or SEM_FAILED on error

Open (create) a new semaphore

The second parameter is the open flag, which is a bit mask parameter.

  • If oflag is 0, we will access an existing semaphore.
  • If oflag is O_CREAT, the system will judge whether the semaphore with this name exists according to the first parameter (name), and create a new semaphore if it does not exist. That is, create a new one if it does not exist.
  • If oflag is O_CREAT | O_EXCL, both conditions must be met for creation. Then if the semaphore of the first parameter name exists, sem_open will return -1 and fail because O_EXCL means exclusive. So if you call sem_open("xxx", O_CREAT | O_EXCL, ...) == -1, it means that the semaphore already exists

Open an existing semaphore

sem_open opens an existing semaphore, so only two parameters (name, oflag) are required . If O_CREAT exists in oflag, then two more parameters are required, namely mode and value. Is this obvious, you want to create a new semaphore, then you have to provide the information needed to create it. (If the semaphore exists, these two parameters will be ignored)

mode parameter

mode is also a bit mask parameter, this bit value is similar to that of opening a file. Generally speaking, there are three Open Modes, namely O_RDONLY, O_WRONLY, O_RDWR, and the prefix O means Open.
When opening a semaphore, it is necessary to deploy sem_post and sem_wait, so Read and Write permissions are required.

perms parameter

The parameters of Linux permissions are divided by permission groups in Linux. Generally speaking, Linxu divides all users into: owner USER, group GROUP, others OTHERS, and super user ROOT, but ROOT generally does not participate in the discussion here.
It is more straightforward to express directly with a graph here.

 

For the definition in the file stat.h of Liux, you can first refer to this Permission-Bits

Commonly used, here the S prefix means Set

  • S_IRUSR user read permission,
  • S_IWUSR User write permission
  • No user read and write permissions
  • S_IRWXU user read, write, execute permission

  • S_IRGRP group read permission
  • S_IWGRP group write permission
  • S_IRWXG group read, write, execute permission

  • S_IROTH Read permission for others
  • S_IWOTH Others write permissions
  • S_IRWXO others read, write, execute permissions

According to the mapping relationship in the above figure, S_IRUSR means binary, 0b100000000, the decimal of this number is 2^8 = 256, try it on linux as expected

printf("%d", S_IRUSR);, outputs 256.


Of course, each permission can be disassembled. For example, the user's read, write, and execute can be expressed as S_IRUSR S_IWUSR S_IXUSR, and the same is true for others. In fact, it is defined like this in the stat.h file.

//宏定义的基础定义,
#define __S_IREAD   0400    /* Read by owner.  */
#define __S_IWRITE  0200    /* Write by owner.  */
#define __S_IEXEC   0100    /* Execute by owner.  */
//用户
#define S_IRUSR __S_IREAD   /* Read by owner. 这个宏就是 0x 04  */
#define S_IWUSR __S_IWRITE  /* Write by owner.  */
#define S_IXUSR __S_IEXEC   /* Execute by owner.  */
#define S_IRWXU (__S_IREAD|__S_IWRITE|__S_IEXEC) /* Read, write, and execute by owner.  */
//群组
#define S_IRGRP (S_IRUSR >> 3)  /* Read by group.  */
#define S_IWGRP (S_IWUSR >> 3)  /* Write by group.  */
#define S_IXGRP (S_IXUSR >> 3)  /* Execute by group.  */
#define S_IRWXG (S_IRWXU >> 3)  /* Read, write, and execute by group.  */
//其他人
#define S_IROTH (S_IRGRP >> 3)  /* Read by others.  */
#define S_IWOTH (S_IWGRP >> 3)  /* Write by others.  */
#define S_IXOTH (S_IXGRP >> 3)  /* Execute by others.  */
#define S_IRWXO (S_IRWXG >> 3) /* Read, write, and execute by others.  */

It can be seen that the most basic number is 0400. This number is represented in octal because it starts with 0 in C language. It means 4 * (8^2) = 256; therefore, you can also use numbers directly when assigning values. 0400 assignment.

So S_IRUSR means binary 0b100000000, octal 0400, and hexadecimal 0x100. For S_IRGRP, it means that S_IRUSR is shifted to the right by 3 bits, that is, 0b100000000 becomes 0b100000[000] and the last three bits are removed to become 0b000100000. This number is 32. If it becomes S_IROTH, it will move 3 bits to the right and become 0b100, which is 4. So right shifting is also an operation that divides by 2, and moving once means dividing by 2. Others can be similarly pushed to

value

It is the initial value that needs to be passed to the new semaphore, and this number is a non-negative integer.

pay attention

If sem_open fails, it returns a value SEM_FAILEDcalled , which is not simply -1. Let's take a look at the source code in the Linux environment

#if __WORDSIZE == 64
# define __SIZEOF_SEM_T 32
#else
# define __SIZEOF_SEM_T 16
#endif

/* Value returned if `sem_open' failed.  */
#define SEM_FAILED      ((sem_t *) 0)

typedef union
{
  char __size[__SIZEOF_SEM_T];
  long int __align;
} sem_t;

sem_t is a union, and SEM_FAILED is a union pointer.

SEM_FAILED may be defined as *((sem_t *) 0) or ((sem_t ) –1) , Linux is defined as (sem_t *) 0)

sem_close

//Returns 0 on success, or –1 on error
int sem_close(sem_t *sem); 

This is to close the semaphore and not delete it. If you want to delete it, you need to call the sem_unlink() function

sem_wait

If the current value of the semaphore is greater than 0, sem_wait will return immediately.

If the value of the semaphore is 0, sem_wait will block. Until this value increases to more than 0, then sem_wait will return immediately, that is to say, this function is to detect the value of the semaphore. If the semaphore is a positive integer, it means that the resource can be accessed, so pass it directly. If it is 0, it blocks, which means No resource could be accessed.

sem_trywait

The sem_trywait function is a non-blocking version of sem_wait.

sem_post

Increment the value of the semaphore by 1

sem_getvalue

int sem_getvalue(sem_t *sem, int *sval);

Get the current semaphore


Unnamed Semaphores unnamed semaphores

Non-named semaphores use the same function, sem_wait(), sem_post(), sem_getvalue()
two different functions sem_init and sem_destroy()

sem_init

//Returns 0 on success, or –1 on error, 这个和sem_open不一样,sem_open函数 如果失败返回的是SEM_FAILED,不是-1
int sem_init(sem_t *sem, int pshared, unsigned int value);
- If pshared is 0, then the semaphore is to be shared between the threads of the
calling process. In this case, sem is typically specified as the address of either a
global variable or a variable allocated on the heap. A thread-shared semaphore
has process persistence; it is destroyed when the process terminates.
- If pshared is nonzero, then the semaphore is to be shared between processes. In
this case, sem must be the address of a location in a region of shared memory (a
POSIX shared memory object, a shared mapping created using mmap(), or a
System V shared memory segment). The semaphore persists as long as the
shared memory in which it resides. (The shared memory regions created by
most of these techniques have kernel persistence. The exception is shared
anonymous mappings, which persist only as long as at least one process maintains
the mapping.) Since a child produced via fork() inherits its parent’s memory
mappings, process-shared semaphores are inherited by the child of a fork(), and
the parent and child can use these semaphores to synchronize their actions.

It can be seen here that if the parameter pshared is 0, it is shared by threads. The sem parameter points to a global variable or a variable allocated on the heap (heap). Here, it is explained that this variable is most likely allocated through malloc as a non-negative number, because there is no such operator as c++ new in linux c. Find an example.

#include <stdlib.h>
int main()
{
  int 1 = 1;
  int *pi = (int*)malloc(sizeof(int)); //this variable pi is allocated at heap region
  *pi = 2;
  //...
  free(pi);
}

And this semaphore will exist during the process, and will only be released when the process ends.

pshared is a non-negative integer, the semaphore is shared by the process. sem points to shared memory (POSIX shared memory object, a shared mapping created using mmap(), or a
System V shared memory segment).

Reposted from: https://www.jianshu.com/p/9c320bb9bdd5

Guess you like

Origin blog.csdn.net/fuhanghang/article/details/130150044