Interprocess communication (anonymous pipes, named pipes, shared memory, semaphores)

Table of contents

anonymous pipe

Create a pipeline ---pipe()

 named pipe

Create FIFOs 

FIFO operation

Implement server&client communication with named pipes

Shared memory

Create shared memory function shmget()

Obtain the shared memory address function shmat()

Delete the shared memory function shmdt()

Shared memory control function shmctl()

amount of signal 

Semaphore Data Structure

New semaphore function semget()

The semaphore operation function semop() 

Control semaphore parameter semctl() function


The communication mechanism between multiple processes under Linux is called IPC, which is a method for multiple processes to communicate with each other. There are a variety of inter-process communication methods under Linux: half-duplex pipes, FIFO (named pipes), message queues, semaphores, shared memory, etc. Using these communication mechanisms can provide a flexible and solid framework for the development of network servers under Linux.


anonymous pipe

A pipe is a mechanism for connecting standard input and standard output between two processes. Pipelines are a long-established method of interprocess communication. Pipelines have existed since the birth of the UNIX operating system.
1. Basic concepts
Since a pipeline is only a one-way communication method that connects the output of a process with the input of another process, it is called "half-duplex". Pipelines are represented by " | " in the shell.

ls  -l | grep *.c

Take the output of ls -l as the input of "grep *.c", the pipeline establishes an input channel in the previous process, and an output channel in the latter process, and transfers the data from the left side of the pipeline to the right side of the pipeline. The output is piped to "grep *.c".
The process creates a pipe, and creates two file descriptors at a time to operate the pipe. One of them writes to the pipe, and the other descriptor reads from the pipe. A pipe connects two processes through the kernel, and two file descriptors are connected together. If a process sends data through the pipe fda[0], it can get information from fdb[0].

Since both process A and process B can access the two descriptors of the pipeline, after the pipeline is created, the direction in each process must be set, and the data is expected to be transmitted in that direction. This requires good planning. Both processes must be set uniformly. The pipe descriptor set to read in process A and write in process B; Lose. The reading and writing of the pipeline is consistent with the general I0 system functions. Use the write() function to write data, and the read() function to read data. Some specific IO operation pipelines are not supported, such as the offset function lseek(). 

Create a pipeline ---pipe()

#include <unistd.h>

        int pipe(int fd[2]);

fd is an array of file descriptors, used to save the two file descriptors returned by the pipeline. The first element in the array (subscript 0) is opened for read operations, while the second element (subscript 1) is created and opened for write operations. (0 is regarded as a mouth for reading, 1 is regarded as a pen for writing). When the function executes successfully, it returns 0, and if it fails, it returns an error code.

It seems useless to only establish a pipeline. To make the pipeline useful, it needs to be combined with the creation of the process, and use two pipelines to communicate between the parent process and the child process. A pipeline is established between the parent process and the child process, the child process writes data to the pipeline, and the parent process reads data from the pipeline. To implement such a model, the write end needs to be closed in the parent process, and the read end needs to be closed in the child process.

#include <iostream>
#include <unistd.h>
#include <string>
#include <string.h>
#include <cerrno>
#include <cassert>
#include <sys/types.h>
using namespace std;

int main()
{
    // 任何一种任何一种进程间通信中,一定要先保证不同的进程之间看到同一份资源
    int pipefd[2] = {0};
    //1. 创建管道
    int n = pipe(pipefd);
    if(n < 0)
    {
        std::cout << "pipe error, " << errno << ": " << strerror(errno) << std::endl;
        return 1;
    }
    //2.创建子进程
    pid_t id=fork();
    assert(id != -1);
    if(id==0)
    {
        //子进程
        //3.关闭不需要的fd,让父进程进行读取,子进程进行写入
        close(pipefd[0]);

        //4.开始通信
        string strmessage="hello,我是子进程";
        char buffer[1024];
        int count=1;
        while(true)
        {
            snprintf(buffer,sizeof buffer,"%s,计数器:%d,我的PId:%d",strmessage.c_str(),count++,getpid());
            write(pipefd[1],buffer,strlen(buffer));
            sleep(1);
        }
        close(pipefd[1]);
        exit(0);
    }

    //父进程
    //3.关闭不需要的fd,让父进程进行读取,子进程进行写入
    close(pipefd[1]);

    //4.开始通信
    char buffer[1024];
    while(true)
    {
        int n=read(pipefd[0],buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]='\0';
            cout<<"我是父进程,child give me message:"<<buffer<<endl;
            
        }
    }

    close(pipefd[0]);
    return 0;
}

features

  1. one-way communication
  2. The essence of a pipeline is a file, because the life cycle of fd follows the process, and the life cycle of the pipeline follows the process.
  3. It can only be used to communicate between processes with a common ancestor (processes with kinship); usually, a pipe is created by a process, and then the process calls fork, and the pipe can be used between the parent and child processes. (pipe opens the pipe, and the name of the pipe is not clear---anonymous pipe).
  4. In pipeline communication, the number of writes and the number of reads are not strongly related to the number of reads and writes that do not strictly match.
  5. It has a certain coordination ability, so that the reader and writer can communicate according to certain steps --- comes with a synchronization mechanism.
#include <iostream>
#include <string>
#include <cerrno>
#include <cassert>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
    int pipefd[2] = {0};
    //1. 创建管道
    int n = pipe(pipefd);
    if(n < 0)
    {
        std::cout << "pipe error, " << errno << ": " << strerror(errno) << std::endl;
        return 1;
    }
    std::cout << "pipefd[0]: " << pipefd[0] << std::endl; // 读端, 0->嘴巴->读书
    std::cout << "pipefd[1]: " << pipefd[1] << std::endl; // 写端, 1->笔->写东西的

    //2. 创建子进程
    pid_t id = fork();
    assert(id != -1); 

    if(id == 0)// 子进程
    {
        //3. 关闭不需要的fd,让父进程进行读取,让子进程进行写入
        close(pipefd[0]);

        //4. 开始通信 -- 结合某种场景
        // const std::string namestr = "hello, 我是子进程";
        // int cnt = 1;
        // char buffer[1024];
        int cnt = 0;
        while(true)
        {
            char x = 'X';
            write(pipefd[1], &x, 1);
            std::cout << "Cnt: " << cnt++<<std::endl;
            sleep(1);
            // break;

            // snprintf(buffer, sizeof buffer, "%s, 计数器: %d, 我的PID: %d", namestr.c_str(), cnt++, getpid());
            // write(pipefd[1], buffer, strlen(buffer));
        }

        close(pipefd[1]);
        exit(0);
    }

    //父进程
    //3. 关闭不需要的fd,让父进程进行读取,让子进程进行写入
    close(pipefd[1]);

    //4. 开始通信 -- 结合某种场景
    char buffer[1024];
    int cnt = 0;
    while(true)
    {
        // sleep(10);
        // sleep(1);
        int n = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if(n > 0)
        {
            buffer[n] = '\0';
            std::cout << "我是父进程, child give me message: " << buffer << std::endl;
        }
        else if(n == 0)
        {
            std::cout << "我是父进程, 读到了文件结尾" << std::endl;
            break;
        }
        else 
        {
            std::cout << "我是父进程, 读异常了" << std::endl;
            break;
        }
        sleep(1);
        if(cnt++ > 5) break;
    }
    close(pipefd[0]);

    int status = 0;
    waitpid(id, &status, 0);
    std::cout << "sig: " << (status & 0x7F) << std::endl;

    sleep(100);

    return 0;
}

 

  • If we have finished reading all the pipeline data, if the other party does not send it, I can only wait. The read call is blocked, that is, the process suspends execution and waits until data arrives.
  • If our writer side fills up the pipeline, we cannot write. The write call blocks until a process reads the data.
  • If I close the write end, read the pipeline data, and read again, read will return 0, indicating that the end of the file has been read.
  • It doesn't make sense for the write end to keep writing and the read end to be closed. The OS doesn't maintain things that are pointless, inefficient, or wasteful of resources. The OS kills the process that keeps writing. The OS will terminate the process through a signal (SIGPIPE--signal 13).

 named pipe

