共享内存区是最快的IPC形式,一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,不再执行进入内核的系统调用来传递彼此的数据
原理
系统在内存中申请一段空间,通过页表映射挂接到进程的共享区,共享区返回首地址。两个进程通信时直接访问这个首地址,实际上读取的是同一段空间。申请的这段空间操作系统会管理起来。这个过程须由操作系统来做,这样两个进程才能保持独立。如果要释放共享内存,先去关联,再释放共享内存
共享内存数据结构
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模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
选项:
IPC_CREAT:不存在就创建,存在获取并返回
IPC_EXCL:存在就出错返回,不单独使用
共享内存也需要权限,在flag参数加入
大小问题
如果内存4k一个基本单位访问,申请4097,虽然查询显示是4097,但实际上申请了4096*2的大小,只给4907空间使用
生成key的函数
这是一套算法,数值计算返回key值
挂接函数 shmat
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
shmaddr为NULL,自动选择一个地址
shmaddr不为NULL,且shmflg无SHM_RND标记,则以shmaddr为连接地址
shmaddr不为NULL,设置了SHM_RND标记,连接的地址自动向下调整为SHMLBA的整数倍。公式:shmaddr(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
需要传的只有第一个参数,剩下两个系统可以自动生成。权限虽然申请时设置了,但挂接的过程也可以再一次设置
去关联 shmdt
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
控制共享内存 shmctl
功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
共享内存的管理属性
key
1.key是一个数字,在内核中具有唯一性,能让不同的进程进行唯一性标识
2.第一个进程可以通过key创建共享,第二个之后只要拿着同一个key就可以看到同一个共享内存
3.可以在共享内存的描述对象中
4.第一次创建的key,通过唯一的路径等参数计算获取,通信双方提前约定好参数
key和shmid
key是操作系统内标定唯一性,shmid只在你的进程内,表示资源的唯一性,shmid和文件标识符不一样,没有用一套标准,有自己的算法。不符合一切皆文件,所以用的不多。共享内存的生命周期随内核,用户不主动关闭会一直存在,除非内核重启(用户释放)
共享内存命令
查询
ipcs -m
删除
ipcrm -m [shmid]
用户层的操作都是用shmid,key是内核标识的。命令行也是用户层
测试
创建一个共享内存,一个进程创建,一个获取
common.hpp
#ifndef __COMMON__
#define __COMMON__#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "log.hpp"//using namespace std;
const char *pathname = "./dir";
const int proj = 123;Log log;
key_t getkey()
{key_t key = ftok(pathname, proj);if (key < 0){log.logmessage(fatal, "ftok error: %s", strerror(errno));}printf("key:%x\n", key);return key;
}int shm(int flag)
{key_t key = getkey();int shmid = shmget(key, 4096, flag);if (shmid < 0){log.logmessage(fatal, "shmget error: %s", strerror(errno));}printf("shmid:%x\n", shmid);return shmid;
}
//创建
int creatshm()
{return shm(IPC_CREAT | IPC_EXCL | 0666);
}//获取
int getshm()
{return shm(IPC_CREAT);
}
#endif
processA.cc
#include "common.hpp"int main()
{creatshm();return 0;
}
processB.cc
#include "common.hpp"int main()
{getshm();return 0;
}
查询共享内存
挂接之后查看nattch
解除挂接然后释放,attch会又变为0,然后消失
processA.cc
#include "common.hpp"int main()
{Log log;int shmid = creatshm();char* addr = (char*)shmat(shmid, 0, 0);if (addr < 0){log.logmessage(fatal, "addr error: %s", strerror(errno));}sleep(5);int ret = shmdt(addr);if (addr < 0){log.logmessage(fatal, "unattach error: %s", strerror(errno));}shmctl(shmid, IPC_RMID, 0);log.logmessage(debug, "free result: %s", strerror(errno));sleep(20);return 0;
}
让两个进程往共享内存写入数据通信,a读,b写
processB.cc
#include "common.hpp"int main()
{int shmid = getshm();char* addr = (char*)shmat(shmid, 0, 0);while (true){cout << "please enter:";fgets(addr, 1024, stdin);}int ret = shmdt(addr);return 0;
}
共享内存的特性
1.共享内存没有同步互斥之类的保护机制
2.共享内存是所有进程通信中速度最快的,因为是直接往内存里写入内容
3.共享内存内部的数据,由用户自己维护
共享内存的结构
第一个属性是共享内存的许可结构,第二个是大小
共享内存perm结构
获取属性,打印,STAT可以从内核中拷贝数据结构
shmid_ds ds;shmctl(shmid, IPC_STAT, &ds);cout << "size: " << ds.shm_segsz << endl;cout << "attach: " << ds.shm_nattch << endl;cout << "key: 0x" << hex << ds.shm_perm.__key << endl;
共享内存加管道
先创建管道,两个进程打开管道,通过向管道输入内容来判断写入方是否写入。当写入了内容,就通过管道发送信号,读取方接收到文件尾就会退出,不然就会阻塞
common.hpp
#ifndef __COMMON__
#define __COMMON__#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <unistd.h>
#include <cstdlib>
#include <fcntl.h>
#include "log.hpp"#define FIFO_FILE "./fifo"
#define MODE 0664enum {FIFO_CREAT_ERR = 1,FIFO_DELET_ERR,FIFO_OPEN_ERR
};//using namespace std;
const char *pathname = "./dir";
const int proj = 123;Log log;
key_t getkey()
{key_t key = ftok(pathname, proj);if (key < 0){log.logmessage(fatal, "ftok error: %s", strerror(errno));}printf("key:%x\n", key);return key;
}int shm(int flag)
{key_t key = getkey();int shmid = shmget(key, 4096, flag);if (shmid < 0){log.logmessage(fatal, "shmget error: %s", strerror(errno));}printf("shmid:%x\n", shmid);return shmid;
}
//创建
int creatshm()
{return shm(IPC_CREAT | IPC_EXCL | 0666);
}//获取
int getshm()
{return shm(IPC_CREAT);
}class Init
{
public:Init(){// 创建管道int n = mkfifo(FIFO_FILE, MODE);if (n == -1){perror("server creat");exit(FIFO_CREAT_ERR);}}~Init(){// 删除管道int n = unlink(FIFO_FILE);if (n == -1){perror("server unlink");exit(FIFO_DELET_ERR);}}
};#endif
processA.cc
#include "common.hpp"int main()
{Log log;Init fifo;int shmid = creatshm();char *addr = (char *)shmat(shmid, 0, 0);//打开管道int fd = open(FIFO_FILE, O_RDONLY);shmid_ds ds;while (true){// cout << "processB: " << addr;// shmctl(shmid, IPC_STAT, &ds);// cout << "size: " << ds.shm_segsz << endl;// cout << "attach: " << ds.shm_nattch << endl;// cout << "key: 0x" << hex << ds.shm_perm.__key << endl;char c = 0;ssize_t x = read(fd, &c, 1);if (x == 0){break;}cout << "processB:" << addr;sleep(1);}int ret = shmdt(addr);shmctl(shmid, IPC_RMID, 0); return 0;
}
processB.cc
#include "common.hpp"int main()
{int shmid = getshm();char* addr = (char*)shmat(shmid, 0, 0);int fd = open(FIFO_FILE, O_WRONLY);while (true){cout << "please enter:";fgets(addr, 1024, stdin);//通知对方write(fd, "c", 1);}int ret = shmdt(addr);return 0;
}