[Linux] Inter-process communication - system V shared memory

Table of contents

 words written in front

System V shared memory principle

Establishment of System V shared memory

Code to implement System V shared memory

Create shared memory shmget()

ftok()

delete shared memory shmctl()

Hook shared memory shmat()

Unhook shared memory shmdt()

Implementation of the overall communication process


 words written in front

         In the previous chapter, we talked about the first way of inter-process communication---pipelines. In this chapter, we will continue to explain the second way of inter-process communication--- system V shared memory .

        Before explaining, it is recommended to understand the method and principle of the inter-process communication pipeline, so that it will be much easier to understand the system V shared memory.

System V shared memory principle

        Shared memory is an inter-process communication (IPC) mechanism that allows multiple processes to share the same memory area so that they can directly read and write data in it, thereby achieving efficient data sharing and communication.

         In shared memory, when the process task_struct creates or connects to the shared memory segment, the operating system will allocate a virtual address space mm_srtuct for each process, and map this virtual address to the same physical memory area through the page table . In this way, multiple processes can access the same physical memory through their own virtual addresses , realizing shared access to the same block of memory. Through the mapping of virtual addresses to physical addresses , multiple processes can see the same shared memory.

        So how to release shared memory?

It is also very simple, you only need to remove the mapping established between each process and the shared memory , and then release the shared memory.

Establishment of System V shared memory

        Assuming that many processes are using shared memory, there will also be a large number of shared memory blocks in the memory, so the OS needs to manage these shared memory blocks. The method: first describe and then organize, so that the shared memory attributes can be abstracted into a data structure , and then use some methods to organize these data structures. So:

       1. Shared memory = shared memory block + kernel data structure corresponding to the shared memory block

       2. The shared memory block must not belong to any process, but to the operating system.

The general process of establishing shared memory is as follows:

  1. Create shared memory segment: Use  shmget() a system call to request the creation of a shared memory segment. This call needs to specify parameters such as the size of the shared memory, permissions and flags, and returns a unique shared memory identifier.

  2. Attach to shared memory segment: Use  shmat() a system call to attach the current process to a shared memory segment . This call will return the address of the shared memory segment and map that address into the virtual address space of the current process.

  3. Access to shared memory: Processes connected to shared memory can directly read and write data in the shared memory segment by performing memory operations on its addresses. Processes can store and access data in shared memory segments using pointers, arrays, or structures.

  4. Detach shared memory: When a process finishes accessing shared memory, it uses  a system call to detachshmdt()  it from the shared memory segment . After detaching, the process can no longer access the shared memory segment, but the shared memory segment still exists.

  5. Delete shared memory segment: When a shared memory segment is no longer needed,  it can be deleted using a system call. This call needs to specify the shared memory identifier and specific control operations, such as passing   parameters to delete the shared memory segment. shmctl()IPC_RMID

        The specific usage method and principle will be explained below.

Code to implement System V shared memory

        According to what we said above, the first step is to use shmget() to create a shared memory segment, let's take a look at its usage.

Create shared memory shmget()

        The purpose of this function is to create and obtain a shared memory.

        Let me talk about the second parameter first, which is size , which represents the size of the shared memory to be created

        Let's talk about the third parameter shmflg, which represents the options we want to set . There are two options:

        1. IPC_CREATE : Create shared memory, if the underlying layer already exists, get it and return; if it does not exist, create shared memory and then return,

        2. IPC_EXCL : It is meaningless to use it alone, and it is generally used together with IPC_CREATE, see below:

        3. IPC_CREATE | IPC_EXCL: If the underlying layer does not exist, create shared memory and return; if the underlying layer exists , return an error . The implication is that if the return is successful, it must be a brand new memory block !

        Finally, let’s talk about the first parameter key.

        When we use shared memory to communicate, there will be a problem. How can I ensure that the shared memory seen by the other party is created by me ? After all there is a lot of shared memory.

        For this, we need to pass the key. The key data is not important , as long as it is unique in the system ! As long as the two communication terminals A and B use the same key, they can find the same piece of shared memory. Because the key is unique, that is, this shared memory block is also unique!

        It is equivalent to numbering each shared memory block. This number is unique, so as long as you get the number, you can find the same shared memory block.

        So how to get this unique key value? Here you need to use the ftok() function

