通常情况下,Linux分配给两个不同进程的内存区域既不重合,也不重叠,以防止进程之间相互干扰,从而使一个进程执行任何操作都不会影响到另一个进程的正确执行。System V IPV提供了共享内存设施,可以创建允许两个或者多个进程间共享访问的内存块,为在多个进程之间共享和传递数据提供了一种高效的方式。如果某个进程向内存写入数据,所做的改动将立刻被可以访问同一段共享内存的其他任何进程看到。
基于共享内存进行通信的基本原理
在多用户环境下,进程采用的地址是程序地址(又称为逻辑地址、虚拟地址),程序地址划分为很多页或者段,在指令执行过程中,由地址转换机构将逻辑地址转换成存储器物理地址。一般情况下,由于系统给不同进程分配不同的存储块,因此可以认为,不同进程的程序地址,不管是相同还是不同,都会映射到不同的物理地址。System V IPC共享内存的基本原理是:根据进程请求分配一块大小合适的存储块,各请求进程在其地址空间为该共享内存块安排程序地址,将程序地址赋值给变量的指针,然后通过变量指针读写共享内存单元,从而传输数据。
共享内存没有提供同步机制,因此我们通常需要用其他的机制来同步对共享内存的访问,一般同步方法有三种:
- 通过传递消息来同步对共享内存的访问,也就是通过消息通信来通知是否完成对共享内存的写入或者读出操作。
- 利用System V IPC提供的信号量机制。
- 在共享内存中留出一个标志单元用于同步。
在写进程结束对共享内存的写操作之前,并没有自动的机制可以阻止读进程读取共享数据。对共享内存访问的同步控制必须有程序员负责。
共享内存的相关API函数
共享内存的相关API函数有shmget、shmat、shmdt、shmctl四个,他们的函数声明在头文件sys/shm.h中。
1.shmget函数
shmget函数用于创建共享内存,该函数创建由键值key标识的共享内存块,并返回标识号,如果内存块已经存在,就直接返回其标识号。
#include <sys/shm.h> int shmget(key_t key, size_t size, int flag);
返回值:如果共享内存创建成功,shmget返回一个非负整数,即共享内存的标识号,如果失败,则返回-1。
key:共享内存的键值,用于有效的对共享内存段命名,可以唯一的标识一个共享内存段,用相同的key调用shmget函数将返回同一个共享内存标识号。当key为IPC_PRIVATE时,会创建只属于进程的私有共享内存。
size:字节为单位指定需要分享内存块的大小。
flag:9个位的标识字段。
2.shmat函数
shmget函数返回的共享内存段还不能被进程访问。想启动对该共享内存的访问,必须通过shmat函数将其映射到一个进程的逻辑地址空间,以获得该共享内存段的程序地址。
#include <sys/shm.h> void *shmat(int shm_id, const void * shm_addr,int flag);
返回值:如果shmat调用成功,则返回一个指向共享内存中第一个字节的指针,如果失败,则返回-1。
shm_id:shmget函数返回的共享内存标识符。
shm_addr:指定要把共享内存映射到当前进程的地址,一般设置为0,让操作系统来安排共享内存的映射地址。
flag:一组位标志,一般设置为0。
3.shmdt函数
shmdt函数的作用是将共享内存从当前进程中分离,它的参数是shmat函数访问的地址指针。
注意:将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。
#include <sys/shm.h> void *shmdt(char * shmaddr);
4.shmctl
shmctl函数用于对共享内存实施控制管理操作,原型:
#include <sys/shm.h> void *shmdt(int shm_id,int command,struct shmid_ds *buf);
shm_id:shmget函数返回的共享内存标识符。
command:要采取的动作,可以取三个值。
- IPC_STAT:获取共享内存的shmid_ds结构并保存于buf。
- IPC_SET:使用buf的值设置共享内存shmid_ds结构。
- RMID:删除共享内存。
buf:一个指针,指向包含共享内存模式和访问权限的结构体。
shmid_ds结构体:
/* Data structure describing a shared memory segment. */ 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() */ #ifndef __x86_64__ unsigned long int __glibc_reserved1; #endif __time_t shm_dtime; /* time of last shmdt() */ #ifndef __x86_64__ unsigned long int __glibc_reserved2; #endif __time_t shm_ctime; /* time of last change by shmctl() */ #ifndef __x86_64__ unsigned long int __glibc_reserved3; #endif __pid_t shm_cpid; /* pid of creator */ __pid_t shm_lpid; /* pid of last shmop */ shmatt_t shm_nattch; /* number of current attaches */ __syscall_ulong_t __glibc_reserved4; __syscall_ulong_t __glibc_reserved5; };
共享内存通信
shmcreate.c利用命令行参数argv[1]提供的键值创建或者检索共享内存。
#include <sys/shm.h> #include <stdio.h> int main(int argc,char * argv[]){ int rtn; int msqid; key_t key; if(argc!=2){ fprintf(stderr,"请以./shmcreate <key>的形式运行,给出共享内存的键值! "); return -1; } sscanf(argv[1],"%x",&key); msqid = shmget(key,4096,IPC_CREAT|0644); return 0; }
shmwrite.c程序获取键值为argv[1]的共享内存,将命令行参数argv[2]提供的信息写入内存。
#include <sys/shm.h> #include <stdio.h> #include <string.h> int main(int argc,char * argv[]){ int rtn; int shmid; key_t key; void * shmptr; if(argc!=3){ fprintf(stderr,"请以./shmwrite <key> <msg>的形式运行,向共享内存写入数据! "); return -1; } sscanf(argv[1],"%x",&key); shmid = shmget(key,4096,IPC_CREAT|0644); shmptr = shmat(shmid,0,0); memcpy(shmptr,argv[2],strlen(argv[2]) + 1); shmdt(shmptr); return 0; }
shmread.c从共享内存中读出信息并显示。
#include <sys/shm.h> #include <stdio.h> int main(int argc,char * argv[]){ int rtn; int shmid; key_t key; void * shmptr; if(argc!=2){ fprintf(stderr,"请以./msgcreate <key>的形式运行,读取内存中的消息! "); return -1; } sscanf(argv[1],"%x",&key); shmid = shmget(key,4096,IPC_CREAT|0644); shmptr =shmat(shmid,0,0); printf("%s ",(char *)shmptr); shmdt(shmptr); return 0; }
shmdel.c负责删除共享内存
#include <stdio.h> #include <sys/shm.h> int main(int argc,char * argv[]){ int rtn; int shmid; key_t key; if(argc!=2){ fprintf(stderr,"请以./msgcreate <key>的形式运行,删除共享内存! "); return -1; } sscanf(argv[1],"%x",&key); shmid = shmget(key,4096,IPC_CREAT|0644); shmctl(shmid,IPC_RMID,NULL); return 0; }
共享内存通信
实现一个生产者消费者应用,生产者进程shmprod.c循环读取用户从键盘输入的信息,通过共享内存传递给消费者进程shmcons.c并显示输出,用户输入"end"可以结束程序的执行。
1.共享内存结构体和同步设计
为防止消费者进程从空的共享内存中读取数据或者读取到旧的数据,同时也防止旧的信息尚未取走就被新的信息覆盖,生产者/消费者应用必须解决同步问题。在这里用一种比较简单的数据标志方法来实现同步,在共享内存中留出一个单元flag作为同步标志,其他单元作为缓冲区buf使用。标志单元flag为0表示共享内存为空,flag为1表示共享内存中有新的数据,flag为2表示数据传输结束。初始时flag为0,生产者进程shmprod.c仅仅在flag=0时才将数据写入内存,并将flag修改为1,若写入结束串"end",则将flag改为2。消费者进程shmcons.c仅当flag=1时才能从共享内存读出数据,并将flag标志修改为0。
将共享内存设计成一下结构体:
#define TEXT_SE_2048 struct shared_mm{ int flag; char buf[TEXT_SE_2048]; } * shmptr;
2.创建共享内存
程序shmcreate.c创建一个大小有要求的共享内存,并将标志位初始化为0。
#include <sys/shm.h> #include <stdint.h> #include <stdio.h> #define TEXT_SE_2048 struct shared_mm{ int flag; char buf[TEXT_SE_2048]; } * shmptr; int main(int argc,char * argv[]){ int rtn; int shmid; key_t key; if (argc!=2){ fprintf(stderr,"请以./msgcreate <key> 的形式运行,给出消息队列的键值! "); return 0; } sscanf(argv[1],"%x",&key); shmid = shmget(key,sizeof(struct shared_mm),IPC_CREAT|0644); shmptr = (struct shared_mm *)shmat(shmid,0,0); shmptr->flag=0; return 0; }
3.设计生产者进程
程序shmprod.c从命令行参数argv[1]获取共享内存键值,获得共享内存Id,映射共享内存,然后执行循环,从标准输入读入数据,写入共享内存,遇到"end"时结束循环,最后解除共享内存映射。
#include <sys/shm.h> #include <string.h> #include <stdio.h> #define TEXT_SE_2048 struct shared_mm{ int flag; char buf[TEXT_SE_2048]; } * shmptr; int main(int argc,char* argv[]){ int rtn; int shmid; key_t key; if (argc!=2){ fprintf(stderr,"请以./shmprod <key> 的形式运行,给出消息队列的键值! "); return 0; } sscanf(argv[1],"%x",&key); shmid = shmget(key,sizeof(struct shared_mm),IPC_CREAT|0644); shmptr = (struct shared_mm *)shmat(shmid,0,0); for (;;) { while (shmptr->flag!=0); scanf("%s",shmptr->buf); if (strcmp(shmptr->buf,"end")==0){ shmptr->flag = 2; break; } else{ shmptr->flag = 1; } } shmdt(shmptr); return 0; }
4.设计消费者进程
程序shmcons.c从命令行参数argv[1]获取共享内存键值,获得共享内存Id,映射共享内存,然后执行循环,从共享内存读出数据,输出显示,遇到"end"时结束循环,最后解除共享内存映射。
#include <sys/shm.h> #include <string.h> #include <stdio.h> #define TEXT_SE_2048 struct shared_mm{ int flag; char buf[TEXT_SE_2048]; } * shmptr; int main(int argc,char* argv[]){ int rtn; int shmid; key_t key; if (argc!=2){ fprintf(stderr,"请以./shmcons <key> 的形式运行,给出消息队列的键值! "); return 0; } sscanf(argv[1],"%x",&key); shmid = shmget(key,sizeof(struct shared_mm),IPC_CREAT|0644); shmptr = (struct shared_mm *)shmat(shmid,0,0); for (;;) { while (shmptr->flag==0); printf("%s ",shmptr->buf); shmptr->flag = 0; if (strcmp(shmptr->buf,"end")==0){ break; } } shmdt(shmptr); return 0; }