Named pipes work much like regular pipes, but there are some notable differences.

  • Named pipes exist in the file system as device special files.
  • Different processes can share data through named pipes.

Create FIFOs 

There are many ways to create named pipes. Among them, it can be done directly with the shell. For example, to create a named pipe called namedfifo in the current directory:

mkfifo namedfifo

 It can be seen that there is a p in the attribute of namedfifo, indicating that this is a pipeline.

Named pipes can also be created from the program, the related functions are:

#include <sys/types.h>
#include <sys/stat.h>

        int mkfifo(const char *filename,mode_t mode);

Create a named pipe: 

int main(int argc, char *argv[]) {

        mkfifo("p2", 0644);

        return 0;

FIFO operation

For named pipe FIFOs, IO operations are basically the same as ordinary pipe IO operations, with one major difference between the two. In a FIFO, an open() function must be used to explicitly establish a channel connected to the pipe. In general, FIFOs are always blocked. That is to say, if the read permission is set when the named pipe FIFO is opened, the reading process will be "blocked" until other processes open the FIFO and write data to the pipe. This blocking action is also true in reverse. If a process opens a pipe to write data, when no process rushes to read data in the pipe, the operation of writing the pipe is also blocked until the written data is read. to perform write operations. If you do not want to block when performing named pipe operations, you can use the O_NONBLOCK flag in the open() call to turn off the default blocking action.

Implement server&client communication with named pipes

comm.hpp (named pipes for client.cc and server.cc)

#pragma once
#include <iostream>
#include <string>

#define NUM 1024

const std::string fifoname="./fifo";
uint32_t mode = 0666;

server.cc

#include <iostream>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.hpp"

int main()
{
    // 1. 创建管道文件,只需要一次创建
    umask(0); //这个设置并不影响系统的默认配置,只会影响当前进程
    int n = mkfifo(fifoname.c_str(), mode);
    if(n != 0)
    {
        std::cout << errno << " : " << strerror(errno) << std::endl;
        return 1;
    }
    std::cout << "create fifo file success" << std::endl;
    // 2. 让服务端直接开启管道文件
    int rfd = open(fifoname.c_str(), O_RDONLY);//只读方式打开
    if(rfd < 0 )
    {
        std::cout << errno << " : " << strerror(errno) << std::endl;
        return 2;
    }
    std::cout << "open fifo success, begin ipc" << std::endl;

    // 3. 正常通信
    char buffer[NUM];
    while(true)
    {
        buffer[0] = 0;
        ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << "client# " << buffer << std::endl;
            //printf("%c", buffer[0]);
            //fflush(stdout);
        }
        else if(n == 0)
        {
            std::cout << "client quit, me too" << std::endl;
            break;
        }
        else 
        {
            std::cout << errno << " : " << strerror(errno) << std::endl;
            break;
        }
    }

    // 关闭不要的fd
    close(rfd);

    unlink(fifoname.c_str());

    return 0;
}

client.cc 

#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
// #include <ncurses.h>
#include "comm.hpp"

int main()
{
    //1. 不需创建管道文件,我只需要打开对应的文件即可!
    int wfd = open(fifoname.c_str(), O_WRONLY);//只写方式打开
    if(wfd < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        return 1;
    }

    // 可以进行常规通信了
    char buffer[NUM];
    while(true)
    {
        std::cout<<"请输入你的消息#:";
        char *msg = fgets(buffer,sizeof(buffer),stdin);
        assert(msg);
        (void)msg;

        buffer[strlen(buffer)-1]=0;
        if(strcasecmp(buffer,"quit") == 0) break;
        ssize_t n = write(wfd,buffer,strlen(buffer));
        assert(n > 0);
        (void)n;
    }
    
    close(wfd);

    return 0;
}

Shared memory

Shared memory is an inter-process communication method that shares a memory area among multiple processes. It realizes memory sharing by mapping memory segments among multiple processes. This is the fastest way of IPC, because there is no intermediate process in the communication of the shared memory method, and the pipeline, message queue and other methods need to convert the data through an intermediate mechanism; on the contrary, the shared memory method directly converts a certain memory segment Mapping, the shared memory between multiple processes is the same physical space, only the address is different, so there is no need to copy, and this space can be used directly.

