[Linux] Shared memory for inter-process communication

Shared memory is faster than pipes~

Article directory


foreword

Shared memory areas are the fastest form of IPC. Once such memory is mapped into the address space of the processes sharing it, these inter-process data transfers no longer involve the kernel, in other words processes no longer pass data to each other by executing system calls into the kernel.

First, the realization principle of shared memory

Let’s take the above picture as an example. A and B are two processes. They both have their own process address space. The process address space is mapped to physical memory through the page table. What is shared memory? There is a mechanism to directly open a piece of memory in the physical memory. This piece of memory is mapped to the shared area in the process address space through the page table (the shared area is between the heap area and the stack area), and then the space in the shared area is The starting address of the resource is returned to the user. If two processes perform the same work, it is possible for different processes to see the same resource, so how to disassociate the two processes? In fact, it is very simple, as shown below:

 To cancel the relationship between two processes, you only need to modify the page table so that the process address space no longer finds the shared memory through the mapping of the page table, and release the opened space, which completes the disassociation between the processes. The above is the basic principle of shared memory.

Second, the code to achieve shared memory

First we create the required files, for example, here I created shmclient.cc and shmserver.cc and makefile, and then we need to use makefile to help us create two executable programs:

.PHONY:all
all:shmclient shmserver

shmclient:shmclient.cc
	g++ -o $@ $^ -std=c++11

shmserver:shmserver.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f shmclient shmserver

Of course we also need a file to store header files:

 In order to prevent repeated inclusion of header files, we use conditional compilation. Next, we start to write the code formally. Before that, we need to understand the shmget function, which is used to obtain shared memory:

 This function has three parameters, the first parameter key will be explained later, the second parameter size is the size of the shared memory to be opened, the third parameter is the mode, IPC_CREAT means to create a shared memory, if the shared memory is not If it exists, create it, and if it already exists, get the existing shared memory and return it. IPC_EXCL cannot be used alone. It must be used in conjunction with IPC_CREAT. The usage method is (IPC_CREAT | IPC_EXCL). The combination of these two is to create a shared memory. If the shared memory does not exist, create a new shared memory. If the shared memory already exists memory, it immediately returns an error, so what's the point of this? The meaning is to ensure that the shared memory created for us must be the latest and has not been used.

Let's take a look at the return value of the shmget function:

 If we create successfully, we will return a shared memory flag, and if it is wrong, we will return -1. Now let’s talk about what the first parameter key is. We can see the type key_t of this parameter, and this parameter is actually We need another function ftok to get it. Let's take a look at the ftok function:

 The first parameter of the ftok function is the path, and the second parameter is an id of type int. These two parameters are filled by the user. The ftok function will combine the path and the id to convert a key value that is difficult to be repeated. Since there must be multiple shared memories in the system at the same time, the shared memory is not what we think, just open up space in the memory, but the system will build a structure to describe the shared memory in order to manage the shared memory, so the shared memory = Kernel data structure of shared memory + real memory space opened up. Let's explain the relationship here by drawing a picture:

 The four shared memory structures in the middle are managed by the system, and the red shared memory is opened by process A, so how to make process B also point to the shared memory opened by A, just let them both use The same path and the same id generate the same key value. With this value, process B can point to the shared memory created by A.

After understanding the above knowledge, we can write the code directly, first package all the header files, because both processes need the same path and id, so let's define it directly:

 We directly define the path as a ., a dot means that it is in the current path, then when the shared memory is created, it will be created in the current path. Let's directly write a function to get the key:

Before using the ftok function, let's take a look at the return value of this function:

 Returns the created key value if successful, or -1 if failed.

#ifndef __COMM__HPP__
#define __COMM__HPP__

#include <iostream>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
using namespace std;
#define PATHNAME "."
#define PROJID 0x6666

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

After we have the key, we can start to create the shared memory in the next step. From the picture we just drew, we can see that there must be a process that created the shared memory first, and then another process went to get the shared memory just created, so Our next step is to create a shared memory on the server side:

 First, we first use a global variable to control the size of the shared memory, and then directly use two function packages to create and open the shared memory:

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

The first static function is the step for us to create shared memory. Flag is the option of how we want to create. The first creation must be a brand new one that has not been used. This is for the server, and the second is to open the existing one. The shared memory is for the client, and then we also write the complete code on the client and server:

 Let's run the code and test it out:

 We run the program and find that there is no problem. Let’s check whether the shared memory is successfully created. The ipcs command can display three resources:

The first resource is a message queue, the second resource is a shared memory segment, and the third is a shared memory core array. We mainly look at the second resource, and we can see that the 1024-sized shared memory we created was created successfully. Of course, we can only check the second one, the command is ipcs -m:

 Let's talk about the meaning of the above, the key is like the inode of the file, and the shmid is like the fd of the file, and the shmid is the core data needed to operate the shared memory. Who created the owner, perms is the permission, and nattch is the number of connections to this shared memory. Let's talk about the command to delete a shared memory: ipcrm -m +shmid (I just said that shmid is used to operate shared memory, which is the same as the fd of the file):

 We successfully deleted the shared memory we just created. Let's continue to write down the code. After creating the shared memory, the client can write something to the server, and then need to delete the shared memory when exiting. Let's take a look at how to delete the shared memory:

 Using the system call to delete the shared memory requires the shmctl function. The first parameter is the shmid of the shared memory, and the second parameter is the instruction, that is, what to do with the shared memory. Let's see what the instructions are:

 There are several common instructions, and IPC_RMID is the operation that actually deletes the shared memory. The first IPC_STAT is to obtain the attributes of the shared memory, and the third parameter is a pointer to a shared memory structure. You can view the corresponding attributes of the shared memory. :

 Let's write a delete shared memory function:

void delshm(int shmid)
{
    int n = shmctl(shmid,IPC_RMID,nullptr);
    assert(n!=-1);
    (void)n;
}

 Because we don't care about its attributes when we delete the shared memory, we just set it to empty.

 Let's show you how to view the properties of shared memory:

 Next, we can also set the permissions when creating shared memory:

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

Next, we need to associate the created shared memory with the process, and the shmat function is required for the association:

 We all know the first parameter, the second parameter means that the shared memory can be specified to be attached to the specified address (here can be set to nullptr, after the nullptr, the operating system will choose the appropriate address to mount), the first The three parameters are to set whether the shared memory is read-only or other permissions (we can set it to 0, and the default is readable and writable)

char* attachShm(int shmid)
{
    char* start = (char*)shmat(shmid,nullptr,0);
    return start;
}

 After we associate the process with the shared memory, the next step is to de-associate them before exiting. De-association also requires a function. This function is shmdt:

 We have already talked about the parameters of this function, which is the address returned to us when we just connected, so we can write the code directly:

void detachShm(char* start)
{
    int n = shmdt(start);
    assert(n!=-1);
    (void)n;
}

 We have finished talking about all the points of the memory above. Let’s encapsulate it with a class so that the code of the client and server can be reduced:

#define SERVER 1
#define CLIENT 0
class Init
{
public:
    Init(int type)
    {
        key_t k = getKey();
        if (type==SERVER)
        {
            shmid = creatShm(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;
    int shmid;
}

Through the encapsulation of the above classes, our client and server can be simplified as follows:

 

 We can see that when both ports are running at the beginning, there are two processes linked to the shared memory. When the client command input is completed and exits, only the server is left. When the server also exits, the number of connections will increase. to 0.

 The above is all the content of our shared memory. After learning shared memory, you must know that shared memory is the fastest access among several methods of inter-process communication.


Summarize

After using the ipcrm -m command to delete the specified shared memory, the shared memory will not be released directly, because the life cycle of the shared memory depends on the operating system, and the shared memory will be deleted and released only when the current mapping link number of the shared memory is 0.

Use ipcrm -a to delete all inter-process communication resources (a stands for all, which means all)

ipcrm -m deletes shared memory, -q deletes message queues, -s deletes semaphores

The principle of shared memory implementation to achieve communication is because all process operations map the same piece of physical memory.

The operation of shared memory is not process-safe, and multiple processes may read and write to shared memory at the same time, which may cause cross writing or reading of data, resulting in data confusion.

The deletion operation of the shared memory is not a direct deletion, but a rejection of subsequent mapping. Only when the number of current mapping links is 0, indicating that there is no process access, will it be truly deleted.

The shared memory life cycle follows the kernel, as long as it is not deleted, it will always exist in the kernel unless the system is restarted or manually deleted.

Guess you like

Origin blog.csdn.net/Sxy_wspsby/article/details/130441608