1.共享内存的概念
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。通信的前提是让两个进程看到同一份资源,信息的交流是建立在这个同一份资源上的,共享内存这种通信方式提供的资源或者平台是物理内存中的一块空间。首先在物理内存中申请一块空间,然后通过页表将这块空间映射到两个需要通信进程的进程地址空间,这样两个进程就可以对这块空间进行数据的写入和读取。
2.共享内存的创建
1.首先在物理地址中开辟好共享内存空间,供进程间通信使用。而且我们知道物理地址中的共享内存可能会有很多个,那么linux就要管理这些内存,管理的方法是先描述再组织,所以除了创建共享内存之外,还要为共享内存创建共享内存的内核数据结构。
2.将共享内存挂接到地址空间,本质上就是给两个进程之间的共享内存和虚拟地址空间之间建立映射,之后就可以开始进程间的数据传输与修改了。
3.用完之后要去掉共享内存和进程地址空间之间的联系,本质上就是修改页表,取消共享内存和虚拟内存的映射关系。
4.释放共享内存,将内存归还给系统。
3.函数接口
创建共享内存函数shmget()
key_t key是用来标识共享内存的,可以保证共享内存本身的唯一性
size_t size 表明需要申请空间的大小,单位是字节
int shmflg 是标志位,有两个参数IPC_CREAT 和IPC_EXCL这里的IPC_CREAT表示如果当前路径下不存在共享内存就创建一个新的共享内存,如果存在共享内存就直接使用该共享内存。后面的IPC_EXCL一般都和IPC_CREAT一起使用,两个标志位一起使用的时候就表示如果该路径下不存在共享内存就创建新的共享内存,如果存在共享内存就会报错,这样就可以保证我们永远使用的都是新创建的共享内存,这样就可以避免使用其他进程创建的共享内存而导致错误。
生成key的函数接口:
pathname路径名
proj_id 项目标识符
创建好key的值就可以创建共享内存了comm.hpp
服务端进程创建和销毁共享内存shm_server.cpp
客户端进程与被创建好的共享内存建立映射关系:shm_clienet.cpp
物理内存开辟好之后就要将物理内存映射到两个进程中
通过该函数就可以在物理内存中创建的共享内存关联到内存地址空间中,这样就可以使用一个指针指向这块内存地址空间了,直接在进程地址空间中修改数据就可以了。这里第一个参数就是上面我们创建的shmid,用来表示该块共享内存,第二个参数表示共享内存的地址,这里我们不需要管直接使用NULL让系统给我们分配就可以了,第三个表示标志位,使用0就可以了。
使用完共享内存后就需要断开挂接
断开挂接后需要释放共享内存将这块空间还给操作系统
4.使用共享内存完成通信:
这里实现的通信大致的步骤就是首先我们创建两个文件,用来表示两个进程,这里可以将两个文件分别取名为:shm_server.cpp和shm_client.cpp,之后在shm_server.cpp中我们首先使用ftok函数创建一个证明共享内存唯一性的key,之后通过该key值和shmget函数创建一个shmid来标识共享内存,然后使用shmat函数将物理地址中开辟的共享内存和进程地址空间进行关联,之后我们让shm_server.cpp读数据,然后通过shmdt函数取消关联和shmctl函数来删除共享内存。在client.c文件中我们也是按照上面的方式使用共享内存,之后我们往共享内存中写入数据,让shm_server.cpp读,之后我们就完成了通过共享内存来实现进程间通信,具体代码如下图:
comm.hpp
#ifndef _COMM_HPP_
#define _COMM_HPP_
#include <iostream>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 0x66
// 共享内存的大小,一般建议是4KB的整数倍
#define MAX_SIZE 4097
key_t getKey()
{
key_t k = ftok(PATHNAME, PROJ_ID); //可以获取同样的一个Key!
if(k < 0)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(1);
}
return k;
}
int getShmHelper(key_t k, int flags)
{
int shmid = shmget(k, MAX_SIZE, flags);
if(shmid < 0)
{
std::cerr << errno << ":" << strerror(errno) << std::endl;
exit(2);
}
return shmid;
}
int getShm(key_t k)
{
return getShmHelper(k, IPC_CREAT);
}
int createShm(key_t k)
{
return getShmHelper(k, IPC_CREAT | IPC_EXCL | 0600);
}
void *attachShm(int shmid)
{
void *mem = shmat(shmid, nullptr, 0); //64系统,8
if((long long)mem == -1L)
{
std::cerr <<"shmat: "<< errno << ":" << strerror(errno) << std::endl;
exit(3);
}
return mem;
}
void detachShm(void *start)
{
if(shmdt(start) == -1)
{
std::cerr <<"shmdt: "<< errno << ":" << strerror(errno) << std::endl;
}
}
void delShm(int shmid)
{
if(shmctl(shmid, IPC_RMID, nullptr) == -1)
{
std::cerr << errno << " : " << strerror(errno) << std::endl;
}
}
#endif
shm_server.cpp
#include "comm.hpp"
#include <unistd.h>
int main()
{
key_t k = getKey();
printf("key: 0x%x\n", k); // key
int shmid = createShm(k);
printf("shmid: %d\n", shmid); //shmid
// sleep(5);
char *start = (char*)attachShm(shmid);
printf("attach success, address start: %p\n", start);
// 使用
while(true)
{
// char buffer[]; read(pipefd, buffer, ...)
printf("client say : %s\n", start);
struct shmid_ds ds;
shmctl(shmid, IPC_STAT, &ds);
printf("获取属性: size: %d, pid: %d, myself: %d, key: 0x%x",\
ds.shm_segsz, ds.shm_cpid, getpid(), ds.shm_perm.__key);
sleep(1);
}
// 去关联
detachShm(start);
sleep(10);
// 删除共享内存
delShm(shmid);
return 0;
}
shm_client.cpp
#include "comm.hpp"
#include <unistd.h>
int main()
{
key_t k = getKey();
printf("key: 0x%x\n", k);
int shmid = getShm(k);
printf("shmid: %d\n", shmid);
char *start = (char*)attachShm(shmid);
printf("attach success, address start: %p\n", start);
const char* message = "hello server, 我是另一个进程,正在和你通信";
pid_t id = getpid();
int cnt = 1;
// char buffer[1024];
while(true)
{
sleep(5);
snprintf(start, MAX_SIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);
}
detachShm(start);
return 0;
}