Linux inter-process communication (3): the principle and use of shared memory

I. Introduction

 The core idea of ​​shared memory is to map the physical memory to the shared area of ​​the process address space through the page table . When two processes map the same piece of physical memory, they can communicate between processes.

 Shared memory is the fastest form of IPC (Inter-Process Communication). Once such physical memory is mapped into the process address space that shares it, the transfer of data between processes will no longer involve the kernel. In other words, processes no longer need to execute system calls into the kernel to transfer data to each other, so the speed is fastest.
insert image description here

2. Create shared memory

insert image description here
[Parameter Description]:

  • 1.size: The size of the shared memory
      specifies the size of the shared memory (unit B ). Note that the size of the allocation is preferably an integer multiple of the page ( 4KB ), because the operating system uses the page as the smallest unit ( rounded up ) to allocate memory. For example, if you apply for a space of 1.01 pages, the operating system will also allocate 2 pages to you, but you can only use the space of 1.01 pages.

  • 2. shmflg: Permission flag
    flag macro, use |to realize various flag combinations. Two common tags are:

    1. IPC_CREAT: Create shared memory. If it already exists, get it; if not, create it
    2. IPC_EXCL: Not used alone, must be used in conjunction with IPC_CREAT . If the specified shared memory does not exist, it is created; if it exists, an error is returned. This ensures that a new section of shared memory will be created.
  • 3. key: the name of the memory segment
    In the Linux operating system kernel, the data structure for managing shared memory is as follows:
    image-20221117192630180
     where __keyis used to identify the uniqueness of the shared memory , and this key value is generally provided by the programmer. If the server applies for shared memory with a certain key value, then the client can see the same shared memory with the same key, thus realizing inter-process communication.

 The essence of shared memory is to use an agreed unique key value for communication . In theory, the key value can be taken arbitrarily, as long as it does not conflict with the existing shared memory in the operating system. But we generally don't get the key value by ourselves. It is recommended to use ftok()the function to randomly generate the key value:
insert image description here
ftok function converts a path name and an integer identifier into a key_t value, called an IPC key value. The value of the identifier is between 1 and 255, because only the last 8 bits are used, and the key value cannot be 0
[Return value]: Returns a valid shared memory identifier

[use demo]:

#define PATH_NAME "/home/whc/linux-daily/process/shm"
#define PROJ_ID 0x1

int main()
{
    
    
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if(key < 0)
    {
    
    
        perror("ftok");
        exit(1);
    }
    // 必须创建一个新的共享内存
    if(shmget(key, 4096, IPC_CREAT | IPC_EXCL) < 0)
    {
    
    
        perror("shmget");
        exit(2);
    }
    return 0;
}

3. Basic operation of shared memory

 The life cycle of shared memory is with the kernel , the process exits, and the shared memory still exists. If the deletion is not displayed, the shared memory will be cleared only after restarting the system:

[Question 1]: How to view the existing shared memory information in the system?

Answer: Use ipcs -m the command to view
insert image description here
 Since we set the flag option to IPC_CREAT | IPC_EXCL, an error will be returned when the specified shared memory already exists:
insert image description here


[Question 2]: How to explicitly delete shared memory?

  1. Use ipcrm -m + corresponding shmid
    insert image description here
  2. If we want the program to automatically release the shared memory after running, we can call the system interface shmctl(). It should be noted that the delete operation of the shared memory is not a direct deletion, but a rejection of subsequent mapping . Only when the current mapping link number is 0 , which means that there is no process access, will it be actually deleted
    insert image description here
//  cmd 参数用于指定操作,设置为IPC_RMID即可用于删除共享内存
shmctl(key, IPC_RMID, nullptr);

[Question 3]: How to set the permissions of shared memory

 By observing ipcs -mthe displayed shared memory information, it is not difficult to find that the value of the perm (permission) column is 0, that is, the shared memory we are currently creating does not have any read, write and execute permissions. The permissions of shared memory are determined at the time of creation:

// 指定共享内存的权限为 0666
shmget(key, 4096, IPC_CREAT | IPC_EXCL | 0666) 

4. Associated shared memory

