Learning System Programming No.21 [Shared memory for inter-process communication]

introduction:

Beijing time: 2023/4/16/21:53, I just posted the new article, and I came back after the class meeting. I could have posted the last blog yesterday. After playing badminton yesterday afternoon, I didn’t even eat , lying in bed, ready to go to bed, set an alarm clock at 19:30, and planned to get up and finish writing the rest of the blog, but suddenly received a bad news, which made me unable to adjust my mentality well, but in At present, it seems that everything is still within the controllable range, otherwise I would not be in this codeword, feeling..., which caused a bad night last night, the good thing is that I made up some today, so everything is still there On track, So, keep going! In this blog, we will really formally learn about shared memory
insert image description here

Review Named Pipes

In the last blog, we learned about named pipes and sub-process recycling, and learned that inter-process communication is not limited to "血缘关系"between processes. Even two unrelated processes also have communication scenarios, and we You know, the knowledge about named pipes we learned in the last blog is a method for building communication between two unrelated processes. Using the knowledge related to named pipes, we can make two unrelated processes You can also have communication skills! This is the essence of the biggest difference between anonymous pipes and named pipes. Anonymous pipes can only allow "血缘关系"processes with a relationship to complete inter-process communication, while named pipes can not only allow blood-related inter-process communication, but also allow completely unrelated processes. Communication between anonymous pipes and named pipes, but in addition to the most essential difference at this time, there are also certain differences in usage. Anonymous pipes are created by the pipe function and opened directly, while named pipes are created by the mkfifo function. Finally, the open system interface needs to be used to open , so in addition to the above-mentioned essential differences between fifo(named pipes) and (anonymous pipes), the biggest difference is the difference in the above-mentioned usage methods. After distinguishing these two points, anonymous pipes and named pipes are in pipeIt is essentially the same, it is just a "共享资源"copy, a memory-level file used to store temporary data

Shared memory

After understanding the above knowledge, we now know that after using named pipes to build the corresponding environment between unconnected processes, it is also possible to communicate between processes at this time. The essence is to let two processes see the same It’s just a shared resource , so next, let’s learn another way to achieve communication between two unconnected processes:共享内存

Fundamental

As shown below:

insert image description here

code writing

After understanding the above principles, we can now enter the second stage, which is the code writing stage, and implement the use of shared memory to complete inter-process communication. Of course, if you want to write code yourself to achieve this function, then just The system call interface must be used, because only the system call interface can help us create shared memory in memory and find the corresponding created shared memory. The specific interface is as follows:

The first interface, 创建共享内存接口(shmget):
insert image description here
As can be seen from the instructions in the above figure, the interface header file: #include <sys/ipc.h> #include <sys/shm.h>, the specific usage method int shmget(key_t key, size_t size, int shmflg);can be found through the usage method at this time, the third parameter of the interface is a shmflg, indicating that this interface uses The parameter passing method of the flag bit realizes various behaviors through macro definition, and then judges whether it matches through conditional judgment and bitwise AND, for example, the twoIPC_CREAT和IPC_EXCL macros in the above figure , at this time, the two macros are shared It is related to the creation of memory, which IPC_CREATis allowed to be used alone. The specific behavior is to view the shared memory. If the shared memory does not exist, create a shared memory. If the shared memory exists, obtain the existing shared memory address and return. IPC_EXCL It is not allowed to be used alone, and must be used in conjunction with IPC_CREAT ( IPC_CREAT | IPC_EXCL). The specific behavior is to check the shared memory. If the shared memory does not exist, create a shared memory. If it already exists, an error will be returned immediately. Therefore, if IPC_CREAT 得出结论is used alone, then this The shared memory may be used by others, it is an old shared memory, and if IPC_EXCL cooperates with IPC_CREAT, then the shared memory created at this time must be a brand new shared memory, and the shared memory that has not been used

We have settled the third parameter of the bitmap knowledge we have learned before. Now let’s look at the other two parameters. The second parameter is obviously a parameter indicating the size of the shared memory to be created. There is not much to explain here. We Focus on the first parameter. The first parameter is the key to determine whether the two processes can find the same shared memory. It represents a key number. This key number is used to allow the two processes to find the same shared memory. The key is similar to the number of the shared memory! So how should we get this key value? At this point, the second system call interface is involved, as follows:

The second interface, 给共享内存设置关键字(ftok):

insert image description here