Create shared memory function shmget()

The function shmget() is used to create a new shared memory segment, or access an existing shared memory segment. The prototype of the function shmget() is as follows:

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_ t size, int shmflg);

key: the name of the shared memory segment

size: shared memory size

shmflg: Consists of nine permission flags, their usage is the same as the mode mode flag used when creating a file

Return value: successfully returns a non-negative integer, which is the identification code of the shared memory segment; fails and returns -1

The first argument to shmget() is the value of the keyword. This value is then compared to key values ​​of other shared memory segments existing in the kernel. After the comparison, both open and access operations will depend on the contents of the shmflg parameter.

  • IPC_CREAT: If the memory segment does not exist in the kernel, create it.
  • IPC_EXCL: When used with IPC CREAT, if the memory segment already exists, the call will fail.

If only IPC_CREAT is used, shmget() will either return the segment identifier of a newly created memory segment, or the identifier of a memory segment already existing in the kernel with the same key value. If IPC_CREAT and IPC_EXCL are used at the same time, there may be two results: if the memory segment does not exist, a new memory segment will be created; if the memory segment already exists, the call will fail and -1 will be returned. IPC_EXCL is useless by itself, but in combination with IPC_CREAT it can be used to prevent an existing memory segment from being opened for access. Once a process has obtained a valid IPC identifier for a given memory segment, its next step is to attach that memory segment, or map that memory segment into its own addressing space.

Use IPC_CREAT alone: ​​Create a shared memory, if the shared memory does not exist, create it; if it exists, get the existing shared memory and return
IPC_EXCT Can not be used alone, generally need to cooperate with IPC_CREAT
 IPC_CREAT | IPC_EXCT: Create a shared memory, If the shared memory does not exist, create it, if it exists, error and return (if the shared memory is successfully created, the shared memory must be the latest)

Obtain the shared memory address function shmat()

The function shmat() is used to obtain the address of the shared memory. After obtaining the shared memory successfully, it can be read and written like a general memory. The prototype of the function is as follows:

#include <sys/types.h>
#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);

shmid: shared memory ID

shmaddr: specify the address of the connection

shmflg: Its two possible values ​​​​are SHM_RND and SHM_RDONLY

Return value: successfully returns a pointer to the first byte of the shared memory; fails to return -1

If the shmaddr parameter value is equal to 0, the kernel will try to find an unmapped region. Users can specify an address, but usually this address is only used to access owned hardware, or to resolve conflicts with other applications. The SHM_RND flag can be ORed with the flag parameter, and the result is then set as the flag parameter, so that the transmitted address page can be aligned.
In addition, if the SHM_RDONLY flag is ORed with the flag parameter, and the result is set as the flag parameter, the mapped shared memory segment can only be marked as read-only.
When the application is successful, the operation of shared memory is the same as that of general memory, and can be directly written and read, as well as offset operations. 

Delete the shared memory function shmdt()

The function shmdt() is used to delete a section of shared memory. The prototype of the function is as follows:

#include <sys/types.h>
#include <sys/shm.h>
int shmdt (const void *shmaddr) ;

shmaddr: pointer returned by shmat

Return value: success returns 0; failure returns -1

Note: Detaching the shared memory segment from the current process does not mean deleting the shared memory segment

When a process no longer needs a shared memory segment, it must call this function to disconnect the segment. This is not the same thing as removing a memory segment from the kernel. After a successful disconnect operation, the value of the shm nattch member of the associated shmid ds structure is decremented by 1. If this value is reduced to 0, the kernel will actually delete the memory segment.

Shared memory control function shmctl()

The shared memory control function shmctl uses a method similar to iocl() to operate shared memory: send commands to the shared memory handle to complete a certain function. The prototype of the function shmcl() is as follows, where shmid is the handle of the shared memory, emd is the command sent to the shared memory, and the last parameter buf is the parameter to send the command to the shared memory.

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf) ;

