共享内存
共享内存(Shared Memory)是指多个进程共享一段指定的内存空间进行数据交互,三种System V IPC机制(另外两种是信号量和消息队列)中共享内存是速度最快的一种。
共享内存机制是最快的一种进程间通信(Interprocess Communication,IPC)机制,因为没有中间介质(如消息队列、管道等的延迟),数据直接由内存映射到进程空间。通常,共享内存段由一个进程创建,接下来的读写操作就由多个进程参与,以此达到通信目的。
现代计算机的工作环境都是保护模式,加上多级页表的设计使得每一个进程都拥有自己的虚拟地址空间,因此可以让一段物理内存同时分配给不同的进程,这就是共享内存机制的实现原理。要实现共享内存通信,就要将同一块物理内存映射到参与通信的每一个进程各自的虚拟地址空间。共享内存只是提供数据的传送,如何控制服务器端和客户端的读写操作互斥,还需要其他的辅助工具(例如信号量)。
采用共享内存通信的一个显而易见的好处就是效率高,因为进程可以直接读写内存,而不需要任何缓冲区。对于像管道和消息队列等通信方式,需要在内核和用户空间进行4次数据拷贝,而共享内存则只需要2次:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,在进程之间共享内存时并不总是读写少量数据后就解除映射,当有新的通信时,再重新建立共享内存,而是保持共享区域,直到通信完毕。这样,数据内容就一直保存在共享内存中,直到解除映射时才写回文件。
共享内存机制的不足在于,需要一定的同步机制控制多个进程对同一块内存的读写,当一个进程在写数据时,不允许其他的进程写数据或读数据,这可以通过信号量的控制实现。
Linux共享内存定义
同另外两种System V IPC机制一样,每个共享内存段都对应一个shmid_ds结构,定义如下:
struct shmid_ds
{
struct ipc_perm shm_perm; //指向共享内存相对应的ipc_perm结构
int shm_segsz; //共享内存的大小,单位为bype
ushort shm_lkcnt; //共享内存被锁定的时间
pid_t shm_cpid; //最近一次调用shmop函数的进程的PID
pid_t shm_lpid; //创建这个共享内存段的进程进程的PID
ulong shm_nattach; //当前把这个内存段附加到地址空间的进程数
time_t shm_atime; //最近一次附加操作的时间
time_t shm_dtime; //最近一次分离操作的时间
time_t shm_ctime; //最近一次修改的时间
};
共享内存的操作
创建或打开
shmget函数用于创建一块新的共享内存或打开一块已经存在的内存,其标准调用格式如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数key表示共享内存标识符;size表示共享内存的大小(单位为字节);shmflg则表示调用函数的操作类型,也可用于设置共享内存的访问权限,两者通过逻辑或表示。 函数的具体动作由key和shmflg共同决定,参考消息队列和信号量。
shmget函数调用成功时返回共享内存的标识符;调用失败时返回-1,并设置相应的error值。
连接共享内存
当一个共享内存被创建或打开后,使用该共享内存的进程必须将此内存区域附加到它的地址空间,Linux提供了shmat函数用于将进程和共享内存连接起来。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数shmid为要连接的共享内存的标识符;参数shmflg指明函数的操作方式,如果shmflg设置了SHM_RDONLY位,则该内存区域被设置为只读,否则为可读写;参数shmaddr和shmflg共同决定共享内存要连接到的地址,详细说明如下:
·如果参数shmaddr为NULL,系统将自动查找进程地址空间,将共享内存区域连接到第一块有效内存上,此时shmflg无效。
·如果参数shmaddr不为NULL,而参数shmflg未设置SHM_RDONLY位,则共享内存连接到由shmaddr指定的地址处。
·如果参数shmaddr不为NULL,且参数shmflg设置了SHM_RDONLY位,则共享内存连接到由shmaddr-(shmaddr%SHMLBA)指定的地址处。
函数调用成功则返回指向共享内存在线程中的地址的指针;调用失败则返回“-1”,同时设置error参数。
脱离共享内存
在使用完共享内存后,应该调用shmdt函数将指定的共享内存段从进程空间中脱离出去。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
shmdt函数仅将共享内存区域进程的地址空间分离,并不删除共享内存。函数调用成功返回0,否则返回-1并设置相应的error值。
属性设置
shmctl用于设置共享内存。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
与信号量的用法相似,共享内存通过cmd和shmid_ds的结构体来指明shmctl函数的操作。
cmd的值 | 含义 |
---|---|
IPC_STAT | 取shmid所指的共享内存段的shmid_ds结构,对参数buf指向的结构赋值 |
IPC_SET | 使用buf指向的结构对shmid段的相关结构赋值,只对以下几个成员有用:uid、gid以及mode,使用此参数要求进程的uid等于shm_perm.cuid或者shm_perm.uid或者拥有root权限 |
IPC_RMID | 删除shmid所指的共享内存,只有当shmid_ds结构的shm_nattch为0时才会真正执行删除 |
SHM_LOCK | 锁定共享内存,此命令只能由超级用户请求 |
SHM_UNLOCK | 解锁共享内存,此命令只能由超级用户请求 |