Create a keyword system call interface, ftok(), the specific use method is as above, the header file: #include<sys/types.h> #include<sys/ipc.h>, the specific function of the calling method key_t ftok(const char *pathname, int proj_id);: create a unique IPC(进程间通信)identifier and provide a unique identifier for the created shared memory, and then Let two different processes find the corresponding shared memory through this identifier, and know the main function and usage of the ftok() interface. At this time, it is necessary: ​​in the 注意operating system, it is impossible to have only one pair of processes for inter-process communication. Instead, there are many, many pairs of processes, so at this time, the operating system must perform 管理(describe first, then organize ) the created shared memory, both in terms of efficiency (quickly locating the corresponding shared memory) and management (not messy). struct shmFinally, it becomes the addition, deletion, checking and modification of each structure, so at this time, the structure struct shm stores all the attributes of a shared memory (creation size, creation time, corresponding key value, etc.), so by analogy And analogy, combined with previous knowledge, a file = content + attributes or a process = the kernel data structure (struct task) corresponding to the process + corresponding code and data, then you can know that in the operating system, shared memory = shared The kernel data structure of the memory (struct shm) + the space opened up in the memory

And understand that when using shared memory to build inter-process communication scenarios, it is similar to using named pipes to build inter-process communication. As long as one process creates a named pipe (that is, opens a certain file), another process at this time When you do not need to open the file, you can directly find the corresponding file through the file name and the corresponding file path, and then read or write to the file, so in the same way, to create a shared memory, as long as one of the processes uses shmget()The interface creates a shared memory, so another process does not need to be created at this time, but only needs to ftok()find the corresponding shared memory according to the return value of the interface (comparison in turn), and then write or read data to it. So this is ftok()the main reason why you need to use the interface to generate a key value. The specific principle is shown in the figure below:

insert image description here

Through the above picture and the description of the above text, we can now understand the basic principle of using shared memory for inter-process communication. shmget()The interface is like building a house, and the interface is like the key to open a certain room in the house. , so the essence of inter-process communication is still building an inter-process communication scenario, that is, how to make these two processes see the sameftok()shmget()ftok()"资源"

The specific code is as follows:

common.hpp file:

insert image description here

server.cpp file:

insert image description here

client.cpp file:

insert image description here

As shown in the code above, the shmget() interface is encapsulated in the common.hpp file, one means to use this interface to create shared memory, and the other means to use this interface to obtain shared memory (specifically related to the above-mentioned key values ​​and IPC_CREAT, IPC_EXCL) , the running result is shown in the figure below:

insert image description here

As shown in the figure above, at this time we found that when we use ftok()the interface to create a key value, the two processes have PATHNAMEthe PROJ_IDsame value, so the return value generated by calling the ftok() interface is the same, and one of the processes can Take this key value to create a new shared memory, and another process can also find the corresponding shared memory according to this key value, and at this time we also found that when we execute first and then execute at this time client, serverthe serverWhat tells us is that the shared memory already exists, and if it is executed first server, then executed client, as shown in the following figure:

insert image description here

The reason is very simple. It is essentially the difference between IPC_CREATand . If it is executed first , it means that it is executed first . If it is used alone , if there is a shared memory corresponding to the key value, then the shared memory will be returned. If not, a new one will be created. shared memory, and when | is used together, if there is no shared memory corresponding to the key value, then it creates a new one, and if there is a shared memory corresponding to the key value, then it must create a new shared memory at this time, so At this time, an error is reported directly , saying that the shared memory already exists. Similarly, in another case, execute first and then execute , then because the shared memory corresponding to the key value exists or does not exist, it will return the corresponding key value. Shared memory, so it can run normally at this time . After understanding the above knowledge, we need to understand that when we use the shmget() interface to create shared memory in a certain program, when the program ends, that is, the process Exit. At this time, the life cycle of shared memory will not be released with the end of the process like anonymous pipes and named pipes. It can be seen from the above figure that when the process exits, the process uses the key value to match the shared memory. , it shows that the shared memory already exists, so it is concluded that when a shared memory is created, if the shared memory is not deleted, then the shared memory will always exist unless the computer is restartedIPC_EXCLclientIPC_CREATIPC_CREATIPC_CREATIPC_EXCLIPC_CREAT | IPC_EXCLIPC_CREATIPC_CREAT
clientserver

delete shared memory

So in order to solve the above problem (when the process exits, that is, the communication is completed, the shared memory will not be deleted), there are two methods at this time, as follows:

1. Use the command to delete manually

Function instruction
Show all IPC facilities ipcs -a
Show all message queues Message Queue ipcs -q
show all semaphores ipcs -s
show all shared memory ipcs -m
Display detailed information of IPC facilities ipcs -q -i id
delete a shared memory ipcrm -m shmid

Interested students can refer to this link: Detailed Explanation of IPC Instructions

2. Use the system call interface
Interface: shmctl(control the status of shared memory)

