共享内存是进程间通信的一种最基本、最快速的机制。共享内存是两个或多个进程共享同一块内存区域,并通过该内存区域实现数据交换的进程间通信机制。通常是由一个进程开辟一块共享内存区域,然后允许多个进程对此区域进行访问。由于不需要使用中间介质,而是数据由内存直接映射到进程空间,因此共享内存是最快速的进程间通信机制。

共享内存的最大不足之处在于,由于多个进程对同一块内存区具有访问的权限,各个进程之间的同步问题显得尤为突出。必须控制同一时刻只有一个进程对共享内存区域写入数据,否则将造成数据的混乱。但是同步控制的问题,可以通过信号量解决。

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:是一个位掩码,具体如下:

multiprocessing manager 内存共享 共享内存 进程同步_#define

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