共享内存shm
- 什么是共享内存shm
- 共享内存的特点
- 关键函数
- ftok
- shmget
- shmat
- shmdt
- shmctl
- 代码示例
什么是共享内存shm
进程间通信的前提:必须让不同的进程看到同一份资源,并且这个资源是OS提供的
而共享内存(Share memory)就是在内核共享内存区找一块物理内存空间,并允许多个进程共享同一块内存区域的机制
不同于消息队列或管道,共享内存允许进程直接读写共享区域,因此具有较高的性能。通常情况下,共享内存用于需要频繁交换数据的场景,例如图形处理、数据库管理等。
原理图:
图片来源于bing搜索
共享内存的特点
- 共享内存的通信方式,不会提供同步机制,共享内存是直接裸露给所有使用者的,因此在使用共享内存的时候一定要注意安全问题
- 共享内存是所有进程通信中速度最快的,因为进程间的这块内存是共享的,因此这块内存中的数据是不需要进行拷贝等操作,每个进程可以直接访问
- 共享内存一般是可以提供较大的空间的
关键函数
ftok
ftok函数可以理解为file to key ,在linux下,一切皆文件,因此共享内存也可以被看成是文件,这里的file就指的是共享的那块物理内存,而ftok 函数用于生成一个与给定路径名和项目标识符相关联的键值(key),用于后续共享内存的创建或连接。
通常在创建共享内存之前,需要使用 ftok 函数生成一个键值,以确保不同进程能够访问同一块共享内存。
shmget
shmget 函数用于创建共享内存段或获取现有共享内存段的标识符。shmget 函数接受三个参数:键值(key)、大小(size)和标志(flags)。键值通常由 ftok 函数生成,大小是要创建的共享内存段的大小一般为4096的整数倍,标志用于指定创建共享内存段的权限和行为。
标志位flags一般会用到两个参数 IPC_CREATE 和 IPC_EXCL,一般情况下 IPC_EXCL不单独使用 IPC_CREATE是创建否则获取.
通过 shmget 函数可以创建新的共享内存段,或者获取已经存在的共享内存段的标识符。
shmat
shmat 函数用于将共享内存段连接到调用进程的地址空间,从而可以访问共享内存中的数据。
shmat 函数接受三个参数:共享内存标识符(shmid)、地址(shmaddr)和标志(flags)。共享内存标识符是由 shmget 函数返回的共享内存段标识符,地址是要将共享内存映射到的地址,如果为 NULL,则由系统自动选择,标志用于指定连接共享内存的行为。
最终返回一个指向共享内存的指针,用于后续对共享内存的访问。
通过 shmat 函数将共享内存连接到进程的地址空间,使得进程可以直接访问共享内存中的数据。
shmdt
shmdt 函数用于从调用进程的地址空间分离共享内存段,停止对共享内存的访问。
shmdt 函数接受一个参数,即指向共享内存的指针。
通过 shmdt 函数可以停止对共享内存的访问,并释放与之相关的资源。
shmctl
shmctl 函数用于对共享内存进行控制操作,如获取信息、设置权限、删除共享内存等。
shmctl 函数接受三个参数:共享内存标识符(shmid)、命令(cmd)和缓冲区(buf)。共享内存标识符是由 shmget 函数返回的共享内存段标识符,命令用于指定要执行的控制操作,缓冲区用于存放返回的信息或接收需要设置的参数。
通过 shmctl 函数可以对共享内存进行各种控制操作,如获取信息、设置权限、删除共享内存等。
代码示例
在下面的代码示例中,我创建了两个.cc文件 server.cc 和 client.cc ,在这两个cpp文件身生成的进程中有一段共享内存来传递信息,并插入命名管道使之具有更明显的分工效果
server.cc
#include<iostream>
#include<sys/ipc.h> //Inter-Process Communication
#include<sys/shm.h>
#include<cstring>
#include"comm.hpp"
int main()
{
//创建管道
bool r = Makefifo();
if(!r) return 1;
//创建唯一的k用于两进程找到这段共享内存的标识 调用函数 ftok
key_t k = GetKey();
// 创建共享内存
int shmid = CreatShm(k);
//挂载共享内存
std::cout<<"开始将shm映射到我的进程中"<<std::endl;
char* s = (char*)shmat(shmid,nullptr,0);
//根据客户端输入内容来从共享内存中获取内容
int fd = open(filename.c_str(),O_RDONLY);
while(true)
{
int code = 0;
ssize_t n = read(fd,&code,sizeof(code));
if(n > 0)
{
std::cout<<"共享内存:"<< s << std::endl;
}
else if(n == 0)
{
break;
}
}
//将shm从进程中移除
shmdt(s);
std::cout<<"将shm从进程中移除"<<std::endl;
//删除shm
shmctl(shmid,IPC_RMID,nullptr);
std::cout<<"将shm从os中删除"<<std::endl;
return 0;
}
client.cc
#include<iostream>
#include<sys/ipc.h> //Inter-Process Communication
#include<sys/shm.h>
#include<cstring>
#include"comm.hpp"
int main()
{ //获取k
key_t k = GetKey();
//获取shm
int shmid = GetShm(k);
//挂载共享内存到进程
char* s = (char*)shmat(shmid,nullptr,0);
//获取管道fd
int wfd = open(filename.c_str(),O_WRONLY);
//对共享内存和管道进行输入,若写入共享内存,在通知管道可读
char c = 'a';
for(;c <= 'z';c++)
{
s[c - 'a'] = c;
int code = 1;
write(wfd,&code,sizeof(code));
std::cout<<"write " << c << " done" << std::endl;
sleep(1);
}
//删除管道
unlink(filename.c_str());
//将shm从进程中移除
shmdt(s);
std::cout<<"将shm从进程中移除"<<std::endl;
//因文共享内存机制的原因,两个进程是不会因此而同步,对两个进程说就是一个公共空间,里面的内容谁想用就用,想改就可以个改
return 0;
}
comm.hpp
#pragma once
#include<iostream>
#include<string>
#include<fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<unistd.h>
const std::string pathname = "/home/cris/vscodef";
const int proj_id = 0x11223344;
const std::string filename = "fifo";
const int sh_size = 4096; //共享内存的大小最好设置成4096的倍数
key_t GetKey()
{
key_t k = ftok(pathname.c_str(),proj_id);
if(k < 0)
{
std::cerr << "errno:" << errno << ",errstring" << strerror(errno) <<std::endl;
exit(1);
}
std::cout<< "key:" << k << std::endl;
return k;
}
int CreatShmHelper(key_t key , int flag)
{
//共享内存的生命周期是随内核的 查看共享内存:ipcs -m 删除: ipcrm -m shmid
// 调用函数shmget 此函数技能创建又能获取
//一般情况下 IPC_EXCL不单独使用 IPC_CREATE是创建否则获取
/// int shmget(key_t key,size_t size ,int shmflg)
int shmid = shmget(key,sh_size,flag);
if(shmid < 0)
{
std::cerr << "errno:" << errno << ",errstring" << strerror(errno) <<std::endl;
exit(2);
}
std::cout<< "shmid:" << shmid << std::endl;
return shmid;
}
int CreatShm(key_t key)
{
return CreatShmHelper(key,IPC_CREAT|IPC_EXCL|0644);
}
int GetShm(key_t key)
{
return CreatShmHelper(key,IPC_CREAT);
}
bool Makefifo()
{
int n = mkfifo(filename.c_str(),0666);
if(n < 0)
{
std::cerr << "errno" << errno << ",errstring" << strerror(errno) << std::endl;
return false;
}
return true;
}
Makefile
.PHONY:all
all:client server
client:client.cc
g++ -o $@ $^ -std=c++11
server:server.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm -f server client fifo
运行效果如图: