浅析Linux的共享内存与tmpfs文件系统

前言

共享内存主要用于进程间通信,Linux有两种共享内存(Shared Memory)机制:

(1) ** System V shared memory(shmget/shmat/shmdt) **

Original shared memory mechanism, still widely used Sharing between unrelated processes.

(2) ** POSIX shared memory(shm_open/shm_unlink) **

Sharing between unrelated processes, without overhead of filesystem I/O Intended to be simpler and better than older APIs.

另外,在Linux中不得不提一下内存映射(也可用于进程间通信):

** Shared mappings – mmap(2) **

l Shared anonymous mappings:Sharing between related processes only (related via fork())

l Shared file mappings:Sharing between unrelated processes, backed by file in filesystem

System V共享内存历史悠久,使用也很广范,很多类Unix系统都支持。一般来说,我们在写程序时也通常使用第一种。这里不再讨论如何使用它们,关于POSIX共享内存的详细介绍可以参考这里1,这里2。

** 讲到那么多,那么问题来了,共享内存与tmpfs有什么关系? **

The POSIX shared memory object implementation on Linux 2.4 makes use of a dedicated filesystem, which is normally mounted under /dev/shm.

从这里可以看到,POSIX共享内存是基于tmpfs来实现的。实际上,更进一步,不仅PSM(POSIX shared memory),而且SSM(System V shared memory)在内核也是基于tmpfs实现的。

tmpfs介绍

下面是内核文档中关于tmpfs的介绍:

tmpfs has the following uses:

1) There is always a kernel internal mount which you will not see at all. This is used for shared anonymous mappings and SYSV shared memory.

This mount does not depend on CONFIG_TMPFS. If CONFIG_TMPFS is not set, the user visible part of tmpfs is not build. But the internal mechanisms are always present.

2) glibc 2.2 and above expects tmpfs to be mounted at /dev/shm for POSIX shared memory (shm_open, shm_unlink). Adding the following line to /etc/fstab should take care of this:

tmpfs /dev/shm tmpfs defaults 0 0

Remember to create the directory that you intend to mount tmpfs on if necessary.

This mount is not needed for SYSV shared memory. The internal mount is used for that. (In the 2.3 kernel versions it was necessary to mount the predecessor of tmpfs (shm fs) to use SYSV shared memory)

从这里可以看到tmpfs主要有两个作用:

(1)用于SYSV共享内存,还有匿名内存映射;这部分由内核管理,用户不可见;

(2)用于POSIX共享内存,由用户负责mount,而且一般mount到/dev/shm;依赖于CONFIG_TMPFS;

到这里,我们可以了解,SSM与PSM之间的区别,也明白了/dev/shm的作用。

下面我们来做一些测试:

测试

我们将/dev/shm的tmpfs设置为64M:


  1. # mount -size=64M -o remount /dev/shm# df -lh 
  2.  
  3. Filesystem Size Used Avail Use% Mounted on 
  4.  
  5. tmpfs 64M 0 64M 0% /dev/shm 

SYSV共享内存的最大大小为32M:


  1. # cat /proc/sys/kernel/shmmax 
  2.  
  3. 33554432 

(1)创建65M的system V共享内存失败:


  1. # ipcmk -M 68157440 
  2.  
  3. ipcmk: create share memory failed: Invalid argument 

这是正常的。

(2)将shmmax调整为65M


  1. # echo 68157440 > /proc/sys/kernel/shmmax# cat /proc/sys/kernel/shmmax 
  2.  
  3. 68157440# ipcmk -M 68157440 
  4.  
  5. Shared memory id: 0# ipcs -m 
  6.  
  7. ------ Shared Memory Segments -------- 
  8.  
  9. key shmid owner perms bytes nattch status 
  10.  
  11. 0xef46b249 0 root 644 68157440 0 

可以看到system v共享内存的大小并不受/dev/shm的影响。

(3)创建POSIX共享内存

点击(此处)折叠或打开


  1.     /*gcc -o shmopen shmopen.c -lrt*/#include <unistd.h> 
  2.  
  3.     #include <fcntl.h> 
  4.  
  5.     #include <sys/stat.h> 
  6.  
  7.     #include <sys/types.h> 
  8.  
  9.     #include <sys/mman.h> 
  10.  
  11.     #include <stdio.h> 
  12.  
  13.     #include <stdlib.h> 
  14.  
  15.     #define MAP_SIZE 68157440 
  16.  
  17.     int main(int argc, char *argv[]) 
  18.  
  19.     { 
  20.  
  21.         int fd; 
  22.  
  23.         void* result; 
  24.  
  25.         fd = shm_open("/shm1", O_RDWR|O_CREAT, 0644); 
  26.  
  27.         if(fd < 0){ 
  28.  
  29.             printf("shm_open failed\n"); 
  30.  
  31.             exit(1); 
  32.  
  33.         } 
  34.  
  35.         return 0; 
  36.  
  37.     } 
  38.  
  39.  
  40. # ./shmopen# ls -lh /dev/shm/shm1 
  41.  
  42. -rw-r--r-- 1 root root 65M Mar  3 06:19 /dev/shm/shm1 

仅管/dev/shm只有64M,但创建65M的POSIX SM也可以成功。

(4)向POSIX SM写数据

点击(此处)折叠或打开


  1.     /*gcc -o shmwrite shmwrite.c -lrt*/#include <unistd.h> 
  2.  
  3.     #include <fcntl.h> 
  4.  
  5.     #include <sys/stat.h> 
  6.  
  7.     #include <sys/types.h> 
  8.  
  9.     #include <sys/mman.h> 
  10.  
  11.     #include <stdio.h> 
  12.  
  13.     #include <stdlib.h> 
  14.  
  15.     #define MAP_SIZE 68157440 
  16.  
  17.     int main(int argc, char *argv[]) 
  18.  
  19.     { 
  20.  
  21.         int fd; 
  22.  
  23.         void* result; 
  24.  
  25.         fd = shm_open("/shm1", O_RDWR|O_CREAT, 0644); 
  26.  
  27.         if(fd < 0){ 
  28.  
  29.              printf("shm_open failed\n"); 
  30.  
  31.              exit(1); 
  32.  
  33.         } 
  34.  
  35.         if (ftruncate(fd, MAP_SIZE) < 0){ 
  36.  
  37.             printf("ftruncate failed\n"); 
  38.  
  39.             exit(1); 
  40.  
  41.         } 
  42.  
  43.         result = mmap(NULL, MAP_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 
  44.  
  45.         if(result == MAP_FAILED){ 
  46.  
  47.             printf("mapped failed\n"); 
  48.  
  49.             exit(1); 
  50.  
  51.         } 
  52.  
  53.         /* ... operate result pointer */ 
  54.  
  55.         printf("memset\n"); 
  56.  
  57.         memset(result, 0, MAP_SIZE); 
  58.  
  59.         //shm_unlink("/shm1"); 
  60.  
  61.         return 0; 
  62.  
  63.     } 
  64.  
  65.  
  66. # ./shmwrite 
  67.  
  68. memset 
  69.  
  70. Bus error 

可以看到,写65M的数据会报Bus error错误。

但是,却可以在/dev/shm创建新的文件:


  1. # ls -lh /dev/shm/ -lh 
  2.  
  3. 总用量 64M 
  4.  
  5. -rw-r--r-- 1 root root 65M 3月 3 15:23 shm1 
  6.  
  7. -rw-r--r-- 1 root root 65M 3月 3 15:24 shm2 
  8.  
  9. 这很正常,ls显示的是inode->size。 
  10.  
  11. # stat /dev/shm/shm2 
  12.  
  13. File: "/dev/shm/shm2" 
  14.  
  15. Size: 68157440 Blocks: 0 IO Block: 4096 普通文件 
  16.  
  17. Device: 10h/16d Inode: 217177 Links: 1 
  18.  
  19. Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) 
  20.  
  21. Access: 2015-03-03 15:24:28.025985167 +0800 
  22.  
  23. Modify: 2015-03-03 15:24:28.025985167 +0800 
  24.  
  25. Change: 2015-03-03 15:24:28.025985167 +0800 

(5)向SYS V共享内存写数据

将System V共享内存的最大值调整为65M(/dev/shm仍然为64M)。


  1. # cat /proc/sys/kernel/shmmax 
  2.  
  3. 68157440 