insert image description here
[Function]: Associate shared memory. Map the shared memory to the shared area of ​​the current process
[parameter description]:

  • shmid: shared memory identifier
  • shmaddr: Specifies the address to connect to. Generally set NULLto , the system will automatically associate a suitable unused address
  • shmflag: generally set to 0

[Return value]: Returns the address of the shared memory segment that is attached (it can be compared to the return value of malloc)

char* s = shmat(shmid, nullptr, 0);
shmdt(s);      // 类比于free函数

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

5. Use pipelines to realize access control of shared memory

Create shared memory → associate shared memory → read and write operations → disassociate → delete shared memory . Through the study of the above interface, we can basically control the shared memory freely. Since the shared memory has been mapped to the shared area of ​​the process address space through the page table, using the shared memory is like accessing a continuous array, which is very simple and does not require any system interface.

 But it is not difficult to find that shared memory lacks an access control mechanism , and there is no synchronization and mutual exclusion mechanism. The two parties using the shared memory do not know each other's existence, so when they write to the shared memory at the same time, they are all scrambling for each other, and the written data is naturally chaotic; One party reads the data in the shared memory away, which will cause incomplete reading of the data.
 Here, I will use the access control mechanism of the pipeline itself to help the shared memory implement a set of access control mechanisms:

// Comm.hpp
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/shm.h>
#include <cstring>
#include <string>
#include <fcntl.h>

using namespace std;

#define FIFO_PATH "./.Fifo"
#define PATH_NAME "/home/whc/linux-daily/process/shm"
#define PROJ_ID 0x1
#define MEM_SIZE 128

void CreateFifo()
{
    
    
    if(access(FIFO_PATH, F_OK))   // 管道文件不存在时才创建
    {
    
    
        if(mkfifo(FIFO_PATH, 0666) < 0)
        {
    
    
            perror("mkfifo");   // 打印错误信息
            exit(1);
        }
    }
}


key_t CreateKey()
{
    
    
    key_t key = ftok(PATH_NAME, PROJ_ID);
    if(key < 0)
    {
    
    
        perror("ftok");
        exit(2);
    }
    return key;
}


int OpenFifo(int flags)    // 打开管道文件
{
    
    
    return open(FIFO_PATH, flags);
}

int Wait(int fd)
{
    
       
    int tmp = 0;
    return read(fd, &tmp, sizeof(tmp));
}

void Signal(int fd)
{
    
    
    int cmd = 1;
    write(fd, &cmd, sizeof(cmd));
}
// shmService.cc
#include "Comm.hpp"

int main()
{
    
    
    CreateFifo();
    int shmid = shmget(CreateKey(), MEM_SIZE, IPC_CREAT | IPC_EXCL | 0666);
    if(shmid < 0)
    {
    
    
        perror("shmid");
        exit(3);
    }
    char* str = (char*)shmat(shmid, nullptr, 0);
    int fd = OpenFifo(O_RDONLY);

    while(true)
    {
    
    
        printf("服务端# ");
        fflush(stdout);
        if(Wait(fd) <= 0) break;
        printf("%s\n", str);
    }
    printf("服务端退出了……\n");
    shmdt(str);
    close(fd);
    unlink(FIFO_PATH);
    shmctl(shmid, IPC_RMID, nullptr);
    return 0;
}
// shmClient.cc
#include "Comm.hpp"

int main()
{
    
    
    int shmid = shmget(CreateKey(), MEM_SIZE, IPC_CREAT);
    if(shmid < 0)
    {
    
    
        perror("shmid");
        exit(3);
    }
    char* str = (char*)shmat(shmid, nullptr, 0);
    int fd = OpenFifo(O_WRONLY);
    while(true)
    {
    
    
        printf("请输入# ");
        fflush(stdout);
        ssize_t s = read(0, str, MEM_SIZE);
        if(s > 0)
        {
    
    
            str[s - 1] = '\0';  // 注意换行符也会被读取
        }
        else if(s == 0)
        {
    
    
            printf("客户端退出了……\n");
            break;
        }
        else 
        {
    
    
            perror("read");
            exit(4);
        }
        Signal(fd);
    }
    shmdt(str);
    close(fd);
    return 0;
}

Guess you like

Origin blog.csdn.net/whc18858/article/details/128467044