shmid: Shared memory identification code returned by shmget

cmd: the action to be taken (there are three possible values)

buf: points to a data structure that holds the mode state and access rights of the shared memory

Return value: success returns 0; failure returns -1

  •  IPC_STAT: Set the data in the shmid_ds structure as the current associated value of the shared memory.
  •  IPC_SET: Get the shmid_ds structure of the memory segment and store it in the address specified by the buf parameter. IPC_ SET sets the value of the ipc_perm member of the shmid_ds structure of the memory segment, and this command obtains the value from the buf parameter.
  • IPC_RMID: Mark a memory segment for deletion. This command does not actually delete the memory segment from memory. Instead, it just marks the memory segment for future deletion. The actual deletion will only happen if the last process currently connected to the memory segment has properly disconnected from it. Of course, if no process is currently connected to the memory segment, the deletion will happen immediately. In order to properly disconnect from its shared memory segment, a process needs to call the shmdt() function.

Little experiment: 

 comm.hpp (encapsulation of the method)

#ifndef __COMM_HPP__
#define __COMM_HPP__

#include <iostream>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <cassert>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>

using namespace std;

// IPC_CREAT and IPC_EXCT
// 单独使用 IPC_CREAT :创建一个共享内存,如果共享内存不存在 ,就创建它;如果存在,就获取已经存在的共享内存并且返回
// IPC_EXCT不能单独使用,一般都要配合IPC_CREAT
// IPC_CREAT |IPC_EXCT:创建一个共享内存,如果共享内存不存在,就创建它,如果存在,出错并返回(如果共享内存创建成功,则这个共享内存一定是最新的)

#define PATHNAME "."
#define PROJID 0x6666

const int gsize = 4096;

key_t getKey()
{
    key_t k = ftok(PATHNAME, PROJID);
    if (k == -1)
    {
        cerr << "error:" << errno << strerror(errno) << endl;
        exit(1);
    }
    return k;
}

// 十六进制转换
string toHex(int x)
{
    char buffer[64];
    snprintf(buffer, sizeof buffer, "0x%x", x);
    return buffer;
}

static int createShmHelper(key_t k, int size, int flag)
{
    int shmid = shmget(k, size, flag);
    if (shmid == -1)
    {
        cerr << "error:" << errno << ":" << strerror(errno) << endl;
        exit(2);
    }
    return shmid;
}

int createShm(key_t k, int size)
{
    umask(0);
    return createShmHelper(k, size, IPC_CREAT | IPC_EXCL | 0666);
}

int getShm(key_t k, int size)
{
    umask(0);
    return createShmHelper(k, size, IPC_CREAT);
}
char *attachShm(int shmid)
{
    char *start = (char *)shmat(shmid, nullptr, 0);
    return start;
}

void detachShm(char *start)
{
    int n = shmdt(start);
    assert(n != -1);
    (void)n;
}
void delShm(int shmid)
{
    int n = shmctl(shmid, IPC_RMID, nullptr);
    assert(n != -1);
    (void)n;
}
#define SERVER 1
#define CLIENT 0
class Init
{
public:
    Init(int t)
    :type(t)
    {
        key_t k = getKey();
        if(type == SERVER)
            shmid = createShm(k,gsize);
        else
        shmid = getShm(k,gsize);

        start = attachShm(shmid);

    }
    char* getStart(){return start;}
    ~Init()
    {
        detachShm(start);
        if(type == SERVER)
            delShm(shmid);
    }

private:
    char*start;
    int type;//server or client
    int shmid;
};
#endif

server.cc

#include "comm.hpp"
#include <unistd.h>
int main()
{
    Init init(SERVER);
    char* start = init.getStart();

    int n = 0;
    while(n <= 26)
    {
        cout<<"client -> server#"<<start<<endl;
        sleep(1);
        n++;
    }
    // //1.创建key
    // key_t k = getKey();
    // cout<<"server key:"<<toHex(k)<<endl;

    // //2.创建共享内存
    // int shmid = createShm(k,gsize);
    // cout<<"server shmid:"<<shmid<<endl;

    // //3.将自己和共享内存关联起来
    // char* start = attachShm(shmid); 
    // sleep(5);
    // //4.将自己和共享内存去关联
    // detachShm(start);
    // //删除共享内存
    // delShm(shmid);
    return 0;
}

