IPC通信:Posix共享内存1
共享内存区是最快的可用IPC形式。它允许多个不相关的进程去访问同一部分逻辑内存。如果需要在两个运行中的进程之间传输数据,共享内存将是一种效率极高的解决方案。一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传输就不再涉及内核。这样就可以减少系统调用时间,提高程序效率
共享内存是由IPC为一个进程创建的一个特殊的地址范围,它将出现在进程的地址空间中。其他进程可以把同一段共享内存段“连接到”它们自己的地址空间里去。所有进程都可以访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会立刻被有访问同一段共享内存的其他进程看到。
要注意的是共享内存本身没有提供任何同步功能。也就是说,在第一个进程结束对共享内存的写操作之前,并没有什么自动功能能够预防第二个进程开始对它进行读操作。共享内存的访问同步问题必须由程序员负责。可选的同步方式有互斥锁、条件变量、读写锁、纪录锁、信号灯。
相关函数
1 mmap()函数 2 功能:把一个文件或一个Posix共享内存区对象映射到调用进程的地址空间。 3 头文件:#include <sys/mman.h> 4 函数原型:void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off); 5 返回值:如果mmap成功则返回映射首地址,如果出错则返回常数MAP_FAILED。 6 参数: 7 addr 指向映射存储区的起始地址; 8 len 映射的字节 9 prot 对映射存储区的保护要求 10 flag 标志位 11 filedes 要被映射文件的描述符 12 off 要映射字节在文件中的起始偏移量 13 14 参数解释如下:整体相当于磁盘文件的对应长度搬移到内存中。如果addr参数为NULL,内核会自己在进程地址空间中选择合适的地址建立映射。 15 如果addr不是NULL,则给内核一个提示,应该从什么地址开始映射,内核会选择addr之上的某个合适的地址开始映射。建立映射后,真正的 16 映射首地址通过返回值可以得到。off参数是从文件的什么位置开始映射,必须是页大小的 17 整数倍(在32位体系统结构上通常是4K)。 18 prot参数有四种取值: 19 PROT_EXEC表示映射的这一段可执行,例如映射共享库 20 PROT_READ表示映射的这一段可读 21 PROT_WRITE表示映射的这一段可写 22 PROT_NONE表示映射的这一段不可访问 23 flag参数有很多种取值,这里只讲两种, 24 MAP_SHARED多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化。 25 MAP_PRIVATE多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化,也不会 26 真的写到文件中去。
注:当进程终止时,该进程的映射内存会自动解除,也可以调用munmap解除映射。munmap成功返回0,出错返回-1。
1 munmap()函数 2 功能:解除存储映射 3 头文件:#include <sys/mman.h> 4 函数原型:int munmap(caddr_t addr,size_t len); 5 参数: 6 addr 指向映射存储区的起始地址 7 len 映射的字节 8 返回值:若成功则返回0,若出错则返回-1 9 10 其中addr参数是由mmap返回的地址,len是映射区的大小。再次访问这些地址导致向调用进程产生一个SIGSEGV信号。如果被映射区是使用 11 MAP_PRIVATE标志映射的,那么调用进程对它所作的变动都被丢弃掉。
内核的虚存算法保持内存映射文件(一般在硬盘上)与内存映射区(在内存中)的同步(前提它是MAP_SHARED内存区)。这就是说,如果我们修改了内存映射到某个文件的内存区中某个位置的内容,那么内核将在稍后某个时刻相应地更新文件。然而有时候我们希望确信硬盘上的文件内容与内存映射区中的文件内容一致,于是调用msync来执行这种同步。
1 msync()函数 2 功能:同步文件到存储器 3 头文件:#include <sys/mman.h> 4 函数原型:int msync(void *addr,size_t len,int flags); 5 参数: 6 addr 指向映射存储区的起始地址 7 len 映射的字节 8 flags 参数为MS_ASYNC(执行异步写),MS_SYNC(执行同步写),MS_INVALIDATE(使高速缓存的数据实效)。 9 其中MS_ASYNC和MS_SYNC这两个常值中必须指定一个,但不能都指定。它们的差别是,一旦写操作已由内核排入队列, 10 MS_ASYNC即返回,而MS_SYNC则要等到写操作完成后才返回。如果还指定了MS_INVALIDATE,那么与其最终拷贝不一致的文件数据的 11 所有内存中拷贝都失效。后续的引用将从文件取得数据。 12 返回值: 若成功则返回0,若出错则返回-1 13 14 15 memcpy()函数 16 功能: 复制映射存储区 17 头文件: #include <string.h> 18 函数原形: void *memcpy(void *dest,const void *src,size_t n); 19 参数: 20 dest 待复制的映射存储区 21 src 复制后的映射存储区 22 n 待复制的映射存储区的大小 23 返回值: 返回dest的首地址
示例代码:
/*mycp.c*/ #include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> int main(int argc,char *argv[]) { int fdin,fdout; void *src,*dst; struct stat statbuf; if(argc!=3) { printf("please input two file!\n"); exit(1); } /*打开原文件*/ if((fdin=open(argv[1],O_RDONLY))<0) perror(argv[1]); /*创建并打开目标文件*/ if((fdout=open(argv[2],O_RDWR|O_CREAT|O_TRUNC))<0) perror(argv[2]); /*获得文件大小信息*/ if(fstat(fdin,&statbuf)<0) printf("fstat error"); /*初始化输出映射存储区*/ if(lseek(fdout,statbuf.st_size-1,SEEK_SET)==-1) printf("lseek error"); if(write(fdout,"1",1)!=1) printf("write error"); /*映射原文件到输入的映射存储区*/ if((src=mmap(0,statbuf.st_size,PROT_READ,MAP_SHARED,fdin,0))==MAP_FAILED) printf("mmap error"); /*映射目标文件到输出的映射存储区*/ if((dst=mmap(0,statbuf.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fdout,0)) ==MAP_FAILED) printf("mmap error"); memcpy(dst,src,statbuf.st_size);/*复制映射存储区*/ munmap(src,statbuf.st_size); /*解除输入映射*/ munmap(dst,statbuf.st_size); /*解除输出映射*/ close(fdin); close(fdout); }
编译运行:
1 root@linux:/mnt/hgfs/C_libary# vi mycp.c 2 root@linux:/mnt/hgfs/C_libary# gcc -o mycp mycp.c 3 root@linux:/mnt/hgfs/C_libary# ./mycp test1.txt test2.txt 4 root@linux:/mnt/hgfs/C_libary# more test2.txt 5 hello world!