1 共享内存示意图
共享内存区是最快的 IPC 形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到 内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
2 共享内存数据结构
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 */
};
3 共享内存函数(重点)
- shmget函数:
功能:用来创建共享内存原型 :int shmget(key_t key, size_t size, int shmflg);参数 :key: 这个共享内存段名字 ;size: 共享内存大小 ;shmflg: 由九个权限标志构成,它们的用法和创建文件时使用的 mode 模式标志是一样的 ;返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回 -1。
shmflg一般常用的权限位有两个:IPC_CREAT and IPC_EXCL
- 单独使用IPC_CREAT: 创建一个共享内存,如果共享内存不存在,就创建之,如果已经存在,获取已经存在的共享内存并返回;
- IPC_EXCL不能单独使用,一般都要配合IPC_CREAT;
- IPC_CREAT | IPC_EXCL: 创建一个共享内存,如果共享内存不存在,就创建之, 如果已经存在,则立马出错返回 -- 如果创建成功,对应的shm,一定是最新的.
我们如何获得该函数的第一个参数key呢?
我们可以使用ftok函数:
用返回值接受到生成的key_t类型即可。
- shmat函数:
功能:将共享内存段连接到进程地址空间原型 :void *shmat(int shmid, const void *shmaddr, int shmflg);参数 :shmid: 共享内存标识shmaddr: 指定连接的地址 ,不指定就设置位空;shmflg: 它的两个可能取值是 SHM_RND 和 SHM_RDONLY ,也可以设置为0;返回值:成功返回一个指针,指向共享内存第一个节;失败返回 -1
说明:
shmaddr 为 NULL ,核心自动选择一个地址shmaddr 不为 NULL 且 shmflg 无 SHM_RND 标记,则以 shmaddr 为连接地址。shmaddr 不为 NULL 且 shmflg 设置了 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。
4 用共享内存实现一个客户端与服务端通信的案例
comm.hpp:
#include<iostream>
#include<cerrno>
#include<cstring>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/stat.h>
#include<unistd.h>
using namespace std;#define PATHNAME "."
#define PROJID 6666
key_t getKey()
{key_t key=ftok(PATHNAME,PROJID);if(key==-1){cout<<"error"<<errno<<":"<<strerror(errno)<<endl;return 1;}return key;
}int creatShm(size_t size,int shmflag=0)
{umask(0);int shmid=shmget(getKey(),size,IPC_CREAT | IPC_EXCL | 0666);if(shmid==-1){cout<<"error"<<errno<<":"<<strerror(errno)<<endl;return 1;}return shmid;
}int getShm(size_t size,int shmflag=0)
{int shmid=shmget(getKey(),size,IPC_CREAT );if(shmid==-1){cout<<"error"<<errno<<":"<<strerror(errno)<<endl;return 1;}return shmid;
}char* attachShm(int shmid)
{char* start=(char*)shmat(shmid,nullptr,0);return start;
}void detouchShm(char* start)
{int n=shmdt(start);if(n==-1){cout<<"error"<<errno<<":"<<strerror(errno)<<endl;return ;}
}void delShm(int shmid)
{int n=shmctl(shmid,IPC_RMID,nullptr);if(n==-1){cout<<"error"<<errno<<":"<<strerror(errno)<<endl;return ;}
}
server.cc:
#include<iostream>
#include"comm.hpp"int main()
{//1 创建共享内存int shmid=creatShm(4096);//2 将共享内存与自己关联起来char* start=attachShm(shmid);//3 使用共享内存通信int cnt=0;while(cnt<=15){cout<<start<<endl;sleep(1);++cnt;}//4 解除自己与共享内存的关联detouchShm(start);//5 删除共享内存delShm(shmid);return 0;
}
client.cc:
#include<iostream>
#include"comm.hpp"int main()
{//1 创建共享内存int shmid=getShm(4096);//2 将共享内存与自己关联起来char* start=attachShm(shmid);//3 使用共享内存通信char ch='A';while(ch<='Z'){start[ch-'A']=ch;++ch;start[ch-'A']=0;sleep(1);} //4 解除自己与共享内存的关联detouchShm(start);//客户端不要删掉共享内存return 0;
}
这样我们便能够正常运行我们的程序了,但是假如我们不小心终止了./server进程而导致共享内存没有被删除,我们可以用命令行来删除共享内存。
查看共享内存命令:
ipcs -m
删除指定共享内存:
ipcrm -m 共享内存的id
共享内存是没有互斥同步机制的,也就是说你在写数据的时候我能够直接读取数据,这个跟管道有很大区别,管道是你写完了后我再来读取数据,管道是需要互斥同步机制的,这个我们放到后面多线程再来补充。