client.cc

#include "comm.hpp"
#include <unistd.h>
#include <string>
#include <vector>
int main()
{
    Init init(CLIENT);
    char* start = init.getStart();

    char c = 'A';

    while(c <= 'Z')
    {
        start[c - 'A'] = c;
        c++;
        start[c - 'A'] = '\0';
        sleep(1);
    }
    // //1.创建key
    // key_t k = getKey();
    // cout<<"client key:"<<toHex(k)<<endl;

    // //2.创建共享内存
    // int shmid = createShm(k,gsize);
    // cout<<"client shmid:"<<shmid<<endl;

    // //3.将自己和共享内存关联起来
    // char* start = attachShm(shmid);

    // sleep(10);
    // //4.将自己和共享内存去关联
    // detachShm(start);
    return 0;
}

amount of signal 

A semaphore is a counter used to control access to resources shared by multiple processes. They are often used as a locking mechanism to prevent another process from accessing a particular resource while one process is operating on it. The producer and consumer model is a typical use of semaphores.

Semaphore Data Structure

The semaphore data structure is a data structure often used in semaphore programming.

union semun { /*Union structure of semaphore operation*/
int val; /*integer variable*/
struct semids *buf; /*semid_ ds structure pointer*/
unsigned short *array; /*array type*/
struct seminfo *_buf; /*Semaphore internal structure*/
};

New semaphore function semget()

The semget() function is used to create a new set of semaphores, or to access an existing set. Its prototype is as follows, where the first parameter key is the key value generated by ftok, the second parameter nsems parameter can specify the number of semaphores that should be created in the new collection, and the third parameter semflsg is the way to open the semaphore.

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_ t key, int nsems, int semflg);

semflsg is the way to open the semaphore.
IPC_ CREAT: If such a semaphore set does not exist in the kernel, create it.
IPC_ EXCL: When used with IPC_CREAT, the operation will fail if the semaphore set already exists. If IPC_CREAT is used alone, semget() returns either the semaphore set identifier of a newly created semaphore set; or the identifier of an already existing set with the same key value. If IPC_ EXCL and IPC_ CREAT are used at the same time, there will be two possible results: if the collection does not exist, a new collection will be created; if the collection already exists, the call will fail and return -1. IPC_EXCL is useless by itself, but when combined with IPC_CREAT it can be used to prevent an existing set of semaphores from being opened for access.

typedef int sem_t;
union semun {   /*信号量操作的联合结构*/
	int val;   /*整型变量*/
	struct semid ds * buf;   /*semid_ ds结构指针*/
	unsigned short* array;  /*数组类型*/
}arg;
sem_t CreateSem(key_t key, int value)//建立信号量
{
	union semun sem;/*信号量结构变量*/
	sem t semid;/*信号量ID*/
	sem.val = value;/*设置初始值*/
	semid = semget(key, 0, IPC_ CREAT | 0666);/*获得信号量的ID*/

	if (-1 == semid)/*获得信号量ID失败*/
	{
		printf("create semaphore error\n"); /*打印信息*/
		return -1;/*返回错误*/
	}
	semctl(semid, 0,SETVAL, sem);/*发送命令,建立value个初始值的信号量*/
	return semid;/*返回建立的信号量*/
	
}

 The CealeSem() function generates a semaphore according to the user's key value, and sets the initial value of the semaphore to the value entered by the user.

The semaphore operation function semop() 

The P and V operations of the semaphore are completed by sending commands to the established semaphore (using the semget() function). The function that sends a command to a semaphore is semop(), and the prototype of this function is as follows:

#include<sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops,unsigned nsops) ;

The second parameter (sops) of the semop() function is a pointer to an array that will perform operations on the semaphore set, and the third parameter (nsops) is the number of operations in the array. The sops parameter points to an array of sembuf structures. The sembuf structure is defined in linux/sem.h as follows.

