共享内存
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完成正确的读出。
注意:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,例如前面说到的信号量。本例就采用信号量来实现对临界资源的互斥访问。