【Linux】系统部分——进程间通信2(共享内存)
21.进程间通信——共享内存
文章目录
- 21.进程间通信——共享内存
- 共享内存的简单原理
- 共享内存的数据结构
- 共享内存的系统调用
- shmget——创建
- shmat——共享内存与进程关联
- shmdt——共享内存与进程去关联
- shmctl——控制、删除
- 共享内存的管理指令
- 使用举例
- 补充:使用系统调用获取时间
共享内存区是最快的IPC形式。⼀旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执⾏进⼊内核的系统调⽤来传递彼此的数据
共享内存的简单原理
在前面学习动静态库的时候,我们已经了解了动态库是如何加载并与进程关联起来的:如果是动态库链接形成的可执行文件,在操作系统运行之后变成进程,在内存中有自己的代码和数据,动态库作为这个可执行程序的一部分,也会加载到内存当中。通过页表把动态库在内存中的地址映射到mm_struct
中的共享区。而不同的进程可以通过相同的方法,将同一个动态库加载到不同的进程中,这个动态库可以通过虚拟地址共享。共像内存的原理也很相似,利用某些系统调用,在物理内存中开辟一段空间,并将这个空间关联在不同进程的虚拟地址空间的共享区,这样就实现了PIC的根本:让不同进程看到同一份资源。
在使用的时候,操作系统先在物理内存开辟空间,之后通过进程的页表将物理空间映射到进程的虚拟地址空间的共享区。因此在不使用共享内存之后,需要先去关联,再释放物理内存空间。
共享内存的数据结构
struct shmid_ds
{struct ipc_perm shm_perm; /* operation perms */int shm_segsz; /* size of segment (bytes) */__kernel_time_t shm_atime; /* last attach time */__kernel_time_t shm_dtime; /* last detach time */__kernel_time_t shm_ctime; /* last change time */__kernel_ipc_pid_t shm_cpid; /* pid of creator */__kernel_ipc_pid_t shm_lpid; /* pid of last operator */unsigned short shm_nattch; /* no. of current attaches */unsigned short shm_unused; /* compatibility */void shm_unused2; /* ditto - used by DIPC */void shm_unused3; /* unused */
}
共享内存的系统调用
shmget——创建
功能:⽤来创建共享内存
原型:
int shmget(key_t key, size_t size, int shmflg);
参数:
key: 这个共享内存段名字
size: 共享内存⼤⼩
shmflg: 由九个权限标志构成,它们的⽤法和创建⽂件时使⽤的mode模式标志是⼀样的
- 取值为IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回。这个选项多用于需要使用其他进程调用 系统调用 创建的共享内存,用的是别人创建的,只是获得shmid值
- 取值为IPC_CREAT | IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,出错返回。这个选项多用于需要创建新的共享内存的进程中,保证 使用的是当前创建的共享内存
- 这个形参可以在其他选项之后
|
共享内存的权限,如果不设置,共享内存的权限默认为0,无法使用返回值:成功返回⼀个⾮负整数,即该共享内存段的标识码;失败返回-1
注意:
-
key
值由用户指定,标识共享内存的唯一性,为了在一定程度上避免用户指定的值与已有的标识冲突,可以使用ftok
函数通过 公共路径 + 唯一标识形成标识。这个公共路径会被看成一个字符串(不一定要是指定的路径,可以任意),唯一标识仍然由用户指定,之后通过算法形成新的唯一标识。(具体使用方法用man
查看)key_t ftok(const char *pathname, int proj_id);
-
shmflg中两种标志的用法:
- 如果我们单独使用IPC_CREAT,如果shm不存在,创建。如果存在,获取它,并返回——>保证调用进程能拿到共享内存
- 如果shm不存在,就创建它。如果存在,出错返回——>我不拿老的共享内存,只要成功,一定是新的共享内存!
-
创建共享内存实际上是由操作系统创建的,只不过是用进程来调用系统函数。所以在进程退出的时候,由进程调用系统调用创建的共享内存不会随进程释放(区分
malloc
),只能由用户让OS释放,或者OS重启
shmat——共享内存与进程关联
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
- shmid: 共享内存标识
- shmaddr:指定连接的地址,一般不用,填
nullptr
- shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY,一般也不用,填0
返回值:成功返回⼀个指针,指向共享内存第⼀个节;失败返回-1
注意,返回的是进程的虚拟地址,不是共享内存的物理内存地址,所以,即使两个进程使用的是同一个共享内存,通过
shmat
得到的虚拟内存地址可能也是不一样的。因为不同进程把共享内存的物理地址通过页表映射到进程的虚拟地址空间的位置不一定相同
shmdt——共享内存与进程去关联
功能:将共享内存段与当前进程脱离
原型
- int shmdt(const void *shmaddr);
参数
- shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl——控制、删除
功能:⽤于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
- shmid:由shmget返回的共享内存标识码
- cmd:将要采取的动作(有三个可取值)
- IPC STAT 把shmidds结构中的数据设置为共享内存的当前关联值
- IPC SET 在进程有足够权限的前提下,把共享内存的当前关联值设置为shmidds数据结构中给出的值
- IPC RMID 删除共享内存段
- buf:指向⼀个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
共享内存的管理指令
-
ipcs
命令ipcs
(Inter-Process Communication Status) 命令用于报告 Linux 系统中进程间通信 (IPC) 设施的状态。它是一个非常重要的系统监视和调试工具,允许用户查看当前正在使用的 IPC 资源详情。ipcs
主要显示以下三种类型的 IPC 资源:消息队列、共享内存段 、信号量数组- 主要选项:
-q
:仅显示消息队列(Message Queues)信息。-m
:仅显示共享内存段(Shared Memory Segments)信息。-s
:仅显示信号量(Semaphores)信息-p
:显示进程 ID 信息,即创建该资源的进程 PID 和最后访问它的进程 PID。-c
:显示创建者信息(用户和组)
$ ipcs -m------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 65536 user 600 524288 2 dest
0x00000000 98305 user 600 4194304 2 dest
0x611f053c 131074 www-data 600 524288 1
- key: IPC 键值(十六进制),进程通过此 key 来请求资源。
0x00000000
通常表示该段是私有的。 - shmid: 共享内存段的唯一标识 ID。
- owner: 拥有该段的用户。
- perms: 权限(八进制格式,类似于文件权限),如
600
表示用户可读可写。共享内存也有权限,shmget的shmflg这个形参可以在其他选项之后|
共享内存的权限 - bytes: 共享内存段的大小(字节)。
- nattch: 当前附加(attach) 到该内存段的进程数量。当此数为 0 时,内核可能会销毁该段。
- status: 状态标志。
dest
表示当最后一个进程分离后,该段将被标记为销毁。
-
ipcrm -m
删除共享内存段在使用时需要指定需要删除的
shmid
也就是shmget
的返回结果,并不是用户指定的key
值- shmid:只给用户用的一个标识shm的标识符
- key:只作为内核中区分shm唯一性的标识符,不作为用户管理shm的id值
使用举例
#pragma once#include <iostream>
#include <string>
#include <cstdio>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdalign.h>
#include <unistd.h>const std::string gpath = "/home/whb/code";
int gprojId = 0x6666;
// 操作系统,申请空间,是按照块为单位的:4KB,1KB, 2KB, 4MB
int gshmsize = 4096;
mode_t gmode = 0600;std::string ToHex(key_t k)//将k转换为16进制并储存在字符串中
{char buffer[64];snprintf(buffer, sizeof(buffer), "0x%x", k);return buffer;
}class ShareMemory
{
private:void CreateShmHelper(int shmflg) //共享内存创建辅助函数{_key = ::ftok(gpath.c_str(), gprojId); //形成keyif (_key < 0){std::cerr << "ftok error" << std::endl;return;}_shmid = ::shmget(_key, gshmsize, shmflg); //创建共享内存并返回shmidif (_shmid < 0){std::cerr << "shmget error" << std::endl;return;}std::cout << "shmid: " << _shmid << std::endl;}public:ShareMemory():_shmid(-1),_key(0),_addr(nullptr) //构造{}~ShareMemory() {} //析构void CreateShm()//共享内存创建函数(或者说如果共享内存不存在,则创建)这个选项多用于需要创建新的共享内存的进程中,保证使用的是当前创建的共享内存{if(_shmid == -1)CreateShmHelper(IPC_CREAT | IPC_EXCL | gmode);}void GetShm()//这个选项多用于需要使用其他进程调用 系统调用 创建的共享内存,用的是别人创建的,只是获得shmid值{CreateShmHelper(IPC_CREAT);}void AttachShm()//关联共享内存{_addr = shmat(_shmid, nullptr, 0); // 为什么会失败???——权限if ((long long)_addr == -1){std::cout << "attach error" << std::endl;}}void DetachShm()//去关联{if(_addr != nullptr)::shmdt(_addr);std::cout << "detach done: " << std::endl;}void DeleteShm()//删除共享内存——一般谁创建则谁删除{shmctl(_shmid, IPC_RMID, nullptr);}void *GetAddr(){return _addr;}private:int _shmid;key_t _key;void *_addr;
};//临时
ShareMemory shm;
//样本数据
struct data
{char status[32];char lasttime[48];char image[4000];
};
使用:
//sever.cc —— 创建和读取共享内存
#include <iostream>
#include <unistd.h>
#include <string.h>
#include "ShareMemory.hpp"
#include "Time.hpp"
#include "Fifo.hpp"int main()
{std::cout << "time: " << GetCurrTime() << std::endl;shm.CreateShm();shm.AttachShm();struct data *image = (struct data *)shm.GetAddr();// 在这里进行IPCwhile (true){printf("status: %s\n", image->status);printf("lasttime: %s\n", image->lasttime);printf("image: %s\n", image->image);strcpy(image->status, "过期");//读取之后过期处理}// sleep(10);shm.DetachShm();shm.DeleteShm();return 0;
}//client.cc —— 使用和写入共享内存
#include <iostream>
#include <string.h>
#include "ShareMemory.hpp"
#include "Time.hpp"
#include "Fifo.hpp"int main()
{shm.GetShm();shm.AttachShm();gpipe.OpenPipeForWrite();struct data *image = (struct data *)shm.GetAddr();// 在这里进行IPCchar ch = 'A';while(ch <= 'Z'){strcpy(image->status, "最新");strcpy(image->lasttime, GetCurrTime().c_str());strcpy(image->image, "xxxxxxxxxxxxxxxxxxxx");sleep(3);}shm.DetachShm();return 0;
}
说明:
- 共享内存的使用与前面的管道有很大区别,管道使用时如果写端没有写入数据,读端就会阻塞,不会继续读数据,防止读到重复的内容。但是共享内存没有这种保护机制,因此会出现数据不一致的问题
- 共享内存,保护机制,需要由用户自己完成保护–信号量(后面学习)
补充:使用系统调用获取时间
#pragma once#include <iostream>
#include <string>
#include <ctime>std::string GetCurrTime()
{time_t t = time(nullptr);struct tm *curr = ::localtime(&t);char currtime[32];snprintf(currtime, sizeof(currtime), "%d-%d-%d %d:%d:%d",curr->tm_year + 1900,curr->tm_mon + 1,curr->tm_mday,curr->tm_hour,curr->tm_min,curr->tm_sec);return currtime;
}