前几天上进程间通讯的课程,看到有同学对共享内存有些不是很明白,而且在查man帮助的时候也不是很能明白系统对其的说明,故有了这篇文章。
共享内存是系统在做进程间通讯时比较常用的IPC通讯方式之一,同时也是效率高的,但是由于其的独特性导致在使用共享内存的时候需要注意一点就是几个进程间通讯时的互斥和同步问题,所以在使用共享内存的时候,我们一般要对其加锁或者加一些其他的同步机制,比如信号灯之类的。
首先看下共享内存的数据结构:
共享内存区的数据结构
每个共享内存段在内核中维护着一个内部结构shmid_ds,该结构定义在linux/shm.h中,代码如下所示:
struct shmid_ds {
struct ipc_perm shm_perm;/* 超作许可权数据结构指针 */
int shm_segsz; /* 共享存储段大小 (bytes) */
time_t shm_atime; /* 后调用shmat时间 */
time_t shm_dtime; /* 后调用shmdt的时间 */
time_t shm_ctime; /* 后调用shmctl的改变的时间 */
unsigned short shm_cpid; /*创建者的进程ID */
unsigned short shm_lpid; /* 后对共享存储段进行操作的进程ID */
short shm_nattch; /* 当前连接数 */
};
共享内存的创建和操作
A:共享内存区的创建
linux下使用函数shmget来创建一个共享内存区,或者访问一个已经存在的共享内存区,该函数定义如下:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
返回:失败-1, 成功返回非负的共享存储段id。
第一个参数key是共享存储关键字。它有特殊值IPC_PRIVATE表示总是创建一个进程私有的共享存储段。当key值不等于IPC_PRIVATE时,shmget动作取决于后一个参数shmflg标志:
1. IPC_CREAT 单独设置此标志,当系统中不存在相同key时,创建一个新的共享存储段,否则返回已存在的共享存储段。
2. IPC_EXCL 单独设置不起作用。与 IPC_CREAT同时设置时,当系统中存在相同key时,错误返回。保证不会打开一个已存在的共享存储段。
如果没有指定IPC_CREATE并且系统中不存在相同key值的共享存储段,将失败返回。
第二个参数size指明要求的共享存储段的大小。当key指定的共享存储段已存在时,取值范围为0和已存在共享段大小。或简单指定为0。成功后此函数返回共享存储段id,同时创建于参数key相连的数据结构shmid_ds。此节构为系统内部使用。
第三个参数也可以设置共享存储段的访问权限,用或于上面的值操作。
B:共享内存区的操作
在使用共享内存区之前,必须通过shmat函数将其附加到进程的地址空间,进程和地址空间就建立了连接。shmat调用成功后就返回一个共享内存区指针,使用该指针就可以访问共享内存区了。
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
返回:失败-1并置errno, 成功返回连接的实际地址。
第一个参数,必须由shmget返回的存储段的id。
第二个参数指明共享存储段要连接到的地址。0,系统为我们创建一个适当的地址值。否则自己指定。
第三个参数shmflg可以设成:SHM_RND, SHM_RDONLY。SHM_RND为自己指定连接地址时用。SHM_RDONLY说明此存储段只读。
当进程结束使用共享内存区时,就要通过函数shmdt断开于共享内存区的连接。函数介绍如下:
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void * shmaddr);
返回:失败-1并置errno, 成功返回0
分离由shmaddr指定的存储段。此值应该由shmat返回的值。
此函数不是删除共享存储段,而是从当前进程分离存储段。
当进程推出时,系统会自动分离它连接的所有共享段。
共享内存区的控制
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
存储段控制函数。可获得shmid_ds全部内容。
返回:失败-1并置errno, 成功返回0。
第一个参数,必须由shmget返回的存储段的id。cmd为指定要求的操作。
CMD 说明 参数
IPC_STAT 放置与shmid相连的shmid_ds结构当前值于buf所指定用户区 buf
IPC_SET 用buf指定的结构值代替与shmid相连的shmid_ds结构值 buf
IPC_RMID 删除制定的信号量集合
SHM_LOCK 锁住共享存储段。只能由超级管理员使用
SHM_UNLOCK unlock共享存储段。只能由超级管理员使用
附例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <errno.h>
#define SHM_SIZE 1024
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
/*创建信号量函数*/
int createsem (const char *pathname,int proj_id,int members,int init_val)
{
key_t msgkey;
int index,sid;
union semun semopts;
if ((msgkey = ftok(pathname,proj_id)) == -1) {
perror ("ftok error1\n");
return -1;
}
if ((sid = semget (msgkey,members,IPC_CREAT |0666)) == -1) {
perror("semget call failed!");
return -1;
}
/*初始化操作*/
semopts.val = init_val;
for (index = 0;index < members; index ++){
semctl(sid,index,SETVAL,semopts);
}
return (sid);
}
/*打开信号量函数*/
int opensem (const char *pathname,int proj_id)
{
key_t msgkey;
int sid;
if ((msgkey = ftok(pathname,proj_id)) == -1) {
perror ("ftok error1\n");
return -1;
}
if ((sid = semget (msgkey,0,IPC_CREAT |0666)) == -1) {
perror("semget call failed!");
return -1;
}
return (sid);
}
/*P操作函数*/
int sem_p(int semid,int index)
{
struct sembuf buf = {0,-1,IPC_NOWAIT};
if (index < 0) {
perror("index of array can not equals a minus value!");
return -1;
}
buf.sem_num = index;
if (semop(semid ,&buf,1) == -1) {
perror("a wrong operations to semaphore occured!");
return -1;
}
return 0;
}
/*V操作函数*/
int sem_v(int semid,int index)
{
struct sembuf buf = {0,+1,IPC_NOWAIT};
if (index < 0) {
perror("index of array can not equals a minus value!");
return -1;
}
buf.sem_num = index;
if (semop(semid ,&buf,1) == -1) {
perror("a wrong operations to semaphore occured!");
return -1;
}
return 0;
}
/*删除信号集函数*/
int sem_delete(int semid)
{
return (semctl(semid,0,IPC_RMID));
}
/*等待信号集函数*/
int wait_sem (int semid,int index)
{
while (semctl(semid,index,GETVAL,0) == 0) {
sleep(1);
}
return 1;
}
/*创建共享内存函数*/
int createshm(char * pathname,int proj_id,size_t size)
{
key_t shmkey;
int sid;
/*获取键值*/
if ((shmkey = ftok(pathname,proj_id)) == -1 ) {
perror("shmkey ftok error! \n");
return -1;
}
if ((sid = shmget(shmkey,size,IPC_CREAT | 0666)) == -1) {
perror("shmget call failed!\n");
return -1;
}
return (sid);
}
/* Write.c */
#include <string.h>
#include "sharemem.h"
int main(void)
{
int semid,shmid;
char *shmaddr;
char write_str[SHM_SIZE];
if ((shmid = createshm(".",'m',SHM_SIZE)) == -1) {
exit (1);
}
if ((shmaddr = shmat(shmid,(char*)0,0)) == (char *)-1) {
perror("attach shared memory error!\n");
exit (1);
}
if ((semid = createsem(".",'s',1,1)) == -1) {
exit(1);
}
while(1) {
wait_sem(semid,0);
sem_p(semid,0);
printf("write:");
fgets(write_str,1024,stdin);
int len = strlen(write_str) -1;
write_str[len] = '\0';
strcpy(shmaddr,write_str);
sleep(2); /*使reader处于阻塞状态*/
sem_v(semid,0); /*v操作*/
sleep(2); /*等待reader进行读操作*/
}
return 0;
}
/* Read.c */
#include "sharemem.h"
int main(void)
{
int semid,shmid;
char *shmaddr;
char write_str[SHM_SIZE];
if ((shmid = createshm(".",'m',SHM_SIZE)) == -1) {
exit (1);
}
if ((shmaddr = shmat(shmid,(char*)0,0)) == (char *)-1) {
perror("attach shared memory error!\n");
exit (1);
}
if ((semid = opensem(".",'s')) == -1) {
exit(1);
}
while(1) {
printf("reader:");
wait_sem(semid,0); /*等待信号值为1*/
sem_p(semid,0);
printf("%s\n",shmaddr);
sleep(2); /*使writer处于阻塞状态*/
sem_v(semid,0); /*v操作*/
sleep(2); /*等待write进行写操作*/
}
return 0;
}