共享内存就是多个进程可以共同使用的同一段物理内存空间,它是通过将同一段物理内存映射到多个进程各自的虚拟空间来实现的。由于映射内存映射到共享它的进程地址空间中,这些进程间数据的传递就不再涉及内核(进程不再通过执行任何进入内核的系统调用来彼此传递数据),所以共享内存是所有可用的进程间通信方式中最快的。
共享内存主要应用在进程间高效的通信,那么是否有其它的应用场景呢?思考如下两种场景是可以用到的:
1、进程在需要频繁处理一个大文件时,将大文件映射到共享内存,可以操作内存的形式来操作文件,效率比磁盘IO高很多,这种方式主要通过mmap系统调用来实现,本文不做深入研究;
2、进程需要存储一些信息,在进程重启的情况下信息仍然不丢失,但是又可以接受在机器重启情况下信息丢失。就可以考虑用共享内存来保存这部分信息。本文即是在这种应用需求下对共享内存(System V共享内存)的学习总结。
共享内存由内核维护,内核维护如下的共享内存信息结构:
struct shmid_ds
{
struct ipc_perm shm_perm; /* operation permission struct */
size_t shm_segsz; /* size of segment in bytes */
__time_t shm_atime; /* time of last shmat() */
#if __WORDSIZE == 32
unsigned long int __unused1;
#endif
__time_t shm_dtime; /* time of last shmdt() */
#if __WORDSIZE == 32
unsigned long int __unused2;
#endif
__time_t shm_ctime; /* time of last change by shmctl() */
#if __WORDSIZE == 32
unsigned long int __unused3;
#endif
__pid_t shm_cpid; /* pid of creator */
__pid_t shm_lpid; /* pid of last shmop */
shmatt_t shm_nattch; /* number of current attaches */
unsigned long int __unused4;
unsigned long int __unused5;
};
后面的函数调用都需要包含以下三个头文件:
#include
#include
#include
一、创建共享内存区
int shmget(key_t key, size_t size, int oflag);
shmget函数创建一个新的共享内存区(或者返回一个已经存在的共享内存区)。返回值是一个称为共享内存区标识符的整数,其它三个shmXXX函数就用它来指代这个内存区。key: 一般通过ftok函数得到:
key_t ftok( char * fname, int id );
fname就是你指定的文件名,该文件名必须是存在可访问的,且不能经常被程序删除创建(因为在一般的UNIX&linux实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值,如果文件在程序中删除或创建,那么文件的索引号会改变),因此经常用代表当前路径的"."来作为fname。
id是子序号,虽然为int,但是只有8个比特被使用(0-255)。
size: 以字节为单位指定共享内存区的大小;
oflag:共享内存区读写权限值的集合,一般设置为:0666(即用户、组和其他用户都有读写权限)。它还可以与IPC_CREATE(指定键IPC对象不存在时创建新对象,否则返回该对象)或IPC_CREAT|IPC_EXCL(指定键IPC对象不存在时创建新对象,否则返回一个EEXIST错误)。
注:shmget创建一个新的共享内存区时,该内存区会被初始化为size字节的0。
二、把共享内存对象映射到调用进程的地址空间
void* shmat(int shmid, const void* shmaddr, int flag);
由shmget创建或打开一个共享内存区后,通过调用shmat把它附接到调用进程的地址空间。若成功则返回共享内存区在调用进程的起始地址,失败则返回-1。
shmid:由shmget返回的标识符;
shmaddr:若shmaddr是一个空指针,那么系统替调用者选择地址(绝大部分场景),若shmaddr是一个非空指针,那么返回地址取决于调用者是否给flag参数指定了SHM_RND 值。若没有指定SHM_RND,那么共享内存区连接到shmaddr参数指定的地址;若指定了SHM_RND,则连接到由(shmaddr - shmaddr%SHMLBA)指定的地址上(为了取整)。
三、断开共享内存连接
int shmdt(const void* shmaddr);
当一个进程完成某个共享内存区的使用时,它就可以调用shmdt断开这个共享内存区的连接。
当一个进程终止时,它连接的所有共享内存区自动断开连接,注意的是本函数只断开与共享内存区的连接而不删除共享内存区。
四、共享内存管理
int shmctl(int shmid, int cmd, struct shmid_ds);
shmctl函数提供了对一个共享内存区的多种操作。
shmid:由shmget返回的共享内存区标识;
cmd:共享内存管理命令:
IPC_RMIDL:从系统中删除由shmid标识的共享内存区并释放回收该区域;
IPC_SET:给锁指定的共享内存区设置shmid_ds结构信息为buffer中信息(只有部分信息可设置);
IPC_STAT:返回给调用者指定的共享内存区的shmid_ds结构(通过buffer参数返回)。
buffer:根据cmd不同设置或读取shmid_ds结构用。