共享存储
进程间通信方式之一,创建一个共享存储区,各个进程通过在指定地址或者内核选用第一个可用地址来对共享存储区进行数据的读写操作达到进程间数据交互。对同一个共享存储区操作,会涉及到对同一块数据进行修改操作,导致数据混乱甚至段错误,所有应该给操作加上锁,保证同一时刻只有一个进程在操作共享存储区。
1.创建共享存储区
调用shmget创建或引用已存在的共享存储区。每个共享存储区都与一个key相对应,创建或引用共享存储区时需指定共享存储区的key,大小以及读写权限。大小一般为内存叶的整数倍,如果不是叶的整数倍,那么最后一页的剩余空间是不可被其它程序所用。引用已经存在的共享存储区,存储区大小一般指定为0,如果指定的大小比存储区还大出错返回。详细使用参照以下shmget的描述。
2.设置获取共享存储区相关属性值
调用shmctl可获取或者设置共享存储区相关信息。比如共享存储区的大小、权限、系统对共享存储区的限制等等。也可以通过设置SHM_LOCK来把共享存储区的内容锁住,不让系统把其内容交换到swap区,以提高对共享存储区访问性能的提升。具体使用细节参照以下shmctl的使用
3.引用共享存储区
利用shmat把共享存储区映射到进程地址空间,供进程读写操作。映射共享内存区时可以指定地址,也可以不指定地址让系统自动指定一个地址。建议让系统来指定地址供共享内存区映射,因为不同
的系统,不同的硬件它们的地址值不一样,所有程序指定了地址的话不便移植。详细描述参照以下shmat的使用
4.解除共享存储区映射
当不再需要对共享存储区时,要调用shmdt进行解除映射,默认进程结束,自动解除映射。详细描述参照以下shmdt的使用
5.删除共享存储区
程序结束不会删除共享存储,需通过调用shmct指定相应的删除命令来把共享存储区删除,不会立即删除,只有等到所有引用共享内存都解除映射后才会删除。详细描述参照以下shmctl的使用
函数shmget
函数调用说明:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
函数功能描述:该函数是用来创建一个共享存储区,返回指定key的共享存储区的标识。
创建一个新的共享存储段有两种情况:
1.key=IPC_PRIVATE
2.key!=IPC_PRIVATE,但是key也不等于已存在的任意一个共享存储段的key,且shmflg = IPC_CREAT
如果指定的key对应的共享存储区已经存在,且shmflg = IPC_CREAT|IPC_EXCL,那么函数返回失败,errno= EEXIST
参数shmflg取值:
IPC_CREAT 创建一个新的共享存储区, 如果没有指定该标志,那么函数会去查找key指定的共享存储区是否存在,如果存在则检查用户是否对其有相应的操作权限
mode_flags 指定对新创建的存储区拥有的权限,没有可执行权限,与open里的权限含义一样
SHM_HUGETLB (since Linux 2.6) 指定共享存储区使用大内存叶来读写数据,如果不指定则使用4k的叶
SHM_NORESERVE (since Linux 2.6.15) 不为共享内存保留交换区间,如果给共享存储预留交换区,那么用户可以对共享存储去进行修改,如果不给共享存储区预留交换区,那么当没有更多物理内存来进行操作时会出现段错误。
当一个新的共享存储区建立以后,共享存储区会清0,初始化与共享存储区相关的结构体shmid_ds:
shm_perm.cuid 和 shm_perm.uid初始化为调用进程的有效用户id
shm_perm.cgid 和 shm_perm.gid初始化为调用进程的有效组id
shm_perm.mode初始化为参数shmflg设置的权限
shm_segsz共享存储区的大小
shm_ctime当前时间
其他都置为0
返回值
成功返回共享存储区id,失败返回-1,设置errno。
errno错误含义:
EACCES 调用进程没有权限访问共享存储区
EEXIST 指定了参数IPC_CREAT | IPC_EXCL,而且指定key的共享存储区已经存在
EINVAL 创建了一个新的共享存储区,但是大小小于SHMMIN或者大于SHMMAX。引用一个已经存在的共享存储区,但是指定的大小大于该存储区
ENFILE 创建的共享存储区个数已经达到极限
ENOENT 引用一个存在的共享存储区时,指定key的存储区不存在
ENOMEM 没有更多的内存来创建共享存储区
ENOSPC 创建的共享存储大小超过系统内存范围
EPERM 指定了SHM_HUGETLB标志,但是调用进程没有权限
EIDRM(linux 2.3.30)引用了一个已经删除的共享存储区
注意:
IPC_PRIVAT是key_t类型不是标志。如果指定了该标志,系统调用会忽视其他东西,仅权限标志除外,并且创建一个新的共享存储区
关于共享存储区限制:
SHMALL 系统用于共享存储的叶最大数量
SHMMAX 最大共享存储区大小
SHMMIN 最小存储区大小
SHMMNI 系统允许共享存储区的最多个数
每个进程拥有共享存储区个数限制没有明确指定
BUGS
参数IPC_PRIVATE可能会导致意想不到的结果,用IPC_NEW替代
函数shmctl
头文件
#include <sys/ipc.h>
#include <sys/shm.h>
函数调用格式
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函数说明:shmctl()通过指定的命令cmd来操作指定的共享存储区shmid。参数buf是指向结构体struct shmid_ds的指针类型,该结构体声明于头文件<sys/shm.h>:
struct shmid_ds {
struct ipc_perm shm_perm; /* 权限 */
size_t shm_segsz; /* 大小 */
time_t shm_atime; /* 最后一次共享存储区映射到进程地址空间时间*/
time_t shm_dtime; /* 最后一次解除映射时间*/
time_t shm_ctime; /* 最后一次修改的时间 */
pid_t shm_cpid; /* 创建共享存储的进程pid*/
pid_t shm_lpid; /* 最后一次调用shmat/shmdt的进程pid*/
shmatt_t shm_nattch; /* 当前使用该共享存储的进程数*/
...
};
结构体struct ipc_perm声明于<sys/ipc.h>头文件中如下所示:
struct ipc_perm {
key_t __key; /* 共享存储区的key*/
uid_t uid; /* 所属用户的id*/
gid_t gid; /* 所属用户的组id*/
uid_t cuid; /* 创建用户的id*/
gid_t cgid; /* 创建用户的组id*/
unsigned short mode; /* 权限以及SHM_DEST和SHM_LOCKED标志*/
unsigned short __seq; /* Sequence number 程序待验证*/
};
cmd取值:
IPC_STAT 从内核中把shmid指定的相关信息拷贝到buf指定的结构体struct shmid_ds中,调用进程需对共享存储区具有读权限
IPC_SET 把buf指向的数据设置到内核关于shmid的相关信息中,更新shm_ctime值
IPC_RMID 删除共享存储区,只有引用共享存储区为0的时候才会被删除(shm_nattch为0)。用户必须确保共享存储区已被删除,否则页面内容会一直存在于内存或者swap交换区中
IPC_INFO (Linux-specific) 返回关于共享内存系统限制。保存系统限制的结构体如下:
struct shminfo {
unsigned long shmmax; /* 最大共享存储区的大小 */
unsigned long shmmin; /* 最小存储区大小 1字节*/
unsigned long shmmni; /* 最多共享存储区个数*/
unsigned long shmseg; /* 一个进程所能引用的共享区最多个数 */
unsigned long shmall; /* 共享存储引用的最多叶数*/
};
SHM_INFO (Linux-specific) 返回系统关于提供的共享存储区的信息结构体shm_info 待考证
struct shm_info {
int used_ids; /*当前系统存在共享存储区总数*/
unsigned long shm_tot; /*当前系统共享存储区占用的叶的总数*/
unsigned long shm_rss; /* # of resident sharedmemory pages */
unsigned long shm_swp; /* # of swapped sharedmemory pages */
unsigned long swap_attempts; /* 保留 */
unsigned long swap_successe;/* 保留 */
};
SHM_STAT (Linux-specific) 与IPC_STAT类似。但是参数shmid不是共享存储区标识,而是系统内核中关于所有共享存储区数组标识
SHM_LOCK (Linux-specific) 禁止共享存储区内容交换到swap交换区。这样目的在于提升性能。设置了该命令,同时会设置shm_perm.mode=SHM_LOCKED
SHM_UNLOCK (Linux-specific) 解锁,允许其共享区内容被交换到swap交换区
在内核2.6.10之前只有特权用户才能使用SHM_LOCK和SHM_UNLOCK。2.6.10后只要有效的组id匹配了共享存储区的所属的组id或者创建者组id就可以使用SHM_LOCK和SHM_UNLOCK。允许进程被锁上的
共享内存的大小是由RLIMIT_MEMLOCK限制的
返回值
对于IPC_INFO或者SHM_INFO操作,成功返回系统中关于所有共享存储的数组标识(该标识和SHM_STAT一起使用来操作系统所有共享存储区)。对于SHM_STAT操作,成功返回共享存储区的标识符。其他操作成功返回0.
失败返回-1,设置errno:
EACCES 用户对共享存储区进行IPC_STAT或者SHM_STAT操作时,没有对该共享存储区具有读权限
EFAULT 用户进行IPC_SET或者IPC_STAT操作时,参数buf指向的地址不可用
EIDRM 指定的共享存储区已经被删除
EINVAL 共享存储区标识或者指令参数无效
ENOMEM (In kernels since 2.6.9) SHM_LOCK操作,但是锁定在内存中的大小已经超过RLIMIT_MEMLOCK(进程可锁定在内存字节最大值)限定值
EOVERFLOW IPC_STAT操作,但是指定的gid和uid值太大无效
EPERM IPC_SET或者IPC_RMID 操作时,没有相应的权限
注意:
IPC_INFO, SHM_STAT和SHM_INFO也被其他几个ipc函数调用。在linux中运行一个进程引用一个已被标志要删除的共享存储区,但是在其他系统中这种操作是被禁止的,避免使用的
函数shmat
头文件:#include <sys/types.h>
#include <sys/shm.h>
调用格式:void *shmat(int shmid, const void *shmaddr, int shmflg);
功能描述:把shmid指定的共享存储区映射到调用进程指定地址空间shmaddr
shmaddr参数有两种形式:1.为空,系统自动选择一个可用的地址空间来映射共享存储区.2不为空,且标志shmflg=SHM_RND(),那么共享存储区会映射到指定的shmaddr,shmaddr必须与页地址对齐。因为考虑到平台之间的地址可能不一样所以建议不指定地址。共享内存映射规则 如图:
公式"shmaddr - (shmaddr % SHMLBA)"的含义是将地址shmaddr移动到低边界地址的整数倍上。SHMLBA代表了低边界地址的倍数
shmflg取值:
SHM_RDONLY 对指定的共享存储区只有读权限
SHM_REMAP 替代已经在shmaddr指定的地址上已经存在的共享存储区映射(如果不指定SHM_REMAP,映射共享存储区到一段已经有映射的地址会返回失败EINVAL),shmaddr必须非空。进程结束 地址空间自动会解除与共享存储区的映射。成功执行后会更新shmid_ds结构体:shm_atime更新为当前时间,shm_lpid更新为当前进程id,shm_nattch递增1
返回值:成功返回进程地址空间的映射共享存储区的地址,失败返回-1,并设置error
errno:
EACCES 调用进程没有权限
EIDRM 指定的共享存储区已被删除
EINVAL 无效的参数
ENOMEM 没有更多的空余空间
函数shmdt
头文件:#include <sys/types.h>
#include <sys/shm.h>
调用格式:int shmdt(const void *shmaddr);
功能说明:解除共享存储的映射。操作成功更新结构体shmid_ds,shm_dtime更新为当前时间,shm_lpid更新为调用进程id,shm_nattch递减1,如果此时shm_nattch为0,且该共享存储区标志为删除,那么共享存储区被删除。
fork创建了子进程,子进程会从父进程那继承共享存储区
execve创建子进程,所有映射的共享存储区会解除映射
返回值:成功返回0,失败返回-1,设置errno
errno:
EINVAL 指定的地址与页地址不对齐,或者指定地址上并没有映射共享存储区
示例代码:
#include <sys/ipc.h>
#include <sys/shm.h>
#define IPC_MEM_KEY 100001
#define IPC_MEM_BUF 4096
int ipc_sem_fun1(int a_ipc_id)
{
char * b_addr = NULL;
b_addr = (char *)shmat(a_ipc_id, NULL, 0);
if(NULL == b_addr)
{
printf("shmat fail line = %d, fun = %s, file = %s\n", __LINE__, __func__, __FILE__);
return -1;
}
sprintf(b_addr, "%s", "test");
printf("write into segment success!\n");
return 0;
}
int ipc_sem_fun2(int a_ipc_id)
{
char * b_addr = NULL;
b_addr = (char *)shmat(a_ipc_id, NULL, 0);
if(NULL == b_addr)
{
printf("shmat fail line = %d, fun = %s, file = %s\n", __LINE__, __func__, __FILE__);
return -1;
}
sleep(10);
printf("data = %s\n", b_addr);
return 0;
}
int main()
{
int b_mem_id;
int b_ret;
int b_pid[2];
struct shmid_ds b_mem_ds = {0};
struct ipc_perm * b_mem_perm = &(b_mem_ds.shm_perm);
struct shm_info b_mem_info = {0};
b_mem_id = shmget(IPC_PRIVATE, IPC_MEM_BUF, 666);
if(b_mem_id < 0)
{
printf("shmget fail:%s line = %d, fun = %s, file = %s\n", \
strerror(errno), __LINE__, __func__, __FILE__);
return -1;
}
b_pid[0] = fork();
if(b_pid[0] < 0)
{
printf("fork fail line = %d, fun = %s, file = %s\n", __LINE__, __func__, __FILE__);
return -1;
}
else if(0 == b_pid[0])
{
ipc_sem_fun1(b_mem_id);
exit(0);
}
b_pid[1] = fork();
if(b_pid[1] < 0)
{
printf("fork fail line = %d, fun = %s, file = %s\n", __LINE__, __func__, __FILE__);
return -1;
}
else if(0 == b_pid[1])
{
ipc_sem_fun2(b_mem_id);
exit(0);
}
while(1)
{
if(wait(NULL) < 0)
{
break;
}
}
b_ret = shmctl(b_mem_id, IPC_RMID, NULL);
if(b_ret < 0)
{
printf("shmctl fail:%s, line = %d, fun = %s, file = %s\n", \
strerror(errno), __LINE__, __func__, __FILE__);
return -1;
}
return 0;
}
运行结果如图: