Original address: blogof33.com/post/9/

preface

Interprocess communication is an interesting topic in POSIX systems.

POSIX semaphore processes are one of the three inter-process Communication (IPC) mechanisms, which are derived from the real-time extension of POSIX.1. The Single UNIX Specification places three mechanisms (message queues, semaphores, and shared storage) in the optional section. Prior to SUSv4, POSIX semaphore interfaces were included in semaphore options. In SUSv4, these interfaces have been moved to the base specification, while message queues and shared storage interfaces remain optional.

The POSIX semaphore interface is intended to address several weaknesses of the XSI semaphore interface.

  • POSIX semaphore interfaces allow for higher performance implementations than XSI interfaces.

  • POSIX semaphores are simpler to use: there are no semaphore sets, and some interfaces are modeled after familiar file system operations. Although it is not required to be implemented in the file system, some systems do.

  • POSIX semaphores perform better when deleted. Recall that when an XSI semaphore is deleted, the operation using the semaphore identifier fails and sets errno to EIDRM. With POSIX semaphores, operations can continue to work normally until the last reference to the semaphore is released.

    From UNIX Advanced Environment Programming (Chinese edition 3), pp. 465-466

Some time ago, when I was writing about pipeline communication, I explored the two types of communication between POSIX processes: named and unknown semaphores. There are many people who think that interprocess communication can only use named semaphores, and nameless semaphores can only be used for multithreaded communication between single processes. In fact, unknown semaphores can also carry out interprocess communication.

The difference between

Named and unknown semaphores differ in the form of creation and destruction, but otherwise work the same.

Nameless semaphore can only exist in memory. The process that uses the semaphore must have access to the memory in which the semaphore resides. Therefore, nameless semaphore can only be applied between threads in the same process (shared process memory). Or threads in different processes that have mapped the same memory contents to their address space (that is, the memory in which the semaphore resides is shared by the communicating process). This means that nameless semaphores can only be accessed through shared memory.

In contrast, named semaphores are accessible by name and can therefore be used by any thread in the process that knows their name.

When POSIX semaphores are used in a single process, nameless semaphores are simpler. Named semaphores are simpler when POSIX semaphores are used across multiple processes.

contact

Either a named semaphore or an unnamed semaphore can be manipulated by the following functions.

wait

Weit refers to the operation of subtracting signal quantity value by one. There are three functions in total, and the function prototype is as follows:

#include <semaphore.h>int sem_wait(sem_t *sem); int sem_trywait(sem_t *sem); int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); GCC Link with -pthread. GCC Link with -pthread. Return value: 0 if successful; If there is an error, return -1Copy the code

Where, the function of the first is that if SEM is less than 0, the thread blocks on semaphore SEM until SEM is greater than 0. Otherwise, the signal quantity value is reduced by 1.

The second function does the same except that it does not block the thread and returns an error if sem is less than 0 (error set to EAGAIN).

The third function also has the same effect as the first one. The second parameter indicates the blocking time. If sem is less than 0, the blocking time will be specified. Abs_timeout points to a structure consisting of seconds and nanoseconds starting from 1970-01-01 00:00:00 +0000 (UTC). The structure is defined as follows:

struct timespec {
               time_t tv_sec;      /* Seconds */
               long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
           };
Copy the code

If the specified blocking time is up and sem is still less than 0, an error is returned (error set to ETIMEDOUT).

post

Post is a signal magnitude plus one operation, and the function prototype is as follows:

#include <semaphore.h>int sem_post(sem_t *sem); Link with-pthread. Return value: 0 if successful; If there is an error, return -1Copy the code

Examples of application

Named semaphore

create

The sem_open function can be called to create a named semaphore.

#include <semaphore.h>sem_t *sem_open(const char *name, int oflag); sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); Link with-pthread. Return value: If successful, a pointer to the semaphore is returned. If error, return SEM_FALLEDCopy the code

The first of these functions is called when an existing named semaphore is used, with flag set to 0.

If the second function is called, the flag argument should be set to O_CREAT, and if the named semaphore does not exist, a new one will be created, if it does, it will be used and will not be initialized again.

When we use the O_CREAT flag, we need to provide two additional parameters:

The mode parameter specifies who can access the semaphore, that is, the permission group. The value of mode is the same as the permission bit to open the file, such as 0666, which indicates that all users can read and write. Because only read and write access matters, implementations often turn on semaphores for read and write.

Value Specifies the initial semaphore value. The value ranges from 0 to SEM_VALUE_MAX.

If the semaphore exists, calling the second function ignores the latter two arguments (that is, mode and value).

The release of

When the semaphore operation is complete, the sem_close function can be called to release any semaphore resources. The function is described as follows:

#include <semaphore.h>int sem_close(sem_t *sem); Link with-pthread. Return value: 0 if successful; If there is an error, return -1Copy the code

If the process exits without calling this function, the kernel automatically closes any open semaphore. Neither the call to this function nor the automatic shutdown of the kernel will change the semaphore value before the release.

The destruction

You can destroy a named semaphore using the sem_unlink function. The function is described as follows:

#include <semaphore.h>int sem_unlink(const char *name); Link with-pthread. Return value: 0 if successful; If there is an error, return -1Copy the code

The sem_unlink function removes the semaphore name. If there are no open semaphore references, the semaphore is destroyed; otherwise, destruction is delayed until the last open reference is closed.

example

For example, in pipe communication, if the parent process uses fork() to create two child processes 1 and 2, the child process 1 and 2 write a paragraph of text to the pipe in order, and the parent process reads the child process from the pipe. To ensure the order of execution of the process, we can use named semaphores.

