共享内存是最快的进程间通信方式, 因为进程可以直接读写内存。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据: 一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域,而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的(当然操作系统会以一些策略来写文件,就是是说如果你没有显示调用munmap 或者 msync的话,也会写文件,但这个过程是不可控的,是由操作系统决定)。
linux的共享内存实现有两种: posix 和 system v: 1) POSIX的共享内存,通过用户空间挂载的tmpfs文件系统实现的,通过文件映射的方式,并且会写回到文件,其它进程也可以看到文件的修改; 2) 而 system v 是由内核本身的tmpfs实现的,内核直接实现了shmget/at系统调用,调用了一个shm的特殊文件,对于其它进程来说是看不到这个文件的,也不会写盘。但共享的内容是随内核持续的,就是说只要机器不重启或主动删除,那么共享区一直存在(这也是tmpfs的特性)。 看到一些文章说system v要比posix快,但这点没有去验证,但从system v不会写文件来说应该是对的。
1. posix
void *mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);
这两个函数分别是用于内存映射与解除内存映射
第一个参数是用户指定的文件被映射到进程地址空间的虚地址,如果为0,表示让内核自己选地址
第二个参数是映射的长度, 注意:映射之后不会立刻占用物理内存空间,但会占用虚存空间
第三个参数指定被映射对象的类型,注意:这里的权限不能超过打开文件的权限,比如打开的时候是只读,而这里设置PORT_WRITE就会出错
prot:
PROT_EXEC 表示映射的内存可执行
PROT_WRITE 表示映射的内存可写
PROT_READ 表示映射的内存可读
PROT_NONE 表示映射的页不能被访问
第四个参数是flags:
MAP_FIXED,MAP_SHARED,MAP_PRIVATE,MAP_ANONYMOUS
尽量不用MAP_FIXED。如果参数start指定的地址无法建立映射时就会放弃
MAP_SHARED 表示与其它映射该文件/设备的进程共享映射,即对文件的修改在其它进程中也可见,共享内存的时候拿来用的,会把内存的数据写回到文件(如果调用了mumap, msync 会强制写,如果没有调用,操作系统也会以一定的机制写,只是不能保证数据完全被写进文件)
MAP_PRIVATE 表示对创建一个专门的写时复制映射,即对映射的修改不会影响到被映射的文件,这个不能作为内存共享来用,因为其他的进程看不到这块内存,也不会写文件。
用户只能指定MAP_SHARED与MAP_PRIVATE之一
MAP_ANONYMOUS是一个匿名映射
第五个参数是文件标识符
第六个参数是被映射文件的起始地址
这个函数返回的是被映射到进程地址空间的地址
munmap;
第一个参数被映射到进程地址空间的地址
第二个参数映射的长度
如果映射的长度大于文件的长度,大于的部分页会被清0
mmap有三种映射方式
(1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下:
fd=open(name, flag, mode);
if(fd<0)
...
ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);
(2) 映射shm_open 打开的对象,
fd=shm_open(name, flag, mode); ,创建的文件直接在 /dev/shm/下面, 接下来的操作和open的差不多,但并不调用close来关闭文件,是调用 shm_unlink 来减计数器,当打开的文件计数器为0的时候这个文件就会被删除。
(3)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。
linux采用的是页式管理机制。对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明:
注意:文件被映射部分而不是整个文件决定了进程能够访问的空间大小,另外,如果指定文件的偏移部分,一定要注意为页面大小的整数倍。
/*shm_write.c写入/读出共享内存区*/ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> int main(int argc,char **argv) { int fd; struct stat buf; char *ptr; if(argc!=2) { printf("usage:open <pathname>\n"); exit(1); } fd=open(argv[1],O_RDWR|O_CREAT,0644);/*创建共享内存区*/ ftruncate(fd,100);/*修改共享区大小*/ fstat(fd,&buf); ptr=mmap(NULL,buf.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);/*连接共享内存区*/
close(fd);//文件已经被映射,即便关闭也可以写
strcpy(ptr,"hello linux");/*写入共享内存区*/
printf("%s\n",ptr);/*读出共享内存区*/
munmap(ptr, buf.st_size);
return 0; }
在/proc/sys/kernel/目录下,记录着系统V共享内存的一下限制,如一个共享内存区的最大字节数shmmax,系统范围内最大共享内存区标识符数shmmni等,可以手工对其调整,但不推荐这样做。
对于system v的共享内存可以用ipcs -m 来查看共享内存信息,用 ipcrm -m id来删除对应shmid的共享内存,当然这两个命令不仅仅用于共享内存,其他system v的包括消息队列等都是适用的,只是参数不一样。
[root@test207 zhouhf]# ipcs
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0xffffffff 3506176 root 0 4096 0
0x00000009 3538945 root 0 4096 0
0x00000000 3407874 root 644 80 2
0x00000000 3440644 root 644 16384 2
0x00000000 3473413 root 644 280 2
[root@test207 zhouhf]# ipcrm -m3506176
其中,
shmid是是对应每一个共享内存的,删除的时候的id也是这个;perms栏中列出共享内存的权限;
nattch表示这块内存被几个进程用,0表示没有被用;
status表示内存的状态,如果是dest的话表示该内存段已调用了删除操作,但依然有进程用着它(当该段内存的mode字段设置了SHM_DEST位时就会显示"dest"字样,在有进程使用的情况下ipcrm 或者 shmctl设置为IPC_RMID都会变为dest),如果用户调用shmctl的IPC_RMID时,内核首先看有多少个进程还和这段内存关联着,如果关联数为0,就会销毁(释放)这段内存,否则就设置这段内存的mode位SHM_DEST,”
2.shmget()用来获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域。
shmat()把共享内存区域映射到调用进程的地址空间 中去,这样,进程就可以方便地对共享区域进行访问操作。
shmdt()调用用来解除进程对共享内存区域的映射。
shmctl实现对共享内存区域的控制操 作。
这里我们不对这些系统调用作具体的介绍,读者可参考相应的手册页面,后面的范例中将给出它们的调用方法。
注:shmget的内部实现包含了许多重要的系统V共享内存机制;shmat在把共享内存区域映射到进程空间时,并不真正改变进程的页 表。当进程第一次访问内存映射区域访问时,会因为没有物理页表的分配而导致一个缺页异常,然后内核再根据相应的存储管理机制为共享内存映射区域分配相应的 页表。
3、系统V共享内存限制
在/proc/sys/kernel/目录下,记录着系统V共享内存的一下限制,如一个共享内存区的最大字节数shmmax,系统范围内最大共享内存区标识符数shmmni等,可以手工对其调整,但不推荐这样做。
注意:
1、 系统V共享内存中的数据,从来不写入到实际磁盘文件中去;而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。 注:前面讲到,系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的,文件系统shm的安装点在交换分区上,系统重新引导后,所有的内容都丢失。
2、 系统V共享内存是随内核持续的,即使所有访问共享内存的进程都已经正常终止,共享内存区仍然存在(除非显式删除共享内存),在内核重新引导之前,对该共享内存区域的任何改写操作都将一直保留。
3、 通过调用mmap()映射普通文件进行进程间通信时,一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。 注:这里没有给出shmctl的使用范例,原理与消息队列大同小异。
参考文章
http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index1.html
http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index2.html
http://blog.chinaunix.net/uid-26669729-id-3077015.html
http://www.cnblogs.com/zhangsf/p/3324169.html system v 共享的例子
http://blog.csdn.net/ctthuangcheng/article/details/9278107 mmap的文章
http://www.cnblogs.com/skyme/archive/2011/01/04/1925404.html system v的一些陷阱