ftok()

Let's first look at the use of the function:

  1. pathname: A string used to identify the pathname of a file . Usually an existing file  is selected , because ftok() the function will use the inode number and  proj_id parameters of the file to generate the key value key through the algorithm.

  2. proj_id: An integer as the project identification number used to generate the key. This parameter usually takes a non-negative integer.


Then we look at the return value:

         See that the generated key value is returned if successful, otherwise -1 is returned.

These are clear, let's use it:

The first four files, comm.hpp, contain the necessary header files and macro definitions, which will not be displayed anymore;

        Log.hpp is a log file. We wrote it in the previous chapter. It can be omitted here, or it can be output directly by cout.

        In shmClient.cc we write the following code:

#include "comm.hpp"
#include "Log.hpp"
int main()
{
    key_t k = ftok(PATH_NAME,PROJ_ID);
    Log("create key done",Debug) << " client key : " << k << endl;

    return 0;
}

        Copy a copy of the code in shmServer, and then change the client in the output statement to server, then we compile and run to see the effect:

         It can be found that the same key value is generated, so that the same shared memory block can be found.


In this way, the first parameter of shmget is also finished, and then let’s talk about the return value.

The return value is the identifier of this shared memory     if the establishment is successful , otherwise -1 is returned and the error code is set.

delete shared memory shmctl()

        After we create the shared memory, we need to delete it at last, because the life cycle of the shared memory depends on the kernel !

        If it is not closed, as long as the operating system is running, it will always exist and occupy space resources. So it must be deleted.

        There are two ways to delete: manual command deletion, code deletion

Command delete:

        We first create a shared memory, and then the process finishes executing and the process exits.                

         We type in the terminal

ipcs -m

        To view the current usage of shared memory

         It can be found that it is not released. Then we can use

ipcrm -m shmid

        To delete the corresponding shared memory, at this time, after I enter this command, there is no such memory.

Perms are mentioned here, which means permissions. We can add permissions to the third option of shmget, as follows:

    int shmid = shmget(k,SHM_SIZE,IPC_CREAT | IPC_EXCL | 0666);

At this point perms becomes 666. 

code deletion

        It is too troublesome to delete manually every time. Wouldn’t it be more convenient for us to uninstall the program and automatically delete it for us?

        So we need to use a function shmctl()

 

  1. shmid: The identifier (ID) of the shared memory segment, which  shmget() is returned when the shared memory is created by calling a function  shmid.

  2. cmd: Control command, used to specify the type of operation to be performed. One of the following commands can be used:

    • IPC_STAT: Obtain the state information of the shared memory segment , and store the result in   the structure buf pointed to by the parameter  .struct shmid_ds
    • IPC_SET: Set the status information of the shared memory segment, using  buf the value provided in the parameter.
    • IPC_RMID: Deletes the shared memory segment, marks it as deleted, and destroys after freeing the last process's attached segment.
  3. buf: A  struct shmid_ds pointer to a structure, used to transfer or receive status information of the shared memory segment.

     This is the content of the struct shmid_ds structure pointer:

struct shmid_ds {
    struct ipc_perm shm_perm;   // 共享内存的权限信息
    size_t shm_segsz;           // 共享内存的大小
    time_t shm_atime;           // 上一次连接共享内存的时间
    time_t shm_dtime;           // 上一次与共享内存断开连接的时间
    time_t shm_ctime;           // 上一次修改共享内存的时间
    pid_t shm_cpid;             // 创建共享内存的进程ID
    pid_t shm_lpid;             // 最后一个操作共享内存的进程ID
    unsigned short shm_nattch;  // 当前连接到共享内存的进程数量
    // 其他字段...
};

