前言共享内存主要用于进程间通信。Linux有两种共享内存(SharedMemory)机制:(1)**SystemV共享内存(shmget/shmat/shmdt)**独创的共享内存机制,至今仍在广泛使用无关进程间共享。(2)**POSIX共享内存(shm_open/shm_unlink)**在不相关的进程之间共享,没有文件系统I/O的开销旨在比旧的API更简单和更好。另外,在Linux中,不得不提一下内存映射(也用于进程间通信):**Sharedmappings–mmap(2)**lSharedanonymousmappings:Sharingbetweenrelatedprocessesonly(relatedviafork())l共享文件映射:无关进程之间的共享,以文件系统中的文件为后盾SystemV共享内存历史悠久,应用广泛,被许多类Unix系统支持。一般来说,我们在写程序的时候一般都会使用第一种。这里不再讨论如何使用它们。关于POSIX共享内存的详细介绍,请参考这里的1和这里的2。**说了这么多,问题来了,共享内存和tmpfs是什么关系?**Linux2.4上的POSIX共享内存对象实现使用专用文件系统,通常安装在/dev/shm下。从这里可以看出POSIX共享内存是基于tmpfs实现的。其实更进一步,不仅是PSM(POSIXsharedmemory),SSM(SystemVsharedmemory)也是基于tmpfs在内核中实现的。tmpfs介绍下面是内核文档中对tmpfs的介绍:tmpfs有以下用途:1)总是有一个内核内部挂载,你根本看不到。这用于共享匿名映射和SYSV共享内存。此挂载不依赖于CONFIG_TMPFS。如果未设置CONFIG_TMPFS,则不会构建tmpfs的用户可见部分。但内部机制始终存在。2)glibc2.2及更高版本期望将tmpfs安装在/dev/shm以用于POSIX共享内存(shm_open、shm_unlink)。将以下行添加到/etc/fstab应该可以解决这个问题:tmpfs/dev/shmtmpfsdefaults00如果需要,请记住创建您打算挂载tmpfs的目录。SYSV共享内存不需要此挂载。内部支架用于此。(2.3内核版本需要挂载tmpfs的前身(shmfs)才能使用SYSV共享内存)从这里我们可以看出tmpfs主要有两个作用:(1)用于SYSV共享内存,匿名内存映射;这部分由内核管理,用户不可见;(2)用于POSIX共享内存,用户负责挂载,一般挂载到/dev/shm;取决于CONFIG_TMPFS;到这里,我们可以理解SSM和PSM的区别,也可以理解/dev/shm的作用下面我们来做一些测试:test我们把/dev/shm的tmpfs设置为64M:#mount-size=64M-oremount/dev/shm#df-lhFilesystemSizeUsedAvailUse%Mountedontmpfs64M064M0%/dev/shmSYSV共享内存最大大小为32M:#cat/proc/sys/kernel/shmmax33554432(1)创建65M系统V共享内存失败:#ipcmk-M68157440ipcmk:createsharememoryfailed:Invalidargument这是正常的。(2)调整shmmax为65M#echo68157440>/proc/sys/kernel/shmmax#cat/proc/sys/kernel/shmmax68157440#ipcmk-M68157440Sharedmemoryid:0#ipcs-m-----SharedMemorySegments--------keyshmidownerpermsbytesnattchstatus0xef46b2490root644681574400可以看到systemv共享内存的大小不受/dev/shm的影响。(3)创建POSIX共享内存点击(这里)折叠或打开/*gcc-oshmopenshmopen.c-lrt*/#include#include#include#include#include#include#include#defineMAP_SIZE68157440intmain(intargc,char*argv[]){intfd;void*result;fd=shm_open("/shm1",O_RDWR|O_CREAT,0644);if(fd<0){printf("shm_openfailed\n");exit(1);}return0;}#./shmopen#ls-lh/dev/shm/shm1-rw-r--r--1rootroot65MMar306:19/dev/shm/shm1只管理/dev/shm只有64M,但是创建一个65M的POSIXSM也能成功。(4)向POSIXSM写入数据点击(这里)折叠或打开/*gcc-oshmwriteshmwrite.c-lrt*/#include#include#include#include#include#include#include#defineMAP_SIZE68157440intmain(intargc,char*argv[]){intfd;void*result;fd=shm_open("/shm1",O_RDWR|O_CREAT,0644);if(fd<0){printf("shm_openfailed\n");exit(1);}if(ftruncate(fd,MAP_SIZE)<0){printf("ftruncatefailed\n");exit(1);}result=mmap(NULL,MAP_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);if(result==MAP_FAILED){printf("mappedfailed\n");exit(1);}/*...operateresultpointer*/printf("memset\n");memset(result,0,MAP_SIZE);//shm_unlink("/shm1");return0;}#./shmwritememsetBuserror可以看出写入65M数据会报Buserror错误。但是,可以在/dev/shm中创建新文件:#ls-lh/dev/shm/-lhtotalusage64M-rw-r--r--1rootroot65MMarch315:23shm1-rw-r--r--1rootroot65MMarch315:24shm2这是正常的,ls显示inode->size。#stat/dev/shm/shm2File:"/dev/shm/shm2"Size:68157440Blocks:0IOBlock:4096普通文件Device:10h/16dInode:217177Links:1Access:(0644/-rw-r--r--)Uid:(0/root)Gid:(0/root)访问:2015-03-0315:24:28.025985167+0800修改:2015-03-0315:24:28.025985167+0800更改:2015-03-0315:24:28.025985167+0800(5)向SYSV共享内存写入数据,调整SystemV共享内存最大值为65M(/dev/shm仍为64M)。#cat/proc/sys/kernel/shmmax68157440单击(此处)折叠或打开/*gcc-oshmvshmv.c*/#include#include#include#include#defineMAP_SIZE68157440intmain(intargc,char**argv){intshm_id,i;key_tkey;chartemp;char*p_map;char*name="/dev/shm/shm3";key=ftok(名称,0);if(key==-1)perror("ftokerror");shm_id=shmget(key,MAP_SIZE,IPC_CREAT);if(shm_id==-1){perror("shmgeterror");返回;}p_map=(char*)shmat(shm_id,NULL,0);memset(p_map,0,MAP_SIZE);if(shmdt(p_map)==-1)perror("detacherror");}#./shmv可以执行通常情况下。(7)结论虽然SystemV和POSIX共享内存都是通过tmpfs实现的,但是局限性不同。也就是说/proc/sys/kernel/shmmax只会影响SYSV共享内存,而/dev/shm只会影响Posix共享内存。事实上,SystemV和Posix共享内存最初使用的是两个不同的tmpfs实例(instance)。内核分析内核初始化时,会自动挂载一个tmpfs文件系统,挂载为shm_mnt:点击(此处)折叠或打开//mm/shmem.cstaticstructfile_system_typeshmem_fs_type={.owner=THIS_MODULE,.name="tmpfs",.get_sb=shmem_get_sb,.kill_sb=kill_litter_super,};int__initshmem_init(void){...error=register_filesystem(&shmem_fs_type);if(error){printk(KERN_ERR"Couldnotregistertmpfs\n");gotoout2;}///mounttmpfs(对于SYSV)shm_mnt=vfs_kern_mount(&shmem_fs_type,MS_NOUSER,shmem_fs_type.name,NULL);/dev/shm的挂载过程与普通文件挂载类似,不再赘述。不过值得注意的是,/dev/shm的默认大小是当前物理内存的1/2:shmem_get_sb–>shmem_fill_super点击(此处)折叠或打开//mem/shmem.cintshmem_fill_super(structsuper_block*sb,void*data,intsilent){...#ifdefCONFIG_TMPFS/**Perdefaultweonlyallowhalfofthephysicalramper*tmpfsinstance,limitinginodestooneperpageoflowmem;*buttheinternalinstanceisleftunlimited.*/if(!(sb->s_flags&MS_NOUSER)){///内核将设置MS_NOUSERsmebinfos->max_blocks=->可以看出因为内核在挂载tmpfs时指定了MS_NOUSER,所以tmpfs没有大小限制。因此SYSV共享内存可以使用的内存空间只受限于/proc/sys/kernel/shmmax;并且用户通过挂载的/dev/shm默认为物理内存的1/2。注意CONFIG_TMPFS。另外,在/dev/shm中创建文件使用的是VFS接口,而SYSV和匿名映射是通过shmem_file_setup实现的:SIGBUS当应用程序访问共享内存对应的地址空间时,如果对应的物理PAGE还没有分配,如果调用了fault方法,分配失败,会返回OOM或BIGBUS错误:Click(here)tofoldoropen#endif};staticintshuctm_fault(structv_fault(str*vma,structvm_fault*vmf){structinode*inode=vma->vm_file->f_path.dentry->d_inode;intererror;intret=VM_FAULT_LOCKED;error=shmem_getpage(inode,vmf->pgoff,&vmf->page,SGP_CACHE,&ret);if(error)return((error==-ENOMEM)?VM_FAULT_OOM:VM_FAULT_SIGBUS);returnret;}shmem_getpage–>shmem_getpage_gfp:/**shmem_getpage_gfp-findpageincache,orgetfromswap,orallocate**Ifweallocateanewonewedonotmarkitdirty.That'suptothe*vm.Ifweswapitinwemarkitdirtyssincewealsofreetheswap*entrysinceapagecannotliveinboththeswapandpagecache*/staticintshmem_getpage_gfp(structinode*inode,pgoff_tindex,structpage**pagep,enumsgp_typesgp,gfp_tgfp,int*fault_type){...if(sbinfo->max_blocks){///dev/shm将具有此值if(percpu_counter_compare(&sbinfo->used_blocks,sbinfo->max_blocks)>=0){error=-ENOSPC;gotounacct;}percpu_counter_inc(&sbinfo->used_blocks);}//分配一个物理PAGEpage=shmem_alloc_page(gfp,info,index);if(!page){error=-ENOMEM;gotodecused;}SetPageSwapBacked(page);__set_page_locked(page);error=mem_cgroup_cache_charge(page,current->mm,gfp&GFP_RECLAIM_MASK);///mem_cgroup检查if(!error)error=shmem_add_to_page_cache(page,mapping,index,gfp,NULL);共享内存和CGROUP目前共享内存的空间是在访问共享内存的第一组计算的,参考:lhttp://lwn.net/Articles/516541/lhttps://www.kernel.org/doc/Documentation/cgroups/memory.txtPOSIX共享内存与Docker目前Docker限制/dev/shm为64M,但不提供参数。这种做法不好。如果应用程序使用大内存的POSIX共享内存,难免会出问题。参考:lhttps://github.com/docker/docker/issues/2606lhttps://github.com/docker/docker/pull/4981总结(1)POSIX共享内存和SYSV共享内存都在kerneltmpfs的实现,但是对应两个不同的tmpfs实例,相互独立。(2)SYSV共享内存(single)的最大值可以通过/proc/sys/kernel/shmmax来限制,POSIX共享内存(sumofall)的最大值可以通过/dev/shm来限制。