一、背景
在Linux应用开发中经常会采用多进程+多线程的模式来进行开发,那么进程之间的通信就非常重要,接下来要介绍的就是进程间通信方式之一的共享内存。共享内存相比较其他的IPC方式最明显的优点就是速度快。各个进程间共享一块的内存空间,数据交互只需要一次拷贝即可,效率非常高。但是由于Linux提供的的共享内存机制没有提供同步互斥的机制,所以在使用的共享内存的时候需要用户自己来实现消息的同步互斥机制,保证共享内存间的数据不会错乱。最常用的就是“信号量+共享内存” 的机制用来保证通讯数据的正确定。
二、基本实现原理
共享内存顾名思义就是所有需要通信的进程都公用一段公共内存空间。但是我们都知道Linux系统为保证内存安全,为每一个进程都分配的一段虚拟地址来使用,每个进程映射出来的虚拟地址都不一样,应用程序是无法直接操作物理内存的,既然无法访问那应该怎么让不同进程共享一段内存空间呢?答案是通过key值映射的方式,来保证每个进程都能映射到同一段物理空间,虽然虚拟地址空间是不固定的,但是这段虚拟地址空间映射到的物理内存是固定的。这样每个进程只要通过同一个key值去申请共享内存,然后将这段物理内存映射到进程的虚拟地址空间,就能实现不同进程访问同一段物理内存空间了。基本原理如下图所示:

三、API接口解析
- key_t ftok(const char *pathname, int proj_id); 该接口是生成key值的接口,pathname是一个已经存在的路径,proj_id是一个非0的正整数,pathname和proj_id的组合来生成一个唯一的key值。不同进程可以固定相同的pathname和不同的proj_id来生成不同的key,也可以通过通过这种组合来获取对方进程的key值。
- int shmget(key_t key, size_t size, int shmflg); 这个接口来创建或者获取已经存在的共享内存ID。第一个参数就是上面接口生成的key值(也可以直接传一个值,最好是大于1024,区别系统已经存在的共享内存区)。第二个参数size就是需要申请的共享内存的大小,最好是页的整数倍,页的大小一般是4096个字节。最后一个参数shmflag传一个标志位和权限位的掩码组合。 如果是创建共享内存id则传 IPC_CREAT|IPC_EXCL|0666 的组合,如果是要获取已经存在共享内存id直接传个权限位0666即可
- void *shmat(int shmid, const void *shmaddr, int shmflg); 将shmid对应的共享内存映射到虚拟内存。第一个参数就是共享内存id;后面两个参数一般直接传 0 即可,表示在页表中找一块未使用过的页对齐地址来使用。
- int shmdt(const void *shmaddr); 将映射好的内存地址和物理共享内存剥离。
- int shmctl(int shmid, int cmd, struct shmid_ds *buf); 可以使用该接口来释放已经申请的shmid对应的共享内存。一般使用IPC_RMID, shmctl(shmid, IPC_RMID, 0); 更多的使用方法可以查阅man帮助手册(man 2 shmctl)
有了以上五个API接口就能用共享内存机制来进行进程间通信了。
四、案例分享
下面就是一个简单的案例来说明下共享内存的使用,分为server和client两个独立的进程,代码实现如下:
server端: 生成由 “/tmp” 和 0x123456组成的key创建一个共享内存ID,并将共享内存映射成能访问的虚拟内存,拷贝数据到共享内存中
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
using namespace std;
int main()
{
cout << "This is share memory server" << endl;
key_t key = ftok("/tmp", 0x123456); //生成key值
if(key < 0)
{
return -1;
}
cout << "key: " << key << endl;
int shmid = shmget(key, 1024, IPC_CREAT | IPC_EXCL | 0666); //创建一个共享内存ID
if(shmid < 0)
{
cout << "share memory create or get failed!" << endl;
return -1;
}
cout << "shmid: " << shmid << endl;
void* shareMemPtr = shmat(shmid, 0, 0); //将物理内存映射到虚拟内存
if(shareMemPtr == nullptr)
{
cout << "share memory attach failed!" << endl;
return -1;
}
cout << "shareMemPtr: " << shareMemPtr << endl;
memcpy(shareMemPtr, "123456", 10);
while (1)
{
sleep(1);
}
shmdt(shareMemPtr); //将虚拟地址和物理内存之间的映射剥离
shmctl(shmid, IPC_RMID, 0); //删除已经申请的共享内存
return 0;
}
client端:获取和服务端相同key值的共享内存id,并将对应的物理内存映射到虚拟地址,来读取共享内存中的数据打印出来。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
using namespace std;
int main()
{
cout << "This is share memory client" << endl;
key_t key = ftok("/tmp", 0x123456); //生成key值
if(key < 0)
{
return -1;
}
cout << "key: " << key << endl;
int shmid = shmget(key, 1024, 0666); //获取一个共享内存ID
if(shmid < 0)
{
cout << "share memory create or get failed!" << endl;
return -1;
}
cout << "shmid: " << shmid << endl;
void* shareMemPtr = shmat(shmid, 0, 0); //将物理内存映射到虚拟内存
if(shareMemPtr == nullptr)
{
cout << "share memory attach failed!" << endl;
return -1;
}
cout << "shareMemPtr: " << shareMemPtr << endl;
char recvBuff[1024] = {0};
memcpy(recvBuff, shareMemPtr, 10);
printf("recvData:%s\n", recvBuff);
shmdt(shareMemPtr); //将虚拟地址和物理内存之间的映射剥离
shmctl(shmid, IPC_RMID, 0); //删除已经申请的共享内存
return 0;
}
server的打印如下:

client端的打印如下:

通过日志信息可以看到,两个进程的key和shmid是一样的,但是映射出来的虚拟地址shareMemPtr是不一样的。同时客户端收到的数据就是服务端写进去的数据。
我们还可以通过 ipcs -m 的命令来查看系统中共享内存的详情。能帮助我们来调试共享内存生成的状态。
五、总结
以上就是Linux共享内存的基本原理和使用方法的简单分享,这里只是介绍如何简单的使用共享内存进行通信,共享内存最重要的是消息同步机制,以及消息体的格式定义,如何确保消息准确快速的传送出去。今天太晚了,后续有时间继续肝吧..........

1435

被折叠的 条评论
为什么被折叠?