The second parameter cmd we currently only use the third IPC_RMID option to delete shared memory.

We don't use the third parameter buf for the time being, and pass in nullptr directly.

    int n = shmctl(shmid,IPC_RMID,nullptr);

Hook shared memory shmat()

        We have created the shared memory. Of course, we need to mount the address space of the process to it before we can use it. The function shmat() is used here.

 

shmat() The function accepts three parameters:

  1. shmid: The identifier (ID) of the shared memory segment, which  shmget() is returned when the shared memory is created by calling a function  shmid. Indicates which shared memory you want to mount.

  2. shmaddr: The shared memory segment is connected to the first address of the process address space . Typically it is set to  NULL, which instructs the system to choose an appropriate address. If you want to specify a specific address, you can pass a non-empty address value. But it is not recommended to use it like this.

  3. shmflg: flag parameter, used to specify options for connecting to shared memory. Commonly used options are:

    • SHM_RDONLY: Connect to the shared memory in read-only mode, no writing is allowed.
    • SHM_RNDshmaddr Ignore the parameter, and the system selects an address for connection.

              For other options refer to  shmat() the function's documentation for more details.


        Its return value is void*, which is a pointer to the shared memory segment, which is the first address connected to the process address space . We need to force the result to the type we need, generally char*, similar to the use of malloc.

So it can be used like this:

    char* shmaddr = (char*)shmat(shmid,nullptr,SHM_RDONLY);

shmaddr is the first address of the connected process address space.

Unhook shared memory shmdt()

        When deleting shared memory, no matter how many processes are connected to the shared memory, they will be cleaned up directly. This method is not very good, so you can cancel the connection before deleting the shared memory. When the hook number is 0, release the shared memory.

        Also look at the usage first:

         This parameter happens to be the first address shmaddr of the process address space returned by shmat just now, which means that the shared memory is separated from the address space of the calling process, so that the process can no longer access the shared memory .

        Look at the return value again:

        Returns 0 if the unhook was successful, and -1 on failure.

So we can use it directly like this:

    //3.将指定的共享内存,挂接到自己的地址空间
    char* shmaddr = (char*)shmat(shmid,nullptr,SHM_RDONLY);

    //这里就是通信的逻辑

    //4.将指定的共享内存,从自己的进程地址空间取消关联
    int n = shmdt(shmaddr);
    assert(n != -1);

Implementation of the overall communication process

        Knowing the above interface, plus the steps we mentioned in the establishment of shared memory, we can make a complete communication process using System V shared memory.        

        0. We need to use ftok() to generate the unique identification key of the shared memory.

        1. Then we use the key to call the shmget() function to create shared memory

        2. Then we need to use shmat() to mount the shared memory

        3. Then implement the communication process

        4. Use shmdt() to unhook

        5. Use shmctl() to delete shared memory (usually server, who created it will delete it )

There are a total of four files here, namely Log.hpp (log information), comm.hpp (common header file), shmServer.cc, shmClient.cc 

Log.hpp (log information)

#pragma once
#include <iostream>
#include <ctime>
#include<string>
using namespace std;

#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3

string msg[] = {
    "Debug ",
    "Notice",
    "Warning",
    "Error"
};

ostream& Log(string message,int level)
{
    cout << " | " << (unsigned)time(NULL) << " | " << msg[level] << " | " << message;

    return cout;
}

comm.hpp (shared header file)

#pragma once 
#include<iostream>
#include<string>
#include<cstring>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
using namespace std;

#define PATH_NAME "/home/hyx"
#define PROJ_ID 0x66
#define SHM_SIZE 4096 //最好是页(PAGE:4096)的整数倍,假设是4097,OS也会申请4096*2的空间,剩下的4095空间相当于浪费了 


