共享内存
1.共享内存就是分配一块能被其他进程访问的内存。每个共享内存段在内核中维护着一个内部结构shmid_ds(和消息队列、信号量一样),该结构体定义在头文件linux/shm.h中。
int shmget(key_t key,size_t size,int shmflg);
函数中:参数key是由ftok()得到的键值;参数size以字节为单位指定内存的大小;shmflg为操作标志位,它的值为一些宏。
shmflg取:
IPC_CREAT:调用shmget时,系统将为此值与其他所有共享内存区的键值进行比较,如果存在相同的key,说明共享内存区已经存在,此时返回该共享内存区的标志符,否则新建一个共享内存区并返回其标志符。
IPC_EXCL:该宏必须和IPC_CREAT一起使用,否则没有意义。当shmflg取IPC_CREAT|IPC_EXCL时,表示如果发现信号集已经存在,则返回-1,错误吗为EEXIST。
注意:档创建一个新的共享内存区时,size值必须大于0;如果是访问一个已经存在的共享内存区,置size为0。
void *shmat(int shmid,const void* shmaddr,int shmflg);
参数shmid为shmget的返回值;
参数shmflg为存取权限标志;
参数shmaddr为共享内存的附加点。参数shmaddr不同的取值情况的含义说明如下:
如果为空,则由内核选择一个空闲的内存区;
如果非空,返回地址取决于调用者是否给shmflg参数指定了SHM_RND值,如果没有指定,则共享内存区附加到由shmaddr指定的地址;否则附加地址为shmaddr向下舍入一个共享内存低端边界地址后的地址(SHMLAB,一个常址)。
通常将参数shmaddr设置为NULL。
(2)当进程结束使用共享内存区时,要通过函数shmdt断开与共享内存区的连接。该函数声明在sys/shm.h文件中,原型如下:
int shmdt(const void* shmaddr);
参数shmaddr为shmat函数的返回值。该函数调用成功后,返回0,否则返回-1。进程脱离共享内存区后,数据结果shmid_ds中的shm_mattch就会减1。但是共享内存段依然存在,只有shm_mattch为0后,即没有任何进程再使用该共享内存区,共享内存区才在内核中被删除。一般来说,当一个进程终止时,它所附加的共享内存区都会自动脱离。
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
函数中:
参数shmid为共享内存区的标志符;
参数buf为指向shmid_ds结构体的指针;
参数cmd为操作标志位。支持以下3中控制操作:
IPC_RMID:从系统中删除由shmid标识的共享内存区;
IPC_SET:设置共享内存区的shmid_ds结构;
writer.c
reader.c
1.共享内存就是分配一块能被其他进程访问的内存。每个共享内存段在内核中维护着一个内部结构shmid_ds(和消息队列、信号量一样),该结构体定义在头文件linux/shm.h中。
2.共享内存的创建
linux下使用函数shmget来创建一个共享内存区,或者访问一个已存在的共享内存区。该函数定义在头文件linux/shm.h中,原型如下:int shmget(key_t key,size_t size,int shmflg);
函数中:参数key是由ftok()得到的键值;参数size以字节为单位指定内存的大小;shmflg为操作标志位,它的值为一些宏。
shmflg取:
IPC_CREAT:调用shmget时,系统将为此值与其他所有共享内存区的键值进行比较,如果存在相同的key,说明共享内存区已经存在,此时返回该共享内存区的标志符,否则新建一个共享内存区并返回其标志符。
IPC_EXCL:该宏必须和IPC_CREAT一起使用,否则没有意义。当shmflg取IPC_CREAT|IPC_EXCL时,表示如果发现信号集已经存在,则返回-1,错误吗为EEXIST。
注意:档创建一个新的共享内存区时,size值必须大于0;如果是访问一个已经存在的共享内存区,置size为0。
3.共享内存区的操作
(1)在使用共享内存区前,必须通过shmat函数将其附加到进程的地址空间。进程与共享内存就建立了连接。shmat调用成功后就会返回一个指向共享内存区的指针,使用该指针就可以访问共享内存区了,如果失败返回-1。该函数声明在linux/shm.h文件中,原型如下:void *shmat(int shmid,const void* shmaddr,int shmflg);
参数shmid为shmget的返回值;
参数shmflg为存取权限标志;
参数shmaddr为共享内存的附加点。参数shmaddr不同的取值情况的含义说明如下:
如果为空,则由内核选择一个空闲的内存区;
如果非空,返回地址取决于调用者是否给shmflg参数指定了SHM_RND值,如果没有指定,则共享内存区附加到由shmaddr指定的地址;否则附加地址为shmaddr向下舍入一个共享内存低端边界地址后的地址(SHMLAB,一个常址)。
通常将参数shmaddr设置为NULL。
(2)当进程结束使用共享内存区时,要通过函数shmdt断开与共享内存区的连接。该函数声明在sys/shm.h文件中,原型如下:
int shmdt(const void* shmaddr);
参数shmaddr为shmat函数的返回值。该函数调用成功后,返回0,否则返回-1。进程脱离共享内存区后,数据结果shmid_ds中的shm_mattch就会减1。但是共享内存段依然存在,只有shm_mattch为0后,即没有任何进程再使用该共享内存区,共享内存区才在内核中被删除。一般来说,当一个进程终止时,它所附加的共享内存区都会自动脱离。
4.共享内存区的控制
Linux对共享内存区的控制是通过调用函数shmctl来完成的,该函数定义在头文件sys/shm.h中,原型如下:int shmctl(int shmid,int cmd,struct shmid_ds *buf);
函数中:
参数shmid为共享内存区的标志符;
参数buf为指向shmid_ds结构体的指针;
参数cmd为操作标志位。支持以下3中控制操作:
IPC_RMID:从系统中删除由shmid标识的共享内存区;
IPC_SET:设置共享内存区的shmid_ds结构;
IPC_STAT:读取共享内存区的shmid_ds结构,并将其存储到buf指向的地址。
5.示例代码
sharemem.h
#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 *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 error!\n");
return -1;
}
if((sid=semget(msgkey,members,IPC_CREAT|0666))==-1)
{
perror("semget call failed.\n");
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 error!\n");
return -1;
}
if((sid=semget(msgkey,0,IPC_CREAT|0666))==-1)
{
perror("semget call failed,\n");
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 cannot equals a minus value!\n");
return -1;
}
buf.sem_num=index;
if(semop(semid,&buf,1)==-1)
{
perror("a wrong operation to semaphore occurred!\n");
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 cannot equals a minus value!\n");
return -1;
}
buf.sem_num=index;
if(semop(semid,&buf,1)==-1)
{
perror("a wrong operation to semaphore occurred!\n");
return -1;
}
return 0;
}
/*删除信号集函数*/
int sem_delete(int semid)
{
return (semctl(semid,0,IPC_RMID));
}
/*等待信号为1*/
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("ftok eror!\n");
return -1;
}
if((sid=shmget(shmkey,size,IPC_CREAT|0666))==-1)
{
perror("shmget call failed.\n");
return -1;
}
return (sid);
}
writer.c
#include"sharemem.h"
#include<string.h>
int main()
{
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("writer:");
fgets(write_str,1024,stdin);
int len=strlen(write_str)-1;
write_str[len]='\0';
strcpy(shmaddr,write_str);
sleep(10);
sem_v(semid,0);
sleep(10);
}
}
reader.c
#include"sharemem.h"
#include<string.h>
int main()
{
int semid,shmid;
char *shmaddr;
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);
sem_p(semid,0);
printf("%s\n",shmaddr);
sleep(10);
sem_v(semid,0);
sleep(10);
}
}
运行结果分析:
运行后发现,writer进程与reader进程是同步的,writer写入的信息被reader完成正确的读出。
注意:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,例如前面说到的信号量。本例就采用信号量来实现对临界资源的互斥访问。