精华内容
下载资源
问答
  • Linux虚拟内存实现原理

    千次阅读 2012-07-18 10:23:44
    本文的目的就在于描述操作系统虚拟内存的使用及内存映射的内部实现。 以下是译文 当你运行一个程序,程序中有许多东西需要存储,堆、栈以及各种功能库。而这一切在你写程序时可能都不需要自己控
    
    

    我们都知道,MongoDB 使用内存映射的方式来进行数据文件的存取操作。本文的目的就在于描述操作系统虚拟内存的使用及内存映射的内部实现。

    以下是译文

    当你运行一个程序,程序中有许多东西需要存储,堆、栈以及各种功能库。而这一切在你写程序时可能都不需要自己控制,Linux内核会帮你完成这些存储的调度,你只需要告诉它你需要做什么,内核就会在合适的地方给你分配内存空间。本文主要通过几个实例程序的内存使用研究,来为大家展示Linux的内存使用状况。

    第一个例子:下面一段程序会打印出程序的pid(进程号)后挂起。

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    
    int main() {
      printf("run `pmap %d`\n", getpid());
      pause();
    }

    将上面代码保存成文件 mem_munch.c 然后运行下面程序编译并执行:

    $ gcc mem_munch.c -o mem_munch
    $ ./mem_munch
    run `pmap 25681`

    上面进程号是25681,可能你试验的结果会不太一样。

    下面我们通过pmap命令来查看一下这个小程序的内存使用情况

    $ pmap 25681
    25681:   ./mem_munch
    0000000000400000      4K r-x--  /home/user/mem_munch
    0000000000600000      4K r----  /home/user/mem_munch
    0000000000601000      4K rw---  /home/user/mem_munch
    00007fcf5af88000   1576K r-x--  /lib/x86_64-linux-gnu/libc-2.13.so
    00007fcf5b112000   2044K -----  /lib/x86_64-linux-gnu/libc-2.13.so
    00007fcf5b311000     16K r----  /lib/x86_64-linux-gnu/libc-2.13.so
    00007fcf5b315000      4K rw---  /lib/x86_64-linux-gnu/libc-2.13.so
    00007fcf5b316000     24K rw---    [ anon ]
    00007fcf5b31c000    132K r-x--  /lib/x86_64-linux-gnu/ld-2.13.so
    00007fcf5b512000     12K rw---    [ anon ]
    00007fcf5b539000     12K rw---    [ anon ]
    00007fcf5b53c000      4K r----  /lib/x86_64-linux-gnu/ld-2.13.so
    00007fcf5b53d000      8K rw---  /lib/x86_64-linux-gnu/ld-2.13.so
    00007fff7efd8000    132K rw---    [ stack ]
    00007fff7efff000      4K r-x--    [ anon ]
    ffffffffff600000      4K r-x--    [ anon ]
     total             3984K

    上面的结果是这个程序的内存使用情况,其实更确切的说是这个程序认为它使用内存的情况。从上面的结果我们能看到,当你访问libc库时,实际上是对内存地址00007fcf5af88000的访问,当你访问ld库时,实际上是对内存地址00007fcf5b31c000的访问。

    上面的输出可能还比较抽象,下面我们修改一下上面的程序,我们在程序的堆和栈上各放一块数据。

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <stdlib.h>
    
    int main() {
      int on_stack, *on_heap;
    
      //局部变量是放在栈上的,所以 on_stack 的地址就是栈的初始地址
      on_stack = 42;
      printf("stack address: %p\n", &on_stack);
    
      //malloc 的内存是在堆上分配的
      on_heap = (int*)malloc(sizeof(int));
      printf("heap address: %p\n", on_heap);
    
      printf("run `pmap %d`\n", getpid());
      pause();
    }

    编译运行:

    $ ./mem_munch
    stack address: 0x7fff497670bc
    heap address: 0x1b84010
    run `pmap 11972`

    然后再用pmap命令查看一下内存使用:

    $ pmap 11972
    11972:   ./mem_munch
    0000000000400000      4K r-x--  /home/user/mem_munch
    0000000000600000      4K r----  /home/user/mem_munch
    0000000000601000      4K rw---  /home/user/mem_munch
    0000000001b84000    132K rw---    [ anon ]
    00007f3ec4d98000   1576K r-x--  /lib/x86_64-linux-gnu/libc-2.13.so
    00007f3ec4f22000   2044K -----  /lib/x86_64-linux-gnu/libc-2.13.so
    00007f3ec5121000     16K r----  /lib/x86_64-linux-gnu/libc-2.13.so
    00007f3ec5125000      4K rw---  /lib/x86_64-linux-gnu/libc-2.13.so
    00007f3ec5126000     24K rw---    [ anon ]
    00007f3ec512c000    132K r-x--  /lib/x86_64-linux-gnu/ld-2.13.so
    00007f3ec5322000     12K rw---    [ anon ]
    00007f3ec5349000     12K rw---    [ anon ]
    00007f3ec534c000      4K r----  /lib/x86_64-linux-gnu/ld-2.13.so
    00007f3ec534d000      8K rw---  /lib/x86_64-linux-gnu/ld-2.13.so
    00007fff49747000    132K rw---    [ stack ]
    00007fff497bb000      4K r-x--    [ anon ]
    ffffffffff600000      4K r-x--    [ anon ]
     total             4116K

    这次多出了上面红色的一行内容,红色内容就是堆的起始位置:

    0000000001b84000    132K rw---    [ anon ]

    在我们程序运行的输出里也有一行红色的输出,这是这个地址在程序中的内存地址:

    heap address: 0x1b84010

    这两个地址基本上是一样的,其中的anon是Anonymous的缩写,表明这段内存是没有文件映射的。

    我们再看上面绿色的两行,与上面相对应,这两行分别是用pmap 和应用程序看到的栈起始地址:

    00007fff49747000    132K rw---    [ stack ]
    stack address: 0x7fff497670bc

    上面说到的内存使用,都只是程序认为自己对内存的使用,实际上程序在分配内存是不知道系统内存的状态的。所以上面的输出都只是从程序自己的角度看到的内存使用状况。比如在上面的例子中,我们看到程序的内存地址空间是从0×0000000000400000到0xffffffffff600000的所有地址(而0xffffffffff600000到0×00007fffffffffffffff之间的地址是有特殊用处的,这里不多讲)。这样算下来,我们总共可以使用的内存空间有1千万TB。

    但是实际上目前没有硬件能有1千万TB的物理内存。为什么操作系统会如此设计呢?原因有很多,可以看这里,但也正因此,我们可以使用远远超出物理内存大小的内存空间。

    内存映射

    内存映射的原理就是让操作系统将一个文件映射到一段内存中,然后在操作这个文件内存就可以像操作内存一样。比如我们创建一个完全内容随机的文件,然后将它用内存映射的方式映射到一段内存空间中。那么我们在这段内存中随便取一位就相当于取到了一个随机数。下面就让我们来做这个实验,先用下面命令生成一个内容随机的文件。

    $ dd if=/dev/urandom bs=1024 count=1000000 of=/home/user/random
    1000000+0 records in
    1000000+0 records out
    1024000000 bytes (1.0 GB) copied, 123.293 s, 8.3 MB/s
    $ ls -lh random
    -rw-r--r-- 1 user user 977M 2011-08-29 16:46 random

    然后我们用下面程序来将这个文件内容映射到内存,再从中取出随机数

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <stdlib.h>
    #include <sys/mman.h>
    
    int main() {
      char *random_bytes;
      FILE *f;
      int offset = 0;
    
      // open "random" for reading
      f = fopen("/home/user/random", "r");
      if (!f) {
        perror("couldn't open file");
        return -1;
      }
    
      // we want to inspect memory before mapping the file
      printf("run `pmap %d`, then press ", getpid());
      getchar();
    
      random_bytes = mmap(0, 1000000000, PROT_READ, MAP_SHARED, fileno(f), 0);
    
      if (random_bytes == MAP_FAILED) {
        perror("error mapping the file");
        return -1;
      }
    
      while (1) {
        printf("random number: %d (press  for next number)", *(int*)(random_bytes+offset));
        getchar();
    
        offset += 4;
      }
    }

    然后运行这个程序:

     $ ./mem_munch
    run `pmap 12727`, then press

    下面我们通过一次次的按下回车键来从这个文件中读取随机数,按下几次后我们可以再通过pmap来查看其内存空间的情况:

    $ pmap 12727
    12727:   ./mem_munch
    0000000000400000      4K r-x--  /home/user/mem_munch
    0000000000600000      4K r----  /home/user/mem_munch
    0000000000601000      4K rw---  /home/user/mem_munch
    000000000147d000    132K rw---    [ anon ]
    00007fe261c6f000 976564K r--s-  /home/user/random
    00007fe29d61c000   1576K r-x--  /lib/x86_64-linux-gnu/libc-2.13.so
    00007fe29d7a6000   2044K -----  /lib/x86_64-linux-gnu/libc-2.13.so
    00007fe29d9a5000     16K r----  /lib/x86_64-linux-gnu/libc-2.13.so
    00007fe29d9a9000      4K rw---  /lib/x86_64-linux-gnu/libc-2.13.so
    00007fe29d9aa000     24K rw---    [ anon ]
    00007fe29d9b0000    132K r-x--  /lib/x86_64-linux-gnu/ld-2.13.so
    00007fe29dba6000     12K rw---    [ anon ]
    00007fe29dbcc000     16K rw---    [ anon ]
    00007fe29dbd0000      4K r----  /lib/x86_64-linux-gnu/ld-2.13.so
    00007fe29dbd1000      8K rw---  /lib/x86_64-linux-gnu/ld-2.13.so
    00007ffff29b2000    132K rw---    [ stack ]
    00007ffff29de000      4K r-x--    [ anon ]
    ffffffffff600000      4K r-x--    [ anon ]
     total           980684K

    上面的输出和之前的大同小异,但是多出了上面红色的一行。这是我们上面的随机文件映射到内存中的内存。我们再使用pmap -x 选项来查看一下程序的内存使用,会得到下面的内容,其中RSS(resident set size)列表示真实占用的内存。

    pmap -x 12727
    12727:   ./mem_munch
    Address           Kbytes     RSS   Dirty Mode   Mapping
    0000000000400000       0       4       0 r-x--  mem_munch
    0000000000600000       0       4       4 r----  mem_munch
    0000000000601000       0       4       4 rw---  mem_munch
    000000000147d000       0       4       4 rw---    [ anon ]
    00007fe261c6f000       0       4       0 r--s-  random
    00007fe29d61c000       0     288       0 r-x--  libc-2.13.so
    00007fe29d7a6000       0       0       0 -----  libc-2.13.so
    00007fe29d9a5000       0      16      16 r----  libc-2.13.so
    00007fe29d9a9000       0       4       4 rw---  libc-2.13.so
    00007fe29d9aa000       0      16      16 rw---    [ anon ]
    00007fe29d9b0000       0     108       0 r-x--  ld-2.13.so
    00007fe29dba6000       0      12      12 rw---    [ anon ]
    00007fe29dbcc000       0      16      16 rw---    [ anon ]
    00007fe29dbd0000       0       4       4 r----  ld-2.13.so
    00007fe29dbd1000       0       8       8 rw---  ld-2.13.so
    00007ffff29b2000       0      12      12 rw---    [ stack ]
    00007ffff29de000       0       4       0 r-x--    [ anon ]
    ffffffffff600000       0       0       0 r-x--    [ anon ]
    ----------------  ------  ------  ------
    total kB          980684     508     100

    如果你的虚拟内存占用(上面的Kbytes列)都是0,不用担心,这是一个在Debian/Ubuntu系统上pmap -x命令的bug。最后一行输出的总占用量是正确的。

    现在你可以看一下RSS那一列,这就是实际内存占用。在random文件上,你的程序实际上可以访问在00007fe261c6f000之前的数十亿字节的内存地址,但是只要你访问的地址超过4KB,那么操作系统就会去磁盘上查找内容。也就是说实际上只有4KB的物理内存被使用了。只有访问这4KB的东西时,才是真正的内存操作。其它部分虽然你使用的也是内存操作函数来访问它,但是由于它没有被加载到内存中,所以在这些内容被访问的时候,操作系统会先去磁盘读random中读取内容到内存中。

    如果我们把程序再修改一下,修改成下面这样,让程序把整个random文件都访问一遍。

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <stdlib.h>
    #include <sys/mman.h>
    
    int main() {
      char *random_bytes;
      FILE *f;
      int offset = 0;
    
      // open "random" for reading
      f = fopen("/home/user/random", "r");
      if (!f) {
        perror("couldn't open file");
        return -1;
      }
    
      random_bytes = mmap(0, 1000000000, PROT_READ, MAP_SHARED, fileno(f), 0);
    
      if (random_bytes == MAP_FAILED) {
        printf("error mapping the file\n");
        return -1;
      }
    
      for (offset = 0; offset < 1000000000; offset += 4) {
        int i = *(int*)(random_bytes+offset);
    
        // to show we're making progress
        if (offset % 1000000 == 0) {
          printf(".");
        }
      }
    
      // at the end, wait for signal so we can check mem
      printf("\ndone, run `pmap -x %d`\n", getpid());
      pause();
    }

    现在我们的pmap -x命令就会得到如下输出:

    $ pmap -x 5378
    5378:   ./mem_munch
    Address           Kbytes     RSS   Dirty Mode   Mapping
    0000000000400000       0       4       4 r-x--  mem_munch
    0000000000600000       0       4       4 r----  mem_munch
    0000000000601000       0       4       4 rw---  mem_munch
    0000000002271000       0       4       4 rw---    [ anon ]
    00007fc2aa333000       0  976564       0 r--s-  random
    00007fc2e5ce0000       0     292       0 r-x--  libc-2.13.so
    00007fc2e5e6a000       0       0       0 -----  libc-2.13.so
    00007fc2e6069000       0      16      16 r----  libc-2.13.so
    00007fc2e606d000       0       4       4 rw---  libc-2.13.so
    00007fc2e606e000       0      16      16 rw---    [ anon ]
    00007fc2e6074000       0     108       0 r-x--  ld-2.13.so
    00007fc2e626a000       0      12      12 rw---    [ anon ]
    00007fc2e6290000       0      16      16 rw---    [ anon ]
    00007fc2e6294000       0       4       4 r----  ld-2.13.so
    00007fc2e6295000       0       8       8 rw---  ld-2.13.so
    00007fff037e6000       0      12      12 rw---    [ stack ]
    00007fff039c9000       0       4       0 r-x--    [ anon ]
    ffffffffff600000       0       0       0 r-x--    [ anon ]
    ----------------  ------  ------  ------
    total kB          980684  977072     104

    我们可以看到,random文件映射实际占用内存量已经和random文件大小一致了,也就是也random文件通过循环访问,其内容已经完全加载到内存中了。现在我们再访问random文件的任何部分,实际上都是内存操作。而不会穿透到磁盘。

    话说回来,这就是为什么MongoDB的内存使用,可以远远超出操作系统物理内存大小。

    展开全文
  • 虚拟内存原理

    千次阅读 2019-10-05 10:36:58
    虚拟内存:为了更加高效并且少出错,现代操作系统提供了一种对主存的抽象概念,叫做虚拟内存虚拟内存是硬件异常,硬件地址翻译,主存,磁盘文件和内核软件的完美交互,他为每个进程提供了一个大的,一致和私有的...

    虚拟内存:为了更加高效并且少出错,现代操作系统提供了一种对主存的抽象概念,叫做虚拟内存。虚拟内存是硬件异常,硬件地址翻译,主存,磁盘文件和内核软件的完美交互,他为每个进程提供了一个大的,一致和私有的地址空间,虚拟内存提供三个能力:
    他将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在主存和磁盘之间来回传送数据,通过这种方式高效使用了主存
    他为每个进程提供了一致的地址空间,从而简化了内存管理
    他保护了每个进程的地址空间不被其他进程破坏

    物理和虚拟寻址
    计算机主存被组织成一个由M个连续的字节大小的单元组成的数组。每字节都有一个唯一的物理地址,每一个字节的地址为0,接下来的字节地址为1,在下一个为2,以此类推,给定这种简单的结构,CPU访问内存的最自然的方式就是使用物理地址,我们把这种方式称为物理寻址。
    现在操作系统都是用虚拟寻址的寻址形式。CPU通过生成一个虚拟地址来访问内存,这个虚拟地址在送到内存之前会被转换成适当的物理地址,将虚拟地址转换成物理地址这一过程成为地址翻译。需要硬件系统个操作系统之间的紧密配合,CPU芯片上的叫做内存管理单元的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表的内容由操作系统管理。
    地址空间:地址空间是一个由非负整数组成的有序集合。一个地址空间大小是由表示最大地址所需要的位数描述的,例如一个包含N=2^n个地址的虚拟地址空间就叫做一个n位地址空间。现在操作系统通常支持32位或者64位寻地址空间。

    地址空间区分了数据对象和他们的属性,一旦认识到这种区别,我们就可以将其推广,允许每个数据对象有多个独立的地址空间,其中每个地址都选自不同的地址空间,这是虚拟内存的基本思想。主存中的每个字节都以一个选自虚拟地址空间和一个选自物理地址空间的物理地址。

    虚拟内存作为缓存的工具:
    虚拟内存被组织成一个有存放在磁盘上的N个连续的字节大小的单元组成的数组,每字节都有唯一的虚拟地址,作为数组的索引,磁盘上的内容被缓存在主存中,和存储器层次结构中的其他缓存一样,磁盘上的数据被分割成块,这些快被分割成磁盘和主存之间的传输单元,VM系统通过将虚拟内存分割成称为虚拟页的大小固定的块来处理这个问题,每个虚拟页的大小为P=2^p字节,类似的,物理内存被分割成物理页,大小为p字节。被称为页帧。

    在任意时刻,虚拟页面都分为三个不相交的子集:
    未分配的:VM系统还未分配的页,未分配的块没有任何数据和他们相关联,因此,也就不占用任何磁盘空间。
    缓存的:当前已经缓存在物理内存中的已经分配的页
    为缓存的:未缓存到物理内存中已分配的页

    DRAM缓存的组织结构:
    SRAM为CPU和主存之间的L1,L2,L3高速缓存
    DRAM表示虚拟内存系统的缓存,他在主存中缓存虚拟页
    DRAM要比SRAM慢10倍,而磁盘要比DRAM慢大约100000倍,因此DRAM不命中比起SRAM不命中要昂贵的多。因为DRAM缓存不命中是由磁盘来服务的。

    在习惯说法中,DRAM缓存不命中,我们称为缺页。

    一次缺页的处理过程:
    在这里插入图片描述
    如上图所示:CPU引用了vp3一个字,vp3并未缓存在内存中(DRAM)中,地址翻译硬件会从内存中读取PTE3,从有效位推断vp3未被缓存,并触发一个缺页异常,缺页异常调用内核中的缺页异常处理程序,改程序会选择一个牺牲页,在此例中就是vp4,如果vp4被修改了,那么内核就会将其复制回磁盘,无论哪种情况,内核都会修改vp4的页表条目PTE4,反应vp4不再缓存在主存中这一事实。
    接下来,内核从磁盘复制vp3到内存中的原来vp4的位置,更新PTE3,随后返回,当异常处理程序返回时,他会重新启动导致缺页的指令,该指令会将导致缺页的虚拟地址发送给地址翻译硬件,现在vp3已经被缓存到主存中了,那么页命中也能由地址翻译硬件处理了。
    下面是完成却也异常处理的示意图:

    在这里插入图片描述
    在虚拟内存的说法中,块被称为页,在磁盘和内存之间传送页的活动叫做页面调度。页从磁盘换入DRAM和从DRAM换出磁盘,一直到最后时刻,也就是当不命中发生时,才换入页面的这种策略,称为按需页面调度。
    调用malloc会使磁盘新的页面在磁盘上产生。
    局部性
    尽管在整个运行期间程序引用的不同页面的总数可能超出物理内存总大小,但是局部性原则保证了在任意时刻,程序趋向在一个较小活动页面上工作,这个集合叫做工作集,或者常驻集合,在初始开销,将工作集页面调度到主存之后,接下来对这个工作集的引用将导致命中,而不会产生额外开销。只要程序有好的时间局部性,虚拟内存系统就能工作相当好,如果工作集的大小超出了物理内存的大小,程序将产生一种不幸的状态,叫做抖动,这个时候页面会被不停的换进与换出。使得程序性能下降。

    按需页面调度和独立的虚拟地址空间的结合,对系统中内存的使用和管理造成了深远的影响,VM简化了连接与加载,代码和数据共享,以及应用程序的内存分配。

    简化链接:独立的地址空间允许每个进程的内存映像使用相同的基本格式,而不管代码和数据实际存放在物理内存何处。
    简化加载:虚拟内存还使得容易想内存中加载可执行文件和共享对象文件。
    将一组连续的虚拟页映射到任意一个文件中的任意位置表示法称为内存映射。
    简化共享:独立的地址空间为操作系统提供了一个管理用户进程和操作系统的自身之间共享的一致机制。一般而言,每个进程都有自己私有的代码、数据、堆以及栈区,是不和其他进程共享的,在这种情况下,操作系统创建页表,将相应的虚拟页映射到不连续的物理页面。
    在一些情况下,还是需要进程来共享数据和代码,比如每个进程必须调用相同的操作系统内核代码,而每个C程序都会调用C标准库中的程序,操作系统通过将不同进程中适当的页面映射到相同的物理页面,从而安排多个进程共享这部分代码的副本,而不是每个进程单独拥有内核或者C标准库。
    简化内存分配:虚拟内存为用户提供了简化内存分配的机制,当一个运行在用户进程中的程序要求分配额外的堆空间的话,操作系统分配相应适当的数字个连续个虚拟内存页面,并且将它们映射到物理内存中任意位置的k个任意的物理页面,由于页表的工作方式,没必要分配连续的物理页面,而是随机分散在物理内存中。

    虚拟内存作为内存保护的工具:
    现代计算机系统必须为操作系统提供手段来控制对内存系统的访问,不应该允许一个进程修改他的只读代码段,而且也不用改允许他读或者修改任何内核中的代码或者数据结构。不应该允许他度或者写其他进程的私有内存,并且不允许他修改与其他进程共享的虚拟页面,除非共享者都显式的允许他这么做。
    每次CPU生成一个地址时,地址翻译硬件都会读一个PTE,所以通过在PTE上添加一些额外的许可位来控制对一个虚拟页面的访问十分简单。

    在这里插入图片描述

    用虚拟内存提供页面级的内存保护

    当用户进程访问一些页面越过权限时,就会触发内核异常处理程序,报告段错误,并强制退出程序。

    展开全文
  • 文章目录0.思维导图1.传统存储管理的特征、缺点2.局部性原理3....如何实现虚拟内存技术 0.思维导图 1.传统存储管理的特征、缺点 2.局部性原理 3.虚拟内存的定义和特征 4.如何实现虚拟内存技术 ...


    0.思维导图

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    1.传统存储管理的特征、缺点

    在这里插入图片描述

    2.局部性原理

    在这里插入图片描述

    3.虚拟内存的定义和特征

    在这里插入图片描述
    在这里插入图片描述

    4.如何实现虚拟内存技术

    在这里插入图片描述

    展开全文
  • 共享内存实现原理

    千次阅读 2018-09-28 18:02:39
    共享内存的使用实现原理(必考必问,然后共享内存段被映射进进程空间之后,存在于进程空间的什么位置?共享内存段最大限制是多少?) nmap函数要求内核创建一个新额虚拟存储器区域,最好是从地质start开始的一个...

    共享内存的使用实现原理(必考必问,然后共享内存段被映射进进程空间之后,存在于进程空间的什么位置?共享内存段最大限制是多少?)

    nmap函数要求内核创建一个新额虚拟存储器区域,最好是从地质start开始的一个区域,并将文件描述符fd指定对象的一个连续的片(chunk)映射到这个新的区域。

     SHMMNI为128,表示系统中最多可以有128个共享内存对象。

     

    共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。

    采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。

    Linux的2.2.x内核支持多种共享内存方式,如mmap()系统调用,Posix共享内存,以及系统V共享内存。linux发行版本如Redhat 8.0支持mmap()系统调用及系统V共享内存,但还没实现Posix共享内存,本文将主要介绍mmap()系统调用及系统V共享内存API的原理及应用。

    一、内核怎样保证各个进程寻址到同一个共享内存区域的内存页面

    1、page cache及swap cache中页面的区分:一个被访问文件的物理页面都驻留在page cache或swap cache中,一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ,它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。

    2、文件与address_space结构的对应:一个具体的文件在打开后,内核会在内存中为之建立一个struct inode结构,其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。

    3、进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。

    4、对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区(swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表。
    注:对于映射普通文件情况(非共享映射),缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到,则说明文件数据还没有读入内存,处理程序会从磁盘读入相应的页面,并返回相应地址,同时,进程页表也会更新。

    5、所有进程在映射同一个共享内存区域时,情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页面。
    注:一个共享内存区域可以看作是特殊文件系统shm中的一个文件,shm的安装点在交换区上。

    上面涉及到了一些数据结构,围绕数据结构理解问题会容易一些。

    二、mmap()及其相关系统调用

    mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

    注:实际上,mmap()系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。

    1、mmap()系统调用形式如下:

    void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
    参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。prot 参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读) , PROT_WRITE (可写), PROT_EXEC (可执行), PROT_NONE(不可访问)。flags由以下几个常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。offset参数一般设为0,表示从文件头开始映射。参数addr指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。这里不再详细介绍mmap()的参数,读者可参考mmap()手册页获得进一步的信息。

    2、系统调用mmap()用于共享内存的两种方式:

    (1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下: 

            fd=open(name, flag, mode);
    if(fd<0)
            ...
            
    ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0); 通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,我们将在范例中进行具体说明。 

    (2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。
    对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可,参见范例2。

    3、系统调用munmap()

    int munmap( void * addr, size_t len )
    该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。

    4、系统调用msync()

    int msync ( void * addr , size_t len, int flags)
    一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

    三、mmap()范例

    下面将给出使用mmap()的两个范例:范例1给出两个进程通过映射普通文件实现共享内存通信;范例2给出父子进程通过匿名映射实现共享内存。系统调用mmap()有许多有趣的地方,下面是通过mmap()映射普通文件实现进程间的通信的范例,我们通过该范例来说明mmap()实现共享内存的特点及注意事项。

    范例1:两个进程通过映射普通文件实现共享内存通信

    范例1包含两个子程序:map_normalfile1.c及map_normalfile2.c。编译两个程序,可执行文件分别为map_normalfile1及map_normalfile2。两个程序通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。map_normalfile2试图打开命令行参数指定的一个普通文件,把该文件映射到进程的地址空间,并对映射后的地址空间进行写操作。map_normalfile1把命令行参数指定的文件映射到进程地址空间,然后对映射后的地址空间执行读操作。这样,两个进程通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。

    下面是两个程序代码:

    /*-------------map_normalfile1.c-----------*/
    #include <sys/mman.h>;
    #include <sys/types.h>;
    #include <fcntl.h>;
    #include <unistd.h>;
    typedef struct{
            char name[4];
            int  age;
    }people;

    main(int argc, char** argv) // map a normal file as shared mem:
    {
            int fd,i;
            people *p_map;
            char temp;
            
            fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
            lseek(fd,sizeof(people)*5-1,SEEK_SET);
            write(fd,"",1);
            
            p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );
            close( fd );
            temp = 'a';
            for(i=0; i<10; i++)
            {
                    temp += 1;
                    memcpy( ( *(p_map+i) ).name, &temp,2 );
                    ( *(p_map+i) ).age = 20+i;
            }
            printf(" initialize over /n ");
            sleep(10);

            munmap( p_map, sizeof(people)*10 );
            printf( "umap ok /n" );
    }

    /*-------------map_normalfile2.c-----------*/
    #include <sys/mman.h>;
    #include <sys/types.h>;
    #include <fcntl.h>;
    #include <unistd.h>;
    typedef struct{
            char name[4];
            int  age;
    }people;

    main(int argc, char** argv)        // map a normal file as shared mem:
    {
            int fd,i;
            people *p_map;
            fd=open( argv[1],O_CREAT|O_RDWR,00777 );
            p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
            for(i = 0;i<10;i++)
            {
            printf( "name: %s age %d;/n",(*(p_map+i)).name, (*(p_map+i)).age );

            }
            munmap( p_map,sizeof(people)*10 );
    }

    map_normalfile1.c首先定义了一个people数据结构,(在这里采用数据结构的方式是因为,共享内存区的数据往往是有固定格式的,这由通信的各个进程决定,采用结构的方式有普遍代表性)。map_normfile1首先打开或创建一个文件,并把文件的长度设置为5个people结构大小。然后从mmap()的返回地址开始,设置了10个people结构。然后,进程睡眠10秒钟,等待其他进程映射同一个文件,最后解除映射。

    map_normfile2.c只是简单的映射一个文件,并以people数据结构的格式从mmap()返回的地址处读取10个people结构,并输出读取的值,然后解除映射。

    分别把两个程序编译成可执行文件map_normalfile1和map_normalfile2后,在一个终端上先运行./map_normalfile2 /tmp/test_shm,程序输出结果如下:

    initialize over
    umap ok

    在map_normalfile1输出initialize over 之后,输出umap ok之前,在另一个终端上运行map_normalfile2 /tmp/test_shm,将会产生如下输出(为了节省空间,输出结果为稍作整理后的结果):

    name: b        age 20;        name: c        age 21;        name: d        age 22;        name: e        age 23;        name: f        age 24;
    name: g        age 25;        name: h        age 26;        name: I        age 27;        name: j        age 28;        name: k        age 29;

    在map_normalfile1 输出umap ok后,运行map_normalfile2则输出如下结果:

    name: b        age 20;        name: c        age 21;        name: d        age 22;        name: e        age 23;        name: f        age 24;
    name:        age 0;        name:        age 0;        name:        age 0;        name:        age 0;        name:        age 0;

    从程序的运行结果中可以得出的结论

    1、 最终被映射文件的内容的长度不会超过文件本身的初始大小,即映射不能改变文件的大小;

    2、 可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小,但不完全受限于文件大小。打开文件被截短为5个people结构大小,而在map_normalfile1中初始化了10个people数据结构,在恰当时候(map_normalfile1输出initialize over 之后,输出umap ok之前)调用map_normalfile2会发现map_normalfile2将输出全部10个people结构的值,后面将给出详细讨论。
    注:在linux中,内存的保护是以页为基本单位的,即使被映射文件只有一个字节大小,内核也会为映射分配一个页面大小的内存。当被映射文件小于一个页面大小时,进程可以对从mmap()返回地址开始的一个页面大小进行访问,而不会出错;但是,如果对一个页面以外的地址空间进行访问,则导致错误发生,后面将进一步描述。因此,可用于进程间通信的有效地址空间大小不会超过文件大小及一个页面大小的和。

    3、 文件一旦被映射后,调用mmap()的进程对返回地址的访问是对某一内存区域的访问,暂时脱离了磁盘上文件的影响。所有对mmap()返回地址空间的操作只在内存中有意义,只有在调用了munmap()后或者msync()时,才把内存中的相应内容写回磁盘文件,所写内容仍然不能超过文件的大小。

    范例2:父子进程通过匿名映射实现共享内存

    #include <sys/mman.h>;
    #include <sys/types.h>;
    #include <fcntl.h>;
    #include <unistd.h>;
    typedef struct{
            char name[4];
            int  age;
    }people;
    main(int argc, char** argv)
    {
            int i;
            people *p_map;
            char temp;
            p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
            if(fork() == 0)
            {
                    sleep(2);
                    for(i = 0;i<5;i++)
                            printf("child read: the %d people's age is %d/n",i+1,(*(p_map+i)).age);
                    (*p_map).age = 100;
                    munmap(p_map,sizeof(people)*10); //实际上,进程终止时,会自动解除映射。
                    exit();
            }
            temp = 'a';
            for(i = 0;i<5;i++)
            {
                    temp += 1;
                    memcpy((*(p_map+i)).name, &temp,2);
                    (*(p_map+i)).age=20+i;
            }

            sleep(5);
            printf( "parent read: the first people,s age is %d/n",(*p_map).age );
            printf("umap/n");
            munmap( p_map,sizeof(people)*10 );
            printf( "umap ok/n" );
    }

    考察程序的输出结果,体会父子进程匿名共享内存:

    child read: the 1 people's age is 20
    child read: the 2 people's age is 21
    child read: the 3 people's age is 22
    child read: the 4 people's age is 23
    child read: the 5 people's age is 24

    parent read: the first people,s age is 100
    umap
    umap ok

    四、对mmap()返回地址的访问

    前面对范例运行结构的讨论中已经提到,linux采用的是页式管理机制。对于用mmap()映射普通文件来说,进程会在自己的地址空间新增一块空间,空间大小由mmap()的len参数指定,注意,进程并不一定能够对全部新增空间都能进行有效访问。进程能够访问的有效地址大小取决于文件被映射部分的大小。简单的说,能够容纳文件被映射部分大小的最少页面个数决定了进程从mmap()返回的地址开始,能够有效访问的地址空间大小。超过这个空间大小,内核会根据超过的严重程度返回发送不同的信号给进程。可用如下图示说明:





    注意:文件被映射部分而不是整个文件决定了进程能够访问的空间大小,另外,如果指定文件的偏移部分,一定要注意为页面大小的整数倍。下面是对进程映射地址空间的访问范例:

    #include <sys/mman.h>;
    #include <sys/types.h>;
    #include <fcntl.h>;
    #include <unistd.h>;
    typedef struct{
            char name[4];
            int  age;
    }people;

    main(int argc, char** argv)
    {
            int fd,i;
            int pagesize,offset;
            people *p_map;
            
            pagesize = sysconf(_SC_PAGESIZE);
            printf("pagesize is %d/n",pagesize);
            fd = open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
            lseek(fd,pagesize*2-100,SEEK_SET);
            write(fd,"",1);
            offset = 0;        //此处offset = 0编译成版本1;offset = pagesize编译成版本2
            p_map = (people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);
            close(fd);
            
            for(i = 1; i<10; i++)
            {
                    (*(p_map+pagesize/sizeof(people)*i-2)).age = 100;
                    printf("access page %d over/n",i);
                    (*(p_map+pagesize/sizeof(people)*i-1)).age = 100;
                    printf("access page %d edge over, now begin to access page %d/n",i, i+1);
                    (*(p_map+pagesize/sizeof(people)*i)).age = 100;
                    printf("access page %d over/n",i+1);
            }
            munmap(p_map,sizeof(people)*10);
    }

    如程序中所注释的那样,把程序编译成两个版本,两个版本主要体现在文件被映射部分的大小不同。文件的大小介于一个页面与两个页面之间(大小为:pagesize*2-99),版本1的被映射部分是整个文件,版本2的文件被映射部分是文件大小减去一个页面后的剩余部分,不到一个页面大小(大小为:pagesize-99)。程序中试图访问每一个页面边界,两个版本都试图在进程空间中映射pagesize*3的字节数。

    版本1的输出结果如下:

    pagesize is 4096
    access page 1 over
    access page 1 edge over, now begin to access page 2
    access page 2 over
    access page 2 over
    access page 2 edge over, now begin to access page 3
    Bus error                //被映射文件在进程空间中覆盖了两个页面,此时,进程试图访问第三个页面

    版本2的输出结果如下:

    pagesize is 4096
    access page 1 over
    access page 1 edge over, now begin to access page 2
    Bus error                //被映射文件在进程空间中覆盖了一个页面,此时,进程试图访问第二个页面

    结论:采用系统调用mmap()实现进程间通信是很方便的,在应用层上接口非常简洁。内部实现机制区涉及到了linux存储管理以及文件系统等方面的内容,可以参考一下相关重要数据结构来加深理解。在本专题的后面部分,将介绍系统v共享内存的实现。

    在共享内存(上)中,主要围绕着系统调用mmap()进行讨论的,本部分将讨论系统V共享内存,并通过实验结果对比来阐述两者的异同。系统V共享内存指的是把所有共享数据放在共享内存区域(IPC shared memory region),任何想要访问该数据的进程都必须在本进程的地址空间新增一块内存区域,用来映射存放共享数据的物理内存页面。

    系统调用mmap()通过映射一个普通文件实现共享内存。系统V则是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信。也就是说,每个共享内存区域对应特殊文件系统shm中的一个文件(这是通过shmid_kernel结构联系起来的),后面还将阐述。

    1、系统V共享内存原理

    进程间需要共享的数据被放在一个叫做IPC共享内存区域的地方,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中去。系统V共享内存通过shmget获得或创建一个IPC共享内存区域,并返回相应的标识符。内核在保证shmget获得或创建一个共享内存区,初始化该共享内存区相应的shmid_kernel结构注同时,还将在特殊文件系统shm中,创建并打开一个同名文件,并在内存中建立起该文件的相应dentry及inode结构,新打开的文件不属于任何一个进程(任何进程都可以访问该共享内存区)。所有这一切都是系统调用shmget完成的。

    注:每一个共享内存区都有一个控制结构struct shmid_kernel,shmid_kernel是共享内存区域中非常重要的一个数据结构,它是存储管理和文件系统结合起来的桥梁,定义如下:

    struct shmid_kernel /* private to the kernel */
    {        
            struct kern_ipc_perm        shm_perm;
            struct file *                shm_file;
            int                        id;
            unsigned long                shm_nattch;
            unsigned long                shm_segsz;
            time_t                        shm_atim;
            time_t                        shm_dtim;
            time_t                        shm_ctim;
            pid_t                        shm_cprid;
            pid_t                        shm_lprid;
    };

    该结构中最重要的一个域应该是shm_file,它存储了将被映射文件的地址。每个共享内存区对象都对应特殊文件系统shm中的一个文件,一般情况下,特殊文件系统shm中的文件是不能用read()、write()等方法访问的,当采取共享内存的方式把其中的文件映射到进程地址空间后,可直接采用访问内存的方式对其访问。

    这里我们采用[1]中的图表给出与系统V共享内存相关数据结构:





    正如消息队列和信号灯一样,内核通过数据结构struct ipc_ids shm_ids维护系统中的所有共享内存区域。上图中的shm_ids.entries变量指向一个ipc_id结构数组,而每个ipc_id结构数组中有个指向kern_ipc_perm结构的指针。到这里读者应该很熟悉了,对于系统V共享内存区来说,kern_ipc_perm的宿主是shmid_kernel结构,shmid_kernel是用来描述一个共享内存区域的,这样内核就能够控制系统中所有的共享区域。同时,在shmid_kernel结构的file类型指针shm_file指向文件系统shm中相应的文件,这样,共享内存区域就与shm文件系统中的文件对应起来。

    在创建了一个共享内存区域后,还要将它映射到进程地址空间,系统调用shmat()完成此项功能。由于在调用shmget()时,已经创建了文件系统shm中的一个同名文件与共享内存区域相对应,因此,调用shmat()的过程相当于映射文件系统shm中的同名文件过程,原理与mmap()大同小异。

    2、系统V共享内存API

    对于系统V共享内存,主要有以下几个API:shmget()、shmat()、shmdt()及shmctl()。

    #include <sys/ipc.h>;
    #include <sys/shm.h>;

    shmget()用来获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域。shmat()把共享内存区域映射到调用进程的地址空间中去,这样,进程就可以方便地对共享区域进行访问操作。shmdt()调用用来解除进程对共享内存区域的映射。shmctl实现对共享内存区域的控制操作。这里我们不对这些系统调用作具体的介绍,读者可参考相应的手册页面,后面的范例中将给出它们的调用方法。

    注:shmget的内部实现包含了许多重要的系统V共享内存机制;shmat在把共享内存区域映射到进程空间时,并不真正改变进程的页表。当进程第一次访问内存映射区域访问时,会因为没有物理页表的分配而导致一个缺页异常,然后内核再根据相应的存储管理机制为共享内存映射区域分配相应的页表。

    3、系统V共享内存限制

    在/proc/sys/kernel/目录下,记录着系统V共享内存的一下限制,如一个共享内存区的最大字节数shmmax,系统范围内最大共享内存区标识符数shmmni等,可以手工对其调整,但不推荐这样做。

    在[2]中,给出了这些限制的测试方法,不再赘述。

    4、系统V共享内存范例

    本部分将给出系统V共享内存API的使用方法,并对比分析系统V共享内存机制与mmap()映射普通文件实现共享内存之间的差异,首先给出两个进程通过系统V共享内存通信的范例:

    /***** testwrite.c *******/
    #include <sys/ipc.h>;
    #include <sys/shm.h>;
    #include <sys/types.h>;
    #include <unistd.h>;
    typedef struct{
            char name[4];
            int age;
    } people;
    main(int argc, char** argv)
    {
            int shm_id,i;
            key_t key;
            char temp;
            people *p_map;
            char* name = "/dev/shm/myshm2";
            key = ftok(name,0);
            if(key==-1)
                    perror("ftok error");
            shm_id=shmget(key,4096,IPC_CREAT);        
            if(shm_id==-1)
            {
                    perror("shmget error");
                    return;
            }
            p_map=(people*)shmat(shm_id,NULL,0);
            temp='a';
            for(i = 0;i<10;i++)
            {
                    temp+=1;
                    memcpy((*(p_map+i)).name,&temp,1);
                    (*(p_map+i)).age=20+i;
            }
            if(shmdt(p_map)==-1)
                    perror(" detach error ");
    }
    /********** testread.c ************/
    #include <sys/ipc.h>;
    #include <sys/shm.h>;
    #include <sys/types.h>;
    #include <unistd.h>;
    typedef struct{
            char name[4];
            int age;
    } people;
    main(int argc, char** argv)
    {
            int shm_id,i;
            key_t key;
            people *p_map;
            char* name = "/dev/shm/myshm2";
            key = ftok(name,0);
            if(key == -1)
                    perror("ftok error");
            shm_id = shmget(key,4096,IPC_CREAT);        
            if(shm_id == -1)
            {
                    perror("shmget error");
                    return;
            }
            p_map = (people*)shmat(shm_id,NULL,0);
            for(i = 0;i<10;i++)
            {
            printf( "name:%s/n",(*(p_map+i)).name );
            printf( "age %d/n",(*(p_map+i)).age );
            }
            if(shmdt(p_map) == -1)
                    perror(" detach error ");
    }

    testwrite.c创建一个系统V共享内存区,并在其中写入格式化数据;testread.c访问同一个系统V共享内存区,读出其中的格式化数据。分别把两个程序编译为testwrite及testread,先后执行./testwrite及./testread 则./testread输出结果如下:

    name: b        age 20;        name: c        age 21;        name: d        age 22;        name: e        age 23;        name: f        age 24;
    name: g        age 25;        name: h        age 26;        name: I        age 27;        name: j        age 28;        name: k        age 29;

    通过对试验结果分析,对比系统V与mmap()映射普通文件实现共享内存通信,可以得出如下结论:

    1、 系统V共享内存中的数据,从来不写入到实际磁盘文件中去;而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。注:前面讲到,系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的,文件系统shm的安装点在交换分区上,系统重新引导后,所有的内容都丢失。

    2、 系统V共享内存是随内核持续的,即使所有访问共享内存的进程都已经正常终止,共享内存区仍然存在(除非显式删除共享内存),在内核重新引导之前,对该共享内存区域的任何改写操作都将一直保留。

    3、 通过调用mmap()映射普通文件进行进程间通信时,一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。注:这里没有给出shmctl的使用范例,原理与消息队列大同小异。

    结论:

    共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制。共享内存可以通过mmap()映射普通文件(特殊情况下还可以采用匿名映射)机制实现,也可以通过系统V共享内存机制实现。应用接口和原理很简单,内部机制复杂。为了实现更安全通信,往往还与信号灯等同步机制共同使用。

    共享内存涉及到了存储管理以及文件系统等方面的知识,深入理解其内部机制有一定的难度,关键还要紧紧抓住内核使用的重要数据结构。系统V共享内存是以文件的形式组织在特殊文件系统shm中的。通过shmget可以创建或获得共享内存的标识符。取得共享内存标识符后,要通过shmat将这个内存区映射到本进程的虚拟地址空间。

    --------------------- 本文来自 hai0808 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/liu0808/article/details/52967167?utm_source=copy

    展开全文
  • 虚拟内存技术原理和使用方法

    千次阅读 2007-10-08 15:46:00
    引言 Windows的内存结构是深入理解...Windows操作 系统对内存的管理可采取多种不同的方式,其中虚拟内存的管理方式可用来管理大型的对象和结构数组。在Windows系统中,任何一个进程都被赋予其自己的虚拟地址空间,
  • 虚拟DOM的实现原理和优劣对比

    千次阅读 2019-12-24 17:28:39
    虚拟DOM的实现原理和优劣对比
  • 前言:JMM基础-计算机原理 1、物理内存模型带来的问题 2、伪共享 3、Java内存模型(JMM) 4、Java内存模型带来的问题 4.1 可见性问题 4.2 竞争问题 4.3 重排序 5、volatile详解 5.1 volatile特性 5.2 ...
  • Java文件读写原理虚拟内存

    千次阅读 2019-04-10 08:50:57
      如果所要求的地址不是有效的虚拟内存地址(不属于正在执行的进程的任何一个内存段),则该页不能通过验证,段错误随即产生。于是,控制权转交给内核的另一部分,通常导致的结果就是进程被强令关闭。   一旦...
  • vue 虚拟dom的实现原理

    千次阅读 2020-03-23 20:23:50
    vue 虚拟dom实现原理前言一、真实DOM和其解析流程二、Virtual DOM 作用是什么?三、虚拟DOM实现 前言 Vue.js 2.0引入Virtual DOM,比Vue.js 1.0的初始渲染速度提升了2-4倍,并大大降低了内存消耗。那么,什么是...
  • 虚拟内存的分段与分页实现

    千次阅读 2014-10-20 08:55:15
    关于内存管理(虚拟内存的分段与分页实现),Intel Pentium完成哪些功能,Windows/Unix OS完成哪些功能?修改 《现代操作系统》在介绍内存管理的分段与分页结合时,介绍了分段和分页结合Intel Pentium的例子,...
  • 上一节所讨论的各种内存管理策略都是为了同时将多个进程保存在内存中以便允许多道程序设计。它们都具有以下两个共同的特征: 1) 一次性 作业必须一次性全部装入内存后,方能开始运行。这会导致两种情况发生: ...
  • KVM 虚拟化架构和实现原理

    万次阅读 2016-07-07 18:42:50
    KVM的虚拟实现KVM虚拟化架构KVM是嵌入在Linux操作系统标准内核中的一个虚拟化模块,它能够将一个Linux标准内核转换成为一个VMM,嵌有KVM模块的Linux标准内核可以支持通过kvm tools来进行加载的GuestOS。...
  • mmap() 是一个系统调用函数,本质是一种进程虚拟内存的映射方法,可以将一个文件、一段物理内存或者其它对象映射到进程的虚拟内存地址空间。实现这样的映射关系后,进程就可以采用指针的方式来读写操作这一段内存,...
  • 3.1 Qemu KVM内存虚拟原理

    千次阅读 2015-07-26 16:05:17
    本节简介内存虚拟化的方法
  • 前言 现在市场上最常见的虚拟化软件有VMWare workstation(VMWare)、VirtualBox(Oracle)、Hyper-V...而这些虚拟实现的方式可以分为全虚拟化、半虚拟化、硬件虚拟化等,本篇主要是理解这些虚拟实现原理
  • 虚拟内存

    千次阅读 2017-12-09 12:07:13
    一:什么是虚拟内存 虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘...
  • 内存分配原理以及malloc的实现

    千次阅读 2019-05-07 20:18:56
    原文:... ... 目录 内存分配的原理 malloc的实现方案 内存分配的原理 从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共...
  • 文章目录目录前文列表虚拟存储器Linux 虚拟存储器内存虚拟化KVM 的内存虚拟化 前文列表 《虚拟化技术实现虚拟化技术发展编年史》 《虚拟化技术实现 — QEMU-KVM》 《虚拟化技术实现 — KVM 的 CPU 虚拟化》 虚拟...
  • CPU 和内存虚拟原理

    千次阅读 2017-05-05 09:45:17
    不过还不够,我们多少得了解一些 KVM 的实现机制,这对以后的工作会有帮助。 CPU 虚拟化 KVM 的虚拟化是需要 CPU 硬件支持的。还记得我们在前面的章节讲过用命令来查看 CPU 是否支持KVM虚拟化吗? root@...
  • 虚拟DOM的实现原理与优缺点

    千次阅读 2020-09-22 21:33:01
    所以,用JS对象模拟DOM节点的好处是,页面的更新可以先全部反映在JS对象(虚拟DOM)上,操作内存中的JS对象的速度显然要更快,等更新完成后,再将最终的JS对象映射成真实的DOM,交由浏览器去绘制。 一、什么是虚拟DOM...
  • qemu-kvm内存虚拟原理

    千次阅读 2017-03-20 11:59:18
    关系:客户机虚拟地址---> 哈希表 ---> 影子页表 ---> 虚拟地址--->宿主物理地址; 哈希表之所以用物理地址映射(到影子页表是用虚拟机地址的)是因为每个客户机只有一个哈希表,而虚拟地址每个客户机有多个进程...
  • 操作系统对内存的管理 没有内存抽象的年代 在早些的操作系统中,并没有引入内存抽象的概念。程序直接访问和操作的都是物理内存。比如当执行如下指令时:mov reg1,1000 这条指令会毫无想象力的将物理地址1000中的...
  • 虚拟化技术原理(CPU、内存、IO)

    万次阅读 2019-08-27 10:10:52
    虚拟化通过在一个物理平台上虚拟出更多的虚拟平台, 而其中的每一个虚拟平台则可以作为独立的终端加入云端的分布式系统。 比起直接使用物理平台, 虚拟化在资源的有效利用、 动态调配和高可靠性方面...
  • 操作系统-虚拟内存 有关操作系统的设计,都是在解决一个问题,即:如果让计算机高效安全的运行多道程序。 本文要解决哪些问题 为何虚拟地址可以起到这样的作用:512M内存的机器,可跑1G大小的游戏? 虚拟内存技术...
  • KVM虚拟化架构和实现原理

    千次阅读 2018-12-16 09:35:52
    版权声明:转载请注明出处 JmilkFan_范桂飓:http://blog.csdn.net/jmilk https://blog.csdn.net/Jmilk/article/details/51853511 ...KVM虚拟化架构 devkvm QEM...
  • 什么是虚拟内存

    千次阅读 多人点赞 2019-11-09 15:33:02
    什么是虚拟内存呢?先查一下维基百科: 虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时...
  • KVM 是如何实现虚拟化的?本节讨论 CPU 和内存虚拟化的原理

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 138,314
精华内容 55,325
关键字:

虚拟内存实现原理