The specific use method is shown in the figure below:
insert image description here
Header file: #include<sys/ipc.h>、#include<sys/shm.h>, specific calling method: int shmctl(int shmid, int cmd, struct shmid_ds *buf);the first parametershmid indicates the shmid of the shared memory you want to control; the second parametercmd indicates the specified operation (command) to be executed, such as : Delete or change the attributes of the shared memory area, for example: IPC_STAT: Get the status information of the shared memory and store it in the shmid_ds structure specified by buf, IPC_SET: Set the status information of the shared memory area, which is contained in buf In the shmid_ds structure pointed to, IPC_RMID: delete the shared memory area from the system; the third parameterbuf : used to transmit or receive shared memory information, is essentially a structure pointer, because 共享内存=内核数据结构(struct shmid_ds)+ 开辟的内存, at this time, buf is a pointer to The pointer to the struct shmid_ds structure, the specific use method is shown in the following code:
insert image description here
the above code indicates that the system call interface is used inside the program to directly delete the corresponding shmidshared memory

How to set permissions for shared memory:

指令:ipcs -mDisplay all shared memory and corresponding information, as shown in the following figure:

insert image description here
As shown in the figure above, at this point we can know that a shared memory has certain permissions (no permissions by default), but if we want it to have certain permissions, we can add it manually, as shown in the following code:
insert image description here

How to associate shared memory

After finishing the above knowledge, creating shared memory and releasing shared memory, we are just one step away from building inter-process communication, that is, how to associate and disassociate processes and shared memory, which means that although we ourselves A shared memory is created, but this shared memory is not necessarily for our own use. If we want to use this shared memory, we must first associate the process with the corresponding shared memory. At this time, we face a problem, that should How to associate the process with the shared memory? At this time, another system call interface is involved: shmat()the specific usage method is shown in the figure below:
insert image description here
shmat()the function used to associate the shared memory with the process address space , the specific meaning of its parameters is as follows:

  1. shmid: the identifier of the shared memory that needs to be associated to the process virtual address space
  2. shmaddr: Specifies the address where the shared memory is associated to the virtual address space. If it is NULL, it means that the system automatically assigns an address
  3. shmflg: Specify the access mode, operation flags and permissions (such as read and write, semaphore, etc.) that a process can access to shared memory in the virtual address space

General:shmat() The function of the function is to associate the shared memory corresponding to shmid with the address space of the current process. After the association, the shared memory can be directly accessed or updated through the address specified by shmaddr. If the shmaddr parameter is NULL, the system will The available space of the address space automatically selects a suitable address to map the shared memory. In shmflg, you can set access rights, create shared memory and other options. Finally, when shmat()the function Returns -1 if an error occurs

注意:Because I know the size of the shared memory, so as long as I know the starting address of the shared memory in the virtual address space at this time, according to the starting address and offset (size), I can get the entire mapping at this time Shared memory on virtual address space

The code implementation is as follows:
insert image description here
So at this time, as long as two different processes call this function, the two different processes can be associated with the corresponding shared memory. At this time, we have completed our goal: let Two different processes see the same copy "资源", so we build an environment for inter-process communication. Finally, inter-process communication can be performed. However , at this time 注意: when the communication between the two processes is completed After that, at this time, the corresponding process should be detached from the corresponding shared memory (that is, disassociated ). At this time, another system call interface is involved: shmdt, the specific usage method is the same as that of shmat, except that the parameters are passed differently. , the specific calling method: int shmdt(const void *shmaddr);So if you want to let a process leave a certain shared memory, you only need to change the page table mapping relationship between the shared memory in the virtual address space of the corresponding process and the shared memory on the physical memory. In short, the function can shmdt()be Access to shared memory is separated from the address space of the current process, and the shared memory can be accessed or deleted by other processes

The specific code is as follows

The specific communication has not started yet, but the environment for inter-process communication has been set up

client:

#include <iostream>
#include "common.hpp"

using namespace std;

int main()
{
    
    
    // 1.获取相同的key值
    key_t k = get_key();
    cout << "server key:" << to_hex(k) << endl;
    // 2.创建共享内存(一个创建,一个获取)
    int shmid = creat_shm(k, shmsize);
    cout << "server shmid:" << shmid << endl;
    // 3.将自己和共享内存关联起来(也就是将共享内存关联到进程的虚拟地址空间中)
    char* start = attch_shm(shmid);
    //具体通信内容

    sleep(5);
    // 4.将自己和共享内存取消关联
    detattch_shm(start);

    // struct shmid_ds ds;
    // int n = shmctl(shmid,IPC_STAT,&ds);
    // if(n == -1)
    // {
    
    
    //    cout << "key:" << to_hex(ds.shm_perm.__key) << endl;
    //    cout << "creater pid:" << ds.shm_cpid << " : " << getpid() << endl;
    // }

    // 要明白shmget()接口的返回值代表的就是对应共享内存的标识符
    // 4.删除共享内存(删一次就够了)
    del_shm(shmid);//使用shmctl共享内存控制接口,删除一个共享内存

    return 0;
}


