Linux interprocess communication – shared memory

Author: Lingmo
Blog homepage: Lingmo’s blog
Column: Linux system programming, file knowledge and understanding, Linux process learning…
If you think the blogger wrote well, I hope everyone will keep repeating (?Follow,?Like,?Comment), please support me a lot! !

Table of Contents

  • Shared memory principle
  • shared memory function
  • Shared memory related instructions
  • Shared memory code display
  • Shared memory, message queue, semaphore kernel data structure

Shared memory principle

Through the previous study, we know that the most important prerequisite for realizing the communication between two processes is to let the two processes see the same memory space. The anonymous pipes and named pipes mentioned in the previous section are essentially looking for a space in memory so that both processes can see it. The difference is that the implementation methods are different. The shared memory we are about to learn now is essentially to allow two processes to see the space on the same memory.

The implementation of shared memory is mainly the following process:

  1. The operating system opens up a space on the memory, and after the space is successfully opened, permissions are opened to the user.

  2. Similar to malloc, after the operating system has opened up such a space in memory, it will return the start address of this space to the process, and then through the mapping of the page table, the process address space of the process will be allocated for this space. addressing.

  3. Then let the process that wants to communicate with it obtain the starting address of the shared memory, and map it into its own process address space through the page table.

  4. In this way, two unrelated processes are associated with a piece of shared memory on the memory. Although the addresses of the shared memory seen by the two processes in their respective process address spaces are different, they are actually pointed to by the page table mapping. It’s the same space.

  5. Finally, the two processes can access this space separately, so that one process can hand over data to another process.

shared memory function

After learning the principles of shared memory, let’s learn how to interface functions related to shared memory.

  • ftok


    ftok, this function is used to generate a special digital key, which can be used to uniquely identify a shared memory. Internally, this function integrates a string and an integer into a special integer through some algorithms, and then outputs it.

    The first parameter is a file path + file name, this parameter must be an existing, accessible path file.

    The second parameter is an integer, which can be passed casually.

  • shmget

    shmget, the function of this system call interface is to open up space in memory as shared memory. If the creation is successful, the number of the shared memory is returned; if the creation fails, -1 is returned and the error code is set.

    The first parameter key is used to uniquely identify a shared memory. It doesn’t matter what this number is. What is important is that it can uniquely identify a shared memory. This number is generated by the ftok() function. In the future, this key will be written into the kernel data structure of the shared memory.

    The second parameter is to indicate the size of the shared memory to be created. It is generally recommended to be an integer multiple of 4096, because the basic unit of memory requested by the operating system is 4096 bytes.

    The third parameter is the option when creating the file, generally IPC_CREAT and IPC_EXCL. IPC_CREAT indicates that if the shared memory to be applied for does not exist, create and obtain the id of the shared memory. If it exists, get the id of the shared memory directly. IPC_EXCL should be used with IPC_CREAT. Indicates that if it does not exist, it will be created, and if it exists, an error will be reported. This can help us if the creation is successful, it must be a new shared memory. If we want to create a new shared memory, we also need to bring a permission. The final third parameter is as follows: IPC_CREAT | IPC_EXCL | 0600.

Seeing this, some readers may be confused. It was mentioned earlier that a key can uniquely identify a shared memory, and now we talk about the shared memory id, so what is the shared memory id?

In fact, both the key and the shmid can uniquely identify a shared memory, but the shmid is the user layer, and the key is the system layer. This is conducive to the decoupling of the user layer and the underlying code of the system.

  • shmat


    shmat associates the created shared memory with the process, that is, obtains the physical address of the shared memory, and converts it into a process virtual address through the page table. If the association is successful, it returns the virtual address after the shared memory mapping, if it fails, it returns (void *) -1 and the error code is set.

    The first parameter shmid is the return value after the shared memory is created successfully. This shmid is the number used by the user to uniquely identify a shared memory.
    The second parameter shmaddr is the address pointer to be attached. If it is nullptr, the system will automatically select an unused address to attach. Generally pass nullptr.
    The third parameter shmflg is a flag bit, which is used to control the attached method and permission. Its two possible values are SHM_RND and SHM_RDONLY. Generally pass 0.

    shmaddr is NULL, the kernel automatically selects an address
    If shmaddr is not NULL and shmflg has no SHM_RND flag, use shmaddr as the connection address.
    If shmaddr is not NULL and shmflg sets the SHM_RND flag, the connected address will be automatically adjusted down to an integer multiple of SHMLBA. Formula: shmaddr – (shmaddr % SHMLBA)
    shmflg=SHM_RDONLY, indicating that the connection operation is used to read-only shared memory

  • shmdt


    shmdt is to associate the process with the shared memory. If the deassociation succeeds, it returns 0, if it fails, it returns -1 and the error code is set.

    The parameter shmaddr indicates that the shared memory exists in the virtual address of the process.

  • shmctl


    shmctl is used to control shared memory, including deleting existing shared memory.

    The first parameter shmid is the number used to uniquely identify the shared memory.

    The second parameter cmd indicates the action to be taken. These three are common: IPC_STAT, IPC_RMID, IPC_SET.

    The third parameter buf is an output parameter, which points to a kernel data structure that saves the mode state and access rights of the shared memory.

    IPC_STAT: Set the data in the shmid ds structure as the current associated value of the shared memory.

    IPC_SET: On the premise that the process has sufficient permissions, set the current associated value of the shared memory to the value given in the shmidds data structure.

    IPC_RMID: delete shared memory.

