共享内存是进程间通信的一种最基本、最快速的机制。共享内存是两个或多个进程共享同一块内存区域,并通过该内存区域实现数据交换的进程间通信机制。通常是由一个进程开辟一块共享内存区域,然后允许多个进程对此区域进行访问。由于不需要使用中间介质,而是数据由内存直接映射到进程空间,因此共享内存是最快速的进程间通信机制。
共享内存的最大不足之处在于,由于多个进程对同一块内存区具有访问的权限,各个进程之间的同步问题显得尤为突出。必须控制同一时刻只有一个进程对共享内存区域写入数据,否则将造成数据的混乱。但是同步控制的问题,可以通过信号量解决。
SYSTEM V共享内存的实现要比POISX早且更完善,更易用,但其缺点是:内存大小在创建时确定,之后不能改变(POISX可以)。
目录
一、System V 共享内存
1、获取key值
2、新建/获取共享内存
3、控制共享内存
4、共享内存映射
5、分离共享内存
二、POSIX 共享内存
1、新建/打开共享内存
2、共享内存映射/解除映射
3、删除共享内存
一、System V 共享内存
1、获取key值
Linux系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
#include <sys/shm.h>
#include <sys/types.h>
key_t ftok( char * fname, int id )
函数参数:
fname:指定的文件路径+文件名,该文件必须是存在而且可以访问
id:随机值,其前8个比特会被用做生成key值
返回值:
成功则返回key_t值,失败返回 -1
2、新建/获取共享内存
用来创建一块共享内存并返回其 id 或者获得一块已经被创建的共享内存的 id
#include <sys/shm.h>
int shmget (key_t key, size_t size, int shmflag);
函数参数:
key:ftok()函数生成的key值
size:共享内存的大小, 单位为字节,如果是新建,此参数必须被指定, 如果是要获取一块创建好的共享内存的 id, 可以将其设置为 0
shmflag:表示共享内存的访问权限,它与文件的访问权限一样,可以与以下值做或操作,以实现特定功能:
• #define IPC_CREAT 00001000 //如果key值不存在则创建
• #define IPC_EXCL 00002000 //如果key存在,则返回失败
• #define IPC_NOWAIT 00004000 //如果需要等待时,直接返回错误
• #define SHM_R 0400 // 可读
• #define SHM_W 0200 // 可写
返回值:
成功返回共享内存的引用标识符,失败返回 -1
3、控制共享内存
用来创建一块共享内存并返回其 id 或者获得一块已经被创建的共享内存的 id
//from /usr/include/sys/shm.h
int shmctl(int shmid,int cmd,struct shmid_ds* buf);
函数参数:
shmid:共享内存标识符
cmd:执行的操作:
• #define IPC_RMID 0 // 删除消息队列
• #define IPC_SET 1 // 设置buf中的消息队列属性
• #define IPC_STAT 2 // 获取消息队列的属性并保存在buf中
• #define IPC_INFO 3 // 获取限制信息
• #define SHM_LOCK 11 // 锁定共享内存,需要root权限
• #define SHM_UNLOCK 12 // 解锁共享内存,需要root权限
buf:cmd为IPC_SET/ IPC_STAT时,表示消息队列属性,其他命令直接设置为NULL即可:
struct shmid_ds {
struct ipc_perm shm_perm; /* 操作权限 */
size_t shm_segsz; /* 大小,单位是字节 */
__kernel_time_t shm_atime; /* 对这段内存最后一次调用shmat的时间 */
__kernel_time_t shm_dtime; /* 最后一次调用shmdt的时间*/
__kernel_time_t shm_ctime; /* 最后一次调用shmctl的时间*/
__kernel_ipc_pid_t shm_cpid; /* 创建者的pid*/
__kernel_ipc_pid_t shm_lpid; /* 最后一次执行shmat或shmdt的进程的pid*/
unsigned short shm_nattch; /*目前关联到次共享内存的进程的数量*/
unsigned short shm_unused; /* 以下为填充 */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
返回值:
成功时的返回值取决于 cmd,失败返回 -1
4、共享内存映射
将共享内存映射到当前进程,映射调用成功后, 会修改 shmid_ds
的部分字段:
- 将 shm_nattach 加一
- 将 shm_lpid 设置为调用进程 pid
- 将 shm_atime 设置为当前时间
//from /usr/include/sys/shm.h
void * shmat(int shmid,void* shmaddr,int flag);
函数参数:
shmid:共享内存标识符
shmaddr:指定共享内存在进程内存地址的映射位置, 为
NULL
, 则表示由系统自行映射,此时 flag 参数无效flag:指定共享内存的访问权限及映射条件:SHM_RDONLY:为只读模式,其他为读写模式
返回值:
成功则返回映射到进程的内存地址,失败返回-1
5、分离共享内存
当一个进程对共享内存区域的访问完成后,可以调用 shmdt 函数使共享内存区域与该进程的地址空间分离。调用成功时修改内核数据结构 shmid_ds
部分字段:
- 将 shm_nattach 减一
- 将 shm_lpid 设置为调用进程的pid
- 将 shm_dtime 设置为当前时间
注意,调用 shmdt 并不会删除共享内存
#include <sys/shm.h>
int shmdt ( const void* shm_addr );
函数参数:
shm_addr:共享内存在进程的映射地址, 即 shmat 返回的值
返回值:
成功则返回0,失败返回-1
二、POSIX 共享内存
1、新建/打开共享内存
打开或新建一个共享内存对象,获取描述符
#include <sys/mman.h>
int shm_open(const char *name, int oflag, mode_t mode);
函数参数:
name:作为共享内存标识,必须使用斜线打头后面跟着一个或多个非斜线字符的名字
oflag:是一个位掩码,具体如下:
mode:是一个位掩码,指定了施加于新共享内存之上的权限。并且与open()一样,mode中的值会与进程的umask取掩码。
返回值:
成功则返回共享内存标识符,失败返回-1
2、共享内存映射/解除映射
在文件I/O章节,我们就已经介绍过mmap()函数,其实它不仅可以把一个文件至调用进程的地址空间 ,也可以把一个共享内存对象映射到调用进程的地址空间。而munmap()则负责解除映射
#include <sys/mman.h>
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
函数参数:
start: 映射内存的首地址,必须为内存页大小(PAGE_SIZE)的整数倍,当然也可以置为NULL,表示由系统分配。
length:将文件中映射到内存部分的长度
prot: 映射的内存权限,但不得与文件的打开权限冲突,该参数有以下选项的组合:
- PORT_READ: 允许读该内存
- PORT_WRITE: 允许写该内存
- PORT_EXEC: 允许执行该内存段
- PORT_NONE: 该内存段不能被访问
flags:控制程序对该内存段修改时,造成的影响范围,常用的选项如下:
- MAP_PRIVATE: 内存是私有的,对他的修改只在局部范围内有效,其他进程不可见。
- MAP_SHARED: 内存是共享的,某进程对该段内存空间的更新对其他进程是可见的
fd: 映射文件的文件描述符
offset:映射内容在该文件的起始偏移位置
#include <sys/mman.h>
int munmap(void* start,size_t length);
函数参数:
start:映射的起始地址
length:文件中映射到内存的部分的长度
返回值:
解除成功返回0,失败返回-1。
3、删除共享内存
删除一个共享内存区对象的名字,删除一个名字不会影响对于其底层支撑对象的现有引用,直到对于该对象的引用全部关闭为止。
int shm_unlink(const char *name);
函数参数:
name:name标识的共享内存
返回值:
成功返回0,失败返回-1