shmServer.cc

#include "comm.hpp"
#include "Log.hpp"
string TransToHex(key_t k)
{
    char buffer[32];
    snprintf(buffer, sizeof(buffer),"0x%x",k);
    return buffer;
}
int main()
{
    //1.创建公共的key值
    key_t k = ftok(PATH_NAME,PROJ_ID);
    Log("create key done",Debug) << " server key : " << TransToHex(k) << endl;

    //2.创建共享内存 --- 建议创建一个全新的共享内存 
    int shmid = shmget(k,SHM_SIZE,IPC_CREAT | IPC_EXCL | 0666);
    if(shmid == -1)
    {
        perror("shmget");
        exit(1);
    }
    Log("create shm done",Debug) << " shmid: " << shmid << endl;
    // sleep(10);

    //3.将指定的共享内存,挂接到自己的地址空间
    char* shmaddr = (char*)shmat(shmid,nullptr,SHM_RDONLY);
    Log("attach shm done",Debug) << " shmid: " << shmid << endl;
    // sleep(10);

    //这里就是通信的逻辑
    //将共享内存当做一个大字符串
    // char buffer[SHM_SIZE];
    //结论1:只要双方使用shm,一方直接向共享内存中写入数据。另一方就可以立马看到
    //       共享内存是所有进程IPC,速度最快的! 因为不需要过多的拷贝!表现为不需要将数据拷贝给操作系统
    for(;;)
    {
        printf("%s\n",shmaddr);
        if(strcmp(shmaddr,"quit") == 0) break;
        sleep(1);
    }

    //4.将指定的共享内存,从自己的进程地址空间取消关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    Log("detach shm done",Debug) << " shmid: " << shmid << endl;
    // sleep(10);

    //5..删除共享内存
    n = shmctl(shmid,IPC_RMID,nullptr);
    assert(n != -1);
    Log("delete shm done",Debug) << " shmid: " << shmid << endl;

    return 0;
}

shmClient.cc 

#include "comm.hpp"
#include "Log.hpp"
int main()
{
    key_t k = ftok(PATH_NAME,PROJ_ID);
    if(k < 0)
    {
        Log("create key done", Error) << "client key : " << k << endl;
        exit(1);
    }
    Log("create key done",Debug) << " client key : " << k << endl;

    //获取共享内存
    int shmid = shmget(k,SHM_SIZE,IPC_CREAT);
    if(shmid < 0)
    {
        Log("create shm failed", Error) << "client key : " << k << endl;
        exit(2);
    }
    Log("create shm success", Debug) << "client key : " << k << endl;
    // sleep(10);

    
    char* shmaddr = (char*)shmat(shmid,nullptr,0);
    if(shmaddr == nullptr)
    {
        Log("attach shm failed", Error) << "client key : " << k << endl;
        exit(3);
    }
    Log("attach shm success", Debug) << "client key : " << k << endl;
    // sleep(10);

    //使用
    //client将共享内存看做一个char类型的buffer
    char a = 'a';
    for(;a <= 'e'; a++)
    {
        //我们是每一次都向shmaddr[共享内存起始地址]写入
        snprintf(shmaddr,SHM_SIZE-1,\
        "hello,server, my pid is: %d, inc : %c\n",getpid(),a);
        sleep(2);
    }
    strcpy(shmaddr,"quit");

    //去关联
    int n = shmdt(shmaddr);
    assert(n != -1);
    Log("detach shm success",Debug) << "client key : " << k << endl;
    // sleep(10);
    //client 不需要删除共享内存,server负责这些,和client没关系。
    return 0;
}

Then we compile and run, and observe in two windows:

 client

 It can be seen that both parties have achieved communication.

This is the end of the introduction of System V shared memory. If you have any questions or doubts, please feel free to comment or private message~

Guess you like

Origin blog.csdn.net/weixin_47257473/article/details/132110664