#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<semaphore.h>
#include<sys/sem.h>
#include <sys/stat.h>

#include<fcntl.h>

int main(a){
    int pid1,pid2;
    sem_t *resource1; 
    sem_t *resource2; 
    int Cpid1,Cpid2=- 1;
    int fd[2];//0 is the read segment, and 1 is the write end
    char outpipe1[100],inpipe[200],outpipe2[100];
    pipe(fd);// Create an unnamed pipe

    pid1 = fork();
    if(pid1<0) {printf("error in the first fork!");
    }else if(pid1==0) {// Child process 1
        resource1=sem_open("name_sem1",O_CREAT,0666.0);
        Cpid1 = getpid();
        close(fd[0]);// Turn off the readout
        lockf(fd[1].1.0);// Lock the area from the current offset to the end of the file
        sprintf(outpipe1,"Child process 1 is sending a message!");
        write(fd[1],outpipe1,strlen(outpipe2));
        lockf(fd[1].0.0);/ / unlock
        sem_post(resource1);
        sem_close(resource1);
        exit(0);
   }else{
        
        pid2 = fork();
        if(pid2<0) {printf("error in the second fork! \n");
        }else if(pid2==0){  
                resource1=sem_open("name_sem1",O_CREAT,0666.0);
                resource2=sem_open("name_sem2",O_CREAT,0666.0);
                Cpid2 = getpid();
                sem_wait(resource1);
				close(fd[0]);
                lockf(fd[1].1.0);
                sprintf(outpipe2,"Child process 2 is sending a message!");

                write(fd[1],outpipe2,strlen(outpipe2));
                lockf(fd[1].0.0);/ / unlock
                sem_post(resource2);
                sem_close(resource1);
                sem_close(resource2);
                exit(0);
        }
        if(pid1 > 0 && pid2 >0){
                resource2=sem_open("name_sem2",O_CREAT,0666.0);
                sem_wait(resource2);
                waitpid(pid1,NULL.0);
                waitpid(pid2,NULL.0);
                close(fd[1]);// Turn off the write end
                read(fd[0],inpipe,200);
                printf("%s\n",inpipe);
                sem_close(resource2);
                
                exit(0);
        }
        sem_unlink("name_sem1");
        sem_unlink("name_sem2");
    }
    return 0;
}

Copy the code

Unknown semaphore

create

Nameless semaphores can be created using the sem_init function, which is described as follows:

#include <semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value); Link with-pthread. Return value: 0 if successful; If there is an error, return -1Copy the code

The pshared parameter indicates whether the semaphore is shared by multiple threads of a process or by multiple processes.

If pshared has a value of 0, then the semaphore will be shared by multiple threads in a single process and should be located at an address visible to all threads (for example, global variables or variables dynamically allocated on the heap).

If pshared is non-zero, then the semaphore is shared between processes and should be in the shared memory region.

The destruction

If the nameless semaphore is finished, the sem_destory function can be called to destroy it. The function is described as follows:

#include <semaphore.h>int sem_destroy(sem_t *sem); Link with-pthread. Return value: 0 if successful; If there is an error, return -1Copy the code

Note:

  • Destroying the currently blocked semaphore of another process or thread results in undefined behavior.
  • Using a destroyed semaphore produces undefined results unless usedsem_initReinitialize the semaphore.
  • An unnamed semaphore should be used before the memory in which it resides is freedsem_destroyDestroyed. Failure to do so may result in resource leaks in some implementations.

example

An example of implementing a named semaphore using an unknown semaphore:

#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<semaphore.h>
#include<sys/sem.h>
#include <sys/stat.h>
#include <sys/shm.h>
#include<fcntl.h>

int main(a){
    int pid1,pid2;
    int Cpid1,Cpid2=- 1;
    int fd[2];//0 is the read segment, and 1 is the write end
    char outpipe1[100],inpipe[200],outpipe2[100];
    void *shm = NULL;
    sem_t *shared;
    int shmid = shmget((key_t) (1234), sizeof(sem_t *), 0666 | IPC_CREAT);// Create a shared memory and return an identifier
    if(shmid == - 1){
        perror("shmat :");
        exit(0);
    }
    shm = shmat(shmid, 0.0);// Returns a pointer to the first byte of shared memory
    shared = (sem_t *)shm;
    sem_init(shared, 1.0);// Initialize the shared memory signal quantity to 0
    pipe(fd);// Create an unnamed pipe

    pid1 = fork();
    if(pid1<0) {printf("error in the first fork!");
    }else if(pid1==0) {// Child process 1

        Cpid1 = getpid();
        close(fd[0]);// Turn off the readout
        lockf(fd[1].1.0);// Lock the area from the current offset to the end of the file
        sprintf(outpipe1,"Child process 1 is sending a message!");
        write(fd[1],outpipe1,strlen(outpipe1));
        lockf(fd[1].0.0);/ / unlock
        sem_post(shared);

        exit(0);
   }else{

        pid2 = fork();
        if(pid2<0) {printf("error in the second fork! \n");
        }else if(pid2==0){
                sem_wait(shared);
                Cpid2 = getpid();
				close(fd[0]);
                lockf(fd[1].1.0);
                sprintf(outpipe2,"Child process 2 is sending a message!");

                write(fd[1],outpipe2,strlen(outpipe2));
                lockf(fd[1].0.0);/ / unlock

                exit(0);
        }
        if(pid1 > 0 && pid2 >0){

                waitpid(pid2,NULL.0);// synchronize to ensure that the child writes to the parent before reading
                close(fd[1]);// Turn off the write end
                read(fd[0],inpipe,200);
                printf("%s\n",inpipe);

                exit(0); }}return 0;
}
Copy the code