Server:

#include <iostream>
#include "common.hpp"

using namespace std;

int main()
{
    
    
    // 1.获取到相同的key值
    key_t k = get_key(); // 这个写法不好,写成获取16进制的方法更好
    cout << "client key:" << to_hex(k) << endl;
    // 2.获取共享内存
    int shmid = get_shm(k, shmsize);
    cout << "client shmid:" << shmid << endl;
    // 3.将自己和共享内存关联起来
    char* start = attch_shm(shmid);
    //具体通信内容
    
    // 4.将自己和共享内存取消关联
    detattch_shm(start);

    return 0;
}



Share code:

#ifndef __COMM_HPP__
#define __COMM_HPP__

#include <iostream>
#include <sys/ipc.h> //这个是ftok接口的头文件
#include <sys/shm.h>
#include <sys/types.h>
#include <cerrno>
#include <string.h>
#include <cstdio>
#include <stdlib.h>
#include <unistd.h>
#include <cassert>
#include <sys/stat.h>
#include <sys/shm.h>

using namespace std;

// 并且注意:此时的这个头文件的一个公共头文件,让两个进程文件都可以使用的文件
#define PATHNAME "."  // 此时这个表示的就是定义一个路径,待会供给shmget接口使用(自己指定,只要两个进程相同就行)
#define PROJ_ID 0x666 // 这个也就是自己给的,也是可以随便写的
// 此时只要把这两个参数传递给ftok()接口,此时该接口根据特定的算法就会生成一个唯一的key值
// 这样定义的好处就是可以直接让两个进程使用同一个路径和id
//-----------------------------------------------------------------------------------
const int shmsize = 4096; // 这个表示的就是开辟共享内存的大小(单位:字节)

key_t get_key() // 这个key_t本质就是一个int而已,大佬就是喜欢typedef
{
    
    
    key_t k = ftok(PATHNAME, PROJ_ID);
    if (k == -1)
    {
    
    
        cout << errno << " : " << strerror(errno) << endl;
        exit(1);
    }
    return k; // 代码走到这里表示的就是ftok()函数生成key值成功,此时就可以把这个key值传给别的进程中的shmget()接口使用了
}

// 将key值搞成16进程(用处不大),但是自己要会写
string to_hex(int x)
{
    
    
    char buffer[64];
    snprintf(buffer, sizeof(buffer), "0x%x", x);
    return buffer;
}

//此时下面的写法显得代码冗余,所以改进如下写法
int creat_shm_helper(key_t k,int shmsize,int flag)
{
    
    
    int shmid = shmget(k,shmsize,flag);
    if(shmid == -1)
    {
    
    
        cerr << errno << " : " << strerror(errno) << endl;
        exit(2);//创建失败就退出
    }
    return shmid;
}

int creat_shm(key_t k, int shmsize) // 此时只是将shmget()接口再进行一次封装而已,这个一定要懂
{
    
    
    // int IPC = shmget(k,shmsize,IPC_CREAT | IPC_EXCL);//注意:一定要创建一个全新的出来,因为老的可能别的进程正在使用中
    // if(IPC == -1)
    // {
    
    
    //     cerr << errno << " : " << strerror(errno) << endl;
    //     exit(2);//创建失败就退出
    // }
    // return IPC;//程序来到这里表示的就是成功,返回对应的内存标识符就行(这样进程就可以根据标识符,找到同一共享内存啦!)
    umask(0);
    return creat_shm_helper(k,shmsize,IPC_CREAT | IPC_EXCL | 0666);
}

int get_shm(key_t k, int shmsize)
{
    
    
    // int IPC = shmget(k,shmsize,IPC_CREAT);//此时表示的是获取一个共享内存,只要把对应的key值给给shmget()接口,此时该接口就会依次去和已经存在的共享内存比较,最终把对应key的共享内存放回给给IPC
    // if(IPC == -1)
    // {
    
    
    //     cerr << errno << " : " << strerror(errno) << endl;
    //     exit(2);//创建失败就退出
    // }
    // return IPC;
    return creat_shm_helper(k,shmsize,IPC_CREAT);
}

char* attch_shm(int shmid)
{
    
    
    char* start = (char*)shmat(shmid,NULL,0);//这个接口的返回值是一个void*,和malloc是一样的,malloc的返回值也是一个void*,本质就是放回一个地址给你
    return start;
}

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

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


#endif



Beijing time: 2023/4/20/0:01
insert image description here

Summary: I have finished learning the knowledge of shared memory in inter-process communication, and found that no matter what kind of communication method, in essence, two processes see the same "resource"!

Guess you like

Origin blog.csdn.net/weixin_74004489/article/details/130187008