点击(此处)折叠或打开


  1. /*gcc -o shmv shmv.c*/#include <sys/ipc.h> 
  2.  
  3. #include <sys/shm.h> 
  4.  
  5. #include <sys/types.h> 
  6.  
  7. #include <unistd.h> 
  8.  
  9. #define MAP_SIZE 68157440 
  10.  
  11. int main(int argc, char** argv){ 
  12.  
  13. int shm_id,i; 
  14.  
  15. key_t key; 
  16.  
  17. char temp; 
  18.  
  19. char *p_map; 
  20.  
  21. char* name = "/dev/shm/shm3"; 
  22.  
  23. key = ftok(name,0); 
  24.  
  25. if(key==-1) 
  26.  
  27. perror("ftok error"); 
  28.  
  29. shm_id=shmget(key,MAP_SIZE,IPC_CREAT); 
  30.  
  31. if(shm_id==-1) 
  32.  
  33.  
  34. perror("shmget error"); 
  35.  
  36. return; 
  37.  
  38.  
  39. p_map=(char*)shmat(shm_id,NULL,0); 
  40.  
  41. memset(p_map, 0, MAP_SIZE); 
  42.  
  43. if(shmdt(p_map)==-1) 
  44.  
  45. perror(" detach error "); 
  46.  
  47.  
  48. #./shmv 

却可以正常执行。

(7)结论

虽然System V与POSIX共享内存都是通过tmpfs实现,但是受的限制却不相同。也就是说/proc/sys/kernel/shmmax只会影响SYS V共享内存,/dev/shm只会影响Posix共享内存。实际上,System V与Posix共享内存本来就是使用的两个不同的tmpfs实例(instance)。

内核分析

内核在初始化时,会自动mount一个tmpfs文件系统,挂载为shm_mnt:

点击(此处)折叠或打开


  1. //mm/shmem.cstatic struct file_system_type  
  2.  
  3. shmem_fs_type = { 
  4.  
  5.     .owner = THIS_MODULE, 
  6.  
  7.    .name = "tmpfs", 
  8.  
  9.     .get_sb = shmem_get_sb, 
  10.  
  11.     .kill_sb = kill_litter_super, 
  12.  
  13. }; 
  14.  
  15.  
  16. int __init shmem_init(void) { 
  17.  
  18.     ... 
  19.  
  20.     error = register_filesystem(&shmem_fs_type); 
  21.  
  22.     if (error)  
  23.  
  24.     { 
  25.  
  26.         printk(KERN_ERR "Could not register tmpfs\n"); 
  27.  
  28.         goto out2; 
  29.  
  30.     } 
  31.  
  32.     ///挂载tmpfs(用于SYS V)  
  33.  
  34.     shm_mnt = vfs_kern_mount(&shmem_fs_type, MS_NOUSER,shmem_fs_type.name, NULL); 

/dev/shm的mount与普通文件mount的流程类似,不再讨论。但是,值得注意的是,/dev/shm默认的大小为当前物理内存的1/2:


  1. shmem_get_sb –> shmem_fill_super 

点击(此处)折叠或打开


  1. //mem/shmem.c 
  2.  
  3. int shmem_fill_super(struct super_block *sb, void *data, int silent) 
  4.  
  5.  
  6.     ... 
  7.  
  8. #ifdef CONFIG_TMPFS  
  9.  
  10. /* 
  11.  
  12. * Per default we only allow half of the physical ram per 
  13.  
  14. * tmpfs instance, limiting inodes to one per page of lowmem; 
  15.  
  16. * but the internal instance is left unlimited. 
  17.  
  18. */ 
  19.  
  20.     if (!(sb->s_flags & MS_NOUSER)) {///内核会设置MS_NOUSER  
  21.  
  22.         sbinfo->max_blocks = shmem_default_max_blocks(); 
  23.  
  24.         sbinfo->max_inodes = shmem_default_max_inodes(); 
  25.  
  26.         if (shmem_parse_options(data, sbinfo, false)) { 
  27.  
  28.             err = -EINVAL; 
  29.  
  30.             goto failed; 
  31.  
  32.         } 
  33.  
  34.     } 
  35.  
  36.     sb->s_export_op = &shmem_export_ops; 
  37.  
  38. #else 
  39.  
  40. ... 
  41.  
  42.  
  43. #ifdef CONFIG_TMPFS 
  44.  
  45. static unsigned long shmem_default_max_blocks(void) { 
  46.  
  47.     return totalram_pages / 2; 
  48.  

可以看到:由于内核在mount tmpfs时,指定了MS_NOUSER,所以该tmpfs没有大小限制,因此,SYS V共享内存能够使用的内存空间只受/proc/sys/kernel/shmmax限制;而用户通过挂载的/dev/shm,默认为物理内存的1/2。

注意CONFIG_TMPFS.

另外,在/dev/shm创建文件走VFS接口,而SYS V与匿名映射却是通过shmem_file_setup实现:

SIGBUS

当应用访问共享内存对应的地址空间,如果对应的物理PAGE还没有分配,就会调用fault方法,分配失败,就会返回OOM或者BIGBUS错误:

点击(此处)折叠或打开


  1. static const struct vm_operations_struct shmem_vm_ops = { 
  2.  
  3.     .fault = shmem_fault, 
  4.  
  5. #ifdef CONFIG_NUMA  
  6.  
  7.     .set_policy = shmem_set_policy, 
  8.  
  9.     .get_policy = shmem_get_policy, 
  10.  
  11. #endif 
  12.  
  13. }; 
  14.  
  15.  
  16. static int shmem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) 
  17.  
  18.  
  19.     struct inode *inode = vma->vm_file->f_path.dentry->d_inode; 
  20.  
  21.     int error; 
  22.  
  23.     int ret = VM_FAULT_LOCKED; 
  24.  
  25.     error = shmem_getpage(inode, vmf->pgoff, &vmf->page, SGP_CACHE, &ret); 
  26.  
  27.     if (error) 
  28.  
  29.         return ((error == -ENOMEM) ? VM_FAULT_OOM : VM_FAULT_SIGBUS); 
  30.  
  31.     return ret; 
  32.  
  33.  
  34.  
  35. shmem_getpage –> shmem_getpage_gfp: 
  36.  
  37. /* 
  38.  
  39.  * shmem_getpage_gfp - find page in cache, or get from swap, or allocate 
  40.  
  41.  * 
  42.  
  43.  * If we allocate a new one we do not mark it dirty. That's up to the 
  44.  
  45.  * vm. If we swap it in we mark it dirty since we also free the swap 
  46.  
  47.  * entry since a page cannot live in both the swap and page cache 
  48.  
  49.  */ 
  50.  
  51. static int shmem_getpage_gfp(struct inode *inode, pgoff_t index, 
  52.  
  53. struct page **pagep, enum sgp_type sgp, gfp_t gfp, int *fault_type)  
  54.  
  55.  
  56.     ... 
  57.  
  58.     if (sbinfo->max_blocks) { ///dev/shm会有该值  
  59.  
  60.         if (percpu_counter_compare(&sbinfo->used_blocks,sbinfo->max_blocks) >= 0) { 
  61.  
  62.             error = -ENOSPC; 
  63.  
  64.             goto unacct; 
  65.  
  66.         } 
  67.  
  68.     percpu_counter_inc(&sbinfo->used_blocks); 
  69.  
  70.     } 
  71.  
  72.     //分配一个物理PAGE 
  73.  
  74.     page = shmem_alloc_page(gfp, info, index); 
  75.  
  76.     if (!page) { 
  77.  
  78.         error = -ENOMEM; 
  79.  
  80.         goto decused; 
  81.  
  82.     } 
  83.  
  84.     SetPageSwapBacked(page); 
  85.  
  86.     __set_page_locked(page); 
  87.  
  88.     error = mem_cgroup_cache_charge(page, current->mm,gfp & GFP_RECLAIM_MASK); ///mem_cgroup检查 
  89.  
  90. if (!error) 
  91.  
  92.     error = shmem_add_to_page_cache(page, mapping, index, gfp, NULL); 

共享内存与CGROUP

目前,共享内存的空间计算在第一个访问共享内存的group,参考:

l http://lwn.net/Articles/516541/

l https://www.kernel.org/doc/Documentation/cgroups/memory.txt

POSIX共享内存与Docker

目前Docker将/dev/shm限制为64M,却没有提供参数,这种做法比较糟糕。如果应用使用大内存的POSIX共享内存,必然会导致问题。 参考:

l https://github.com/docker/docker/issues/2606

l https://github.com/docker/docker/pull/4981

总结

(1)POSIX共享内存与SYS V共享内存在内核都是通过tmpfs实现,但对应两个不同的tmpfs实例,相互独立。

(2)通过/proc/sys/kernel/shmmax可以限制SYS V共享内存(单个)的最大值,通过/dev/shm可以限制POSIX共享内存的最大值(所有之和)。

本文作者:佚名

来源:51CTO

时间: 2024-11-17 13:09:43

浅析Linux的共享内存与tmpfs文件系统的相关文章

linux下共享内存mmap和DMA(直接访问内存)的使用 【转】

转自:http://blog.chinaunix.net/uid-7374279-id-4413316.html 介绍Linux内存管理和内存映射的奥秘.同时讲述设备驱动程序是如何使用"直接内存访问"(DMA)的.尽管你可能反对,认为DMA更属于硬件处理而不是软件接口,但我觉得与硬件控制比起来,它与内存管理更相关.这一章比较高级:大多数驱动程序的作者并不需要太深入到系统内部.不过理解内存如何工作可以帮助你在设计驱动程序时有效地利用系统的能力.       共 享内存可以说是最有用的进程

Linux中共享内存及内存映射技术研究

Linux给我们提供了丰富的内部进程通信机制,包括共享内存.内存映射文件.先入先出(FIFO).接口(sockets)以及多种用于同步的标识.在本文中,我们主要讨论一下共享内存和内存映射文件技术. 一般来说,内部进程通信(interprocess communication)也就是IPC,是指两个或两个以上进程以及两个或者两个以上线程之间进行通信联系.每个IPC机制都有不同的强项或者弱点,不过没有一个IPC机制包含内建的同步方法.因此程序员不但需要自己在程序中实现同步,而且还需要为了利用IPC机

Linux的共享内存及内存映射

一.POSIX共享内存的实现 共享内存是在进程间共享某一块内存.是最快一种ipc通信机构.其中posix共享内存机制 它主要是通过内存映射(mmap)机制来实现的. 在进程间共享内存使用如下固定步骤: 1.创建一个共享内存 int shm_open(const char *name, int oflag, mode_t mode); name是共享内存名字,各个进程通过名字来找到同一块内存. oflag,是这个内存属性.类似于文件属性.使用O_RDWR/O_RDONLY/O_CREAT,第一次创

linux操作系统修改共享内存的简单方法

近日在优化oracle的时候,遇到了oracle的SGA大小远远大于linux系统共享内存的最大值,以至于当我用ipcs命令查看的时候,oracle的SGA区被分成了10个段,这大大影响的oracle的反应速度.后来经过查阅资料,得出一下简便的修改共享内存的方法,和大家共享. 一般的以为修改系统共享内存需要修改内核程序,然后make,很麻烦.其实,只要在rc.local的加入一点东西,就可以免去修改内核的麻烦.修改方法如下:修改/etc/rc.d/rc.local文件. 在文件的前面注释的后面加

Linux 共享内存 详解

一.什么是共享内存区 共享内存区是最快的可用IPC形式.它允许多个不相关的进程去访问同一部分逻辑内存.如果需要在两个运行中的进程之间传输数据,共享内存将是一种效率极高的解决方案.一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传输就不再涉及内核.这样就可以减少系统调用时间,提高程序效率. 共享内存是由IPC为一个进程创建的一个特殊的地址范围,它将出现在进程的地址空间中.其他进程可以把同一段共享内存段"连接到"它们自己的地址空间里去.所有进程都可以访问共享内存中的地址.如果

Linux中进程内存RSS与cgroup内存的RSS统计 - 差异

转载一篇关于进程内存计算和CGROUP内存计算差异的文章http://hustcat.github.io/memory-usage-in-process-and-cgroup/ 在Linux内核,对于进程的内存使用与Cgroup的内存使用统计有一些相同和不同的地方.进程的内存统计一般来说,业务进程使用的内存主要有以下几种情况:(1)用户空间的匿名映射页(Anonymous pages in User Mode address spaces),比如调用malloc分配的内存,以及使用MAP_ANO

linux网络编程之POSIX共享内存和系列函数

在前面介绍了system v 共享内存的相关知识,现在来稍微看看posix 共享内存 和系列函数. 共享内存简单来说 就是一块真正的物理内存区域,可以使用一些函数将这块区域映射到进程的地址空间进行读写,而posix 共享内存与system v 共享内存不同的是它是用虚拟文件系统(tmpfs)实现的,已经挂载在/dev/shm 下面.man 7 shm_overview 下面 来看系列函数,编译时候加上 -lrt 选项,即连接librt 库 (实时库) 功能:用来创建或打开一个共享内存对象 原型

unix/linux共享内存应用与陷阱

共享内存是系统出于多个进程之间通讯的考虑,而预留的的一块内存区.在/proc/sys/kernel/目录下,记录着共享内存的一些限制,如一个共享内存区的最大字节数shmmax,系统范围内最大共享内存区标识符数shmmni等,可以手工对其调整,但不推荐这样做. 一.应用 共享内存的使用,主要有以下几个API:ftok().shmget().shmat().shmdt()及shmctl(). 1)用ftok()函数获得一个ID号. 应用说明: 在IPC中,我们经常用用key_t的值来创建或者打开信号

ipcs与Linux共享内存的示例

一.共享内存相关知识 所谓共享内存,就是多个进程间共同地使用同一段物理内存空间,它是通过将同一段物理内存映射到不同进程的 虚拟空间来实现的.由于映射到不同进程的虚拟空间中,不同进程可以直接使用,不需要像消息队列那样进行复制,所以共享内存的效率很高.共享内存可以通过mmap()映射普通文件机制来实现,也可以System V共享内存机制来实现,System V是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信,也就是说每个共享内存区域对应特殊文件系统shm中的一个文件. 二.共享内存原理