struct sembuf {         ushort sem_num; /*number of semaphore*/         short sem_op; /*operation of semaphore*/         short sem_ flg; /*operating flag of semaphore*/


}

  • sem_num: The number of the semaphore to be processed by the user.
  • sem_op: The operation to be performed (positive, negative, or zero).
  • sem_flg: Flag for semaphore operations. If sem_op is negative, subtract a value from the semaphore. If sem_op is positive, add the value from the semaphore. If sem_op is 0, the process is set to sleep until the value of the semaphore is 0. 

For example, "stuet sembuf sem={0, +1, NOWAIT}:" means adding 1 to semaphore 0. The basic P and V operations can be constructed with the function semop(), and the code is as follows. Sem_P constructs a sembuf structure of {0, +1, NOWAIT} to increase a semaphore value: Sem_V constructs a sembuf structure of {0, -1, NOWAIT} to perform an operation of reducing a semaphore, corresponding to The semaphore is passed in (semid) by the function.

int Sem P(sem t semid)  /*增加信号量*/ 
{

        struct sembuf sops={0,+1, IPC NOWAIT} ; /*建立信号量结构值*/
        return (semop (semid, &sops,1)) ; /*发送命令*/

}
int Sem V(sem t semid)  /*减小信号量值*/
{
        struct sembuf sops={0,-1,IPC NOWAIT} ;  /*建立信号量结构值*/
        return (semop (semid, &sops,1));  /*发送信号量操作方法*/

}

Control semaphore parameter semctl() function

Similar to the ioctl() function of the file operation, other operations on the semaphore are done through the function semctl(). The prototype of the function semctl() is as follows:

#include <sys/types
#include <sys/types.h>
#include <sys/sem. h
int semctl(int semid, int semnum, int cmd, .. );

The function semctl() is used to perform control operations on semaphore sets. This call is similar to the function msgctl(), and the msgctl() function is used for operations on the message queue. The first parameter of the semctl() function is the value of the keyword (in our case it is the value returned by calling the semget) function). The second parameter (semun) is the number of the semaphore to perform the operation. It is an index value of the semaphore set. For the first semaphore in the set (there may be only this semaphore), its The index value will be a value of 0. The cmd parameter represents the command to be executed on the collection:

  • IPC_STAT: Get the semid_ds structure of a collection and store it in the address specified by the buf parameter of the semun union.
  • IPC_SET: Set the value of the ipc_perm member of the semid_ds structure of a set. The value of this command is obtained from the buf parameter of the semun union.
  • IPC_ _RMID: Remove the set from the kernel.
  • GETALL: Used to get the values ​​of all semaphores in the set. Integer values ​​are stored in an array of unsigned short integers specified by the array member of the union.
  • GETNCNT: Returns the number of processes currently waiting for resources.
  • GETPID: Returns the PID of the process that executed the last semop call.
  • GETVAL: Returns the value of a semaphore in the collection.
  • GETZCNT: Returns the number of processes waiting for resource utilization to reach 100 percent.
  • SETALL: Set the values ​​of all semaphores in the set to the corresponding values ​​contained in the array members of the union.
  • SETVAL: Set the value of a single semaphore in the set to the value of the val member of the union.
     
void SetvalueSem(sem_t semid, int val)
{
	union semun sem;
	sem.val = value;
	semctl(semid, 0, SETVAL, sem);
}
void GetvalueSem(sem_t semid)
{
	union semun sem;
	return semctl(semid, 0, GETVAL, sem);
}

The SetvalueSem() function sets the value of the semaphore, which is realized through the SETVAL command, and the set value is realized through the val field of the joint variable sem. The GetvalueSem() function is used to obtain the value of the semaphore, and the command GETVAL of the semctl() function will make it return the current value of the given semaphore. Of course, destroying the semaphore can also be achieved using the semctl() function.

void DeatroySem(sem_t semid)
{
	union semun sem;
	sem.val = 0;

	semctl(semid, 0, IPC_RMID, sem);
}

Guess you like

Origin blog.csdn.net/m0_55752775/article/details/130699692