Shared memory related instructions

ipcs ; View shared memory, message queue, semaphore
ipcs -m ; only view shared memory
ipcrm -m (shmid); delete the corresponding shared memory

Shared memory code display

  • comm.h – header file
#pragma once
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
using namespace std;
#define MAX_SIZE 4096
#define PATHNAME "."
#define PROJ_ID 0x66

key_t getkey()
{<!-- -->
    key_t key = ftok(PATHNAME, PROJ_ID);
    if(key == -1)
    {<!-- -->
        perror("ftok");
        exit(1);
    }
    return key;
}

int getshm(key_t key, int flags)
{<!-- -->
    int shmid = shmget(key, MAX_SIZE, flags);
    if(shmid == -1)
    {<!-- -->
        perror("shmget");
        exit(2);
    }
    return shmid;
}

int delshm(int shmid)
{<!-- -->
    int n = shmctl(shmid, IPC_RMID, nullptr);
    if(n == -1)
    {<!-- -->
        perror("shmctl");
        exit(3);
    }
    return n;
}

void* attachshm(int shmid)
{<!-- -->
    void* mem = shmat(shmid, nullptr, 0);
    if((long long) mem == -1L)
    {<!-- -->
        perror("shmat");
        exit(4);
    }
    return mem;
}

void detahshm(void* start)
{<!-- -->
    if(shmdt(start) == -1)
    {<!-- -->
        perror("shmdt");
        exit(5);
    }
}

  • client – client/send
#include "comm.h"
int main()
{<!-- -->
    key_t ckey = getkey();
    //cout << "ckey: " << ckey << endl;

    int cshmid = getshm(ckey, IPC_CREAT);
    //cout << "cshmid: " << cshmid << endl;

    char* start = (char*)attachshm(cshmid);
    //printf("client:%p\
", start);

    const char* message = "hello server, I am another process, communicating with you";
    int cnt = 1;
    while(true)
    {<!-- -->
        snprintf(start, MAX_SIZE, "%s[pid:%d][message number:%d]", message, getpid(), cnt ++ );
        sleep(1);
        if(cnt == 6)
            break;
    }

    detahshm(start);
    //cout << "client detahshm" << endl;
    //cout << "client quit" << endl;
    sleep(3);
    return 0;
}
  • server – server/receiver
#include "comm.h"

int main()
{<!-- -->
    key_t skey = getkey();
    //cout << "skey: " << skey << endl;

    int sshmid = getshm(skey, IPC_CREAT | IPC_EXCL | 0600);
    //cout << "sshmid: " << sshmid << endl;
    char* start = (char*)attachshm(sshmid);
    //printf("server:%p\
", start);
    while(true)
    {<!-- -->
        printf("client say:%s\
", start);
        sleep(1);
    }

    detahshm(start);
    //cout << "server detahshm" << endl;
    sleep(5);
    int ret = delshm(sshmid);
    //cout << "server quit" << endl;
    return 0;
}

shared memory, message queue, semaphore kernel data structure

  • Shared memory

  • message queue:

  • signal

Through observation, we found that the kernel data structure of the system V standard communication mechanism is embedded with a structure struct ipc_perm.

If we have an array, which is an array of structure pointers struct ipc_perm* array[], then any of these three mechanisms are stored in this structure. As long as we take out any element in the array, perform type conversion on this pointer, and convert it into the corresponding struct shmid_ds *, struct msqid_ds *, struct semid_ds *, and then we can access the corresponding kernel data structure and save the information.

If we regard struct ipc_perm as a parent class, struct shmid_ds, struct msqid_ds, and struct semid_ds as subclasses. We have achieved access to the corresponding subclass through the parent class pointer, which is polymorphism implemented in C language!