【问题标题】:Why is VSIZE so much larger for 64 bit Linux process?为什么 64 位 Linux 进程的 VSIZE 这么大?
【发布时间】:2013-01-17 02:55:29
【问题描述】:

我的基本问题是,为什么 64 位进程的 VSIZE 比为 32 位编译的完全相同的程序大得多?

以下是 32 位进程的 /proc//maps 文件的输出。

00148000-00149000 r-xp 00000000 00:00 0               [vdso]
00149000-002d2000 r-xp 00000000 fd:02 8914142         /lib/libc-2.12.so
002d2000-002d3000 ---p 00189000 fd:02 8914142         /lib/libc-2.12.so
002d3000-002d5000 r--p 00189000 fd:02 8914142         /lib/libc-2.12.so
002d5000-002d6000 rw-p 0018b000 fd:02 8914142         /lib/libc-2.12.so
002d6000-002d9000 rw-p 00000000 00:00 0 
005c9000-005da000 r-xp 00000000 fd:02 17059392        /tmp/vsizetest/lib/libtesting.so
005da000-005db000 rw-p 00010000 fd:02 17059392        /tmp/vsizetest/lib/libtesting.so
005db000-0061b000 rw-p 00000000 00:00 0 
00661000-00689000 r-xp 00000000 fd:02 8917713         /lib/libm-2.12.so
00689000-0068a000 r--p 00027000 fd:02 8917713         /lib/libm-2.12.so
0068a000-0068b000 rw-p 00028000 fd:02 8917713         /lib/libm-2.12.so
00694000-006ab000 r-xp 00000000 fd:02 8917680         /lib/libpthread-2.12.so
006ab000-006ac000 r--p 00016000 fd:02 8917680         /lib/libpthread-2.12.so
006ac000-006ad000 rw-p 00017000 fd:02 8917680         /lib/libpthread-2.12.so
006ad000-006af000 rw-p 00000000 00:00 0 
006e5000-00703000 r-xp 00000000 fd:00 3150403         /lib/ld-2.12.so
00703000-00704000 r--p 0001d000 fd:00 3150403         /lib/ld-2.12.so
00704000-00705000 rw-p 0001e000 fd:00 3150403         /lib/ld-2.12.so
00983000-009a0000 r-xp 00000000 fd:02 8914997         /lib/libgcc_s-4.4.5-20110214.so.1
009a0000-009a1000 rw-p 0001d000 fd:02 8914997         /lib/libgcc_s-4.4.5-20110214.so.1
00ca5000-00d86000 r-xp 00000000 fd:02 6300601         /usr/lib/libstdc++.so.6.0.13
00d86000-00d8a000 r--p 000e0000 fd:02 6300601         /usr/lib/libstdc++.so.6.0.13
00d8a000-00d8c000 rw-p 000e4000 fd:02 6300601         /usr/lib/libstdc++.so.6.0.13
00d8c000-00d92000 rw-p 00000000 00:00 0 
08048000-08049000 r-xp 00000000 fd:02 21134666        /tmp/vsizetest/bin/testvsz
08049000-0804a000 rw-p 00000000 fd:02 21134666        /tmp/vsizetest/bin/testvsz
09b8d000-09bae000 rw-p 00000000 00:00 0               [heap]
f7796000-f779c000 rw-p 00000000 00:00 0 
ff998000-ff9ae000 rw-p 00000000 00:00 0               [stack]

这导致总 VSIZE 为 3656。

以下是 64 位进程的 /proc//maps 文件的输出。

00400000-00401000 r-xp 00000000 fd:02 21134667              /tmp/vsizetest/bin64/testvsz
00600000-00601000 rw-p 00000000 fd:02 21134667              /tmp/vsizetest/bin64/testvsz
02301000-02322000 rw-p 00000000 00:00 0                     [heap]
3b7c800000-3b7c820000 r-xp 00000000 fd:00 661349            /lib64/ld-2.12.so
3b7ca1f000-3b7ca20000 r--p 0001f000 fd:00 661349            /lib64/ld-2.12.so
3b7ca20000-3b7ca21000 rw-p 00020000 fd:00 661349            /lib64/ld-2.12.so
3b7ca21000-3b7ca22000 rw-p 00000000 00:00 0 
3b7cc00000-3b7cd86000 r-xp 00000000 fd:00 661350            /lib64/libc-2.12.so
3b7cd86000-3b7cf86000 ---p 00186000 fd:00 661350            /lib64/libc-2.12.so
3b7cf86000-3b7cf8a000 r--p 00186000 fd:00 661350            /lib64/libc-2.12.so
3b7cf8a000-3b7cf8b000 rw-p 0018a000 fd:00 661350            /lib64/libc-2.12.so
3b7cf8b000-3b7cf90000 rw-p 00000000 00:00 0 
3b7d000000-3b7d083000 r-xp 00000000 fd:00 661365            /lib64/libm-2.12.so
3b7d083000-3b7d282000 ---p 00083000 fd:00 661365            /lib64/libm-2.12.so
3b7d282000-3b7d283000 r--p 00082000 fd:00 661365            /lib64/libm-2.12.so
3b7d283000-3b7d284000 rw-p 00083000 fd:00 661365            /lib64/libm-2.12.so
3b7d800000-3b7d817000 r-xp 00000000 fd:00 661352            /lib64/libpthread-2.12.so
3b7d817000-3b7da16000 ---p 00017000 fd:00 661352            /lib64/libpthread-2.12.so
3b7da16000-3b7da17000 r--p 00016000 fd:00 661352            /lib64/libpthread-2.12.so
3b7da17000-3b7da18000 rw-p 00017000 fd:00 661352            /lib64/libpthread-2.12.so
3b7da18000-3b7da1c000 rw-p 00000000 00:00 0 
3b7e000000-3b7e007000 r-xp 00000000 fd:00 661361            /lib64/librt-2.12.so
3b7e007000-3b7e206000 ---p 00007000 fd:00 661361            /lib64/librt-2.12.so
3b7e206000-3b7e207000 r--p 00006000 fd:00 661361            /lib64/librt-2.12.so
3b7e207000-3b7e208000 rw-p 00007000 fd:00 661361            /lib64/librt-2.12.so
3b87000000-3b87016000 r-xp 00000000 fd:00 664219            /lib64/libgcc_s-4.4.6-20110824.so.1
3b87016000-3b87215000 ---p 00016000 fd:00 664219            /lib64/libgcc_s-4.4.6-20110824.so.1
3b87215000-3b87216000 rw-p 00015000 fd:00 664219            /lib64/libgcc_s-4.4.6-20110824.so.1
3d44c00000-3d44ce8000 r-xp 00000000 fd:00 3019214           /usr/lib64/libstdc++.so.6.0.13
3d44ce8000-3d44ee8000 ---p 000e8000 fd:00 3019214           /usr/lib64/libstdc++.so.6.0.13
3d44ee8000-3d44eef000 r--p 000e8000 fd:00 3019214           /usr/lib64/libstdc++.so.6.0.13
3d44eef000-3d44ef1000 rw-p 000ef000 fd:00 3019214           /usr/lib64/libstdc++.so.6.0.13
3d44ef1000-3d44f06000 rw-p 00000000 00:00 0 
7f30ab397000-7f30ab39c000 rw-p 00000000 00:00 0 
7f30ab39c000-7f30ab3ad000 r-xp 00000000 fd:02 21127804      /tmp/vsizetest/lib64/libtesting.so
7f30ab3ad000-7f30ab5ac000 ---p 00011000 fd:02 21127804      /tmp/vsizetest/lib64/libtesting.so
7f30ab5ac000-7f30ab5ad000 rw-p 00010000 fd:02 21127804      /tmp/vsizetest/lib64/libtesting.so
7f30ab5ad000-7f30ab5ee000 rw-p 00000000 00:00 0 
7f30ab606000-7f30ab609000 rw-p 00000000 00:00 0 
7fff69512000-7fff69528000 rw-p 00000000 00:00 0             [stack]
7fff695ff000-7fff69600000 r-xp 00000000 00:00 0             [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0     [vsyscall]

这导致 VSIZE 为 18480。

这两个映射之间的主要区别是来自 64 位数据的以下条目:

3b7cd86000-3b7cf86000 ---p 00186000 fd:00 661350             /lib64/libc-2.12.so
3b7d083000-3b7d282000 ---p 00083000 fd:00 661365             /lib64/libm-2.12.so
3b7d817000-3b7da16000 ---p 00017000 fd:00 661352             /lib64/libpthread-2.12.so
3b7e007000-3b7e206000 ---p 00007000 fd:00 661361             /lib64/librt-2.12.so
3b87016000-3b87215000 ---p 00016000 fd:00 664219             /lib64/libgcc_s-4.4.6-20110824.so.1
3d44ce8000-3d44ee8000 ---p 000e8000 fd:00 3019214            /usr/lib64/libstdc++.so.6.0.13
7f30ab3ad000-7f30ab5ac000 ---p 00011000 fd:02 21127804       /tmp/vsizetest/lib64/libtesting.so

在 18480 VSIZE 中占 14316。

其他程序的其他实验似乎表明,在 64 位中,您似乎为进程使用的每个共享库获得了这些私有、不可读、不可写、不可执行的内存块之一,而在 32 位中几乎没有这些块。

有谁知道这些内存块是什么?

注意:根据对类似问题What these memory regions for, from a Linux process? 的一些回答,这不是多线程进程,它已经编译了-fPIC。

【问题讨论】:

    标签: linux memory-management


    【解决方案1】:

    VSIZE 的主要区别在于共享库的 PROT_NONE 映射(模式“---p”)在 32 位和 64 位版本的情况下是如何完成的。

    这些正是您发现的产生差异的映射。

    通常对于每个加载的共享库,我们将有四个映射:

    3b7cc00000-3b7cd86000 r-xp 00000000 fd:00 661350            /lib64/libc-2.12.so
    3b7cd86000-3b7cf86000 ---p 00186000 fd:00 661350            /lib64/libc-2.12.so
    3b7cf86000-3b7cf8a000 r--p 00186000 fd:00 661350            /lib64/libc-2.12.so
    3b7cf8a000-3b7cf8b000 rw-p 0018a000 fd:00 661350            /lib64/libc-2.12.so
    

    第一个是可执行权限的代码段,第二个是PROT_NONE(模式---)映射(页面可能无法访问),最后两个是数据段(只读部分和读写)。

    PROT_NONE 的大小为 MAXPAGESIZE,因此它在 32 位和 64 位版本中的创建方式不同。对于 32 位版本,它有 4KB 大小(对于 i386 为 MAXPAGESIZE),对于 64 位版本,它有 2MB(对于 x86_64 系统的标准 MAXPAGESIZE)。

    需要注意的是,这块内存实际上并没有被消耗(它只是消耗地址空间的地址),如下所述:

    http://www.greenend.org.uk/rjk/tech/dataseg.html

    “这个额外的东西不会花费你任何 RAM 或交换空间,只需要每个进程中的地址空间,这在 64 位平台上供应充足。根本原因是为了保持库的有效共享,但实现有点奇怪。”

    最后一个技巧,我发现使用 pmap 实用程序检查内存映射比解析映射文件更容易并产生更易于阅读的输出:

    基本信息:

    pmap <PID>
    

    更多信息:

    pmap -x <PID>
    

    【讨论】:

    • 不错的答案,指向内存是按页面加载的。只需添加 pmap 是通过阅读“/proc//maps”来实现的... :-)
    • 刚刚修改了句子以明确 pmap 的工作原理。 pmap 最好的一点是它会解析地图文件并为您计算区域的大小。
    【解决方案2】:

    [不是一个真正的答案...超出我的知识范围]

    如果内存段真的是“私有的、不可读的、不可写的、不可执行的”,那么它们永远不应该被引用,即使它们存在于虚拟内存空间中,它们也永远不会占用任何真实内存,因此不必担心太多。 (?)

    这一定是某种簿记或碎片问题。由于这些是共享库 (*.so) 的一部分,这就是这些库的构建方式。除了链接到那些库之外,它实际上与您的程序无关。除非你想重建这些库,或者不使用它们,否则没有什么可做的(反正也没什么好处,因为它们无论如何都不应该使用真正的内存)。

    也许相关? 在What these memory regions for, from a Linux process?

    @caf 说一些“---p”的内存段是“保护页”。

    这表明它们的存在只是为了捕获一个杂散的指针或堆栈增长到远错误...一种内存中的硬分隔符,因此系统可以捕获一个常见错误并停止处理,而不是让那些常见错误溜走(它引用它们是一个致命错误,它们真的永远不会使用任何真实内存)。

    【讨论】:

      【解决方案3】:

      通过加载libc.so 并从加载程序加载动态库的方式来看,回答为什么以及什么构成了 64 位共享库有额外的内存块。下面是 32 位和 64 位可执行文件的 strace 输出,它告诉我们有对 mmapmprotect 的调用。

      esunboj@L9AGC12:~/32_64bit$ strace ./crash-x86-64
      ...
      open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
      read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\30\2\0\0\0\0\0"...,
      832) = 832
      fstat(3, {st_mode=S_IFREG|0755, st_size=1811128, ...}) = 0
      mmap(NULL, 3925208, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) =    
      0x7fa354f8a000
      mprotect(0x7fa35513f000, 2093056, PROT_NONE) = 0
      mmap(0x7fa35533e000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE,
      3, 0x1b4000) = 0x7fa35533e000
      mmap(0x7fa355344000, 17624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS,
      -1, 0) = 0x7fa355344000
      close(3)                                = 0
      ...
      
      esunboj@L9AGC12:~/32_64bit$ strace ./crash
      ...
      open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
      read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\226\1\0004\0\0\0"...,
      512) = 512
      fstat64(3, {st_mode=S_IFREG|0755, st_size=1730024, ...}) = 0
      mmap2(NULL, 1743580, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 
      0xfffffffff7546000
      mprotect(0xf76e9000, 4096, PROT_NONE)   = 0
      mmap2(0xf76ea000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE,  
      3, 0x1a3) = 0xfffffffff76ea000
      mmap2(0xf76ed000, 10972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, 
      -1, 0) = 0xfffffffff76ed000
      close(3)                                = 0
      ...
      

      仔细观察strace的两件事是需要调查的,

      1.他们每个人都映射内存 3 次,并在第一个 mmap 之后对 mprotect 进行 1 次调用。

      2。比较 64 位和 32 位的 mprotect 调用分别具有受保护区域的 2093056B4096B

      dl-load.c 中,子程序_dl_map_object_from_fd() 通过设置所需的权限和零填充库的.bss 部分将动态库内存段映射到虚拟空间并更新链接映射结构。让我们在这里获取一些代码以进行更多分析,

      struct link_map *
       _dl_map_object_from_fd ( )
      {
       ...
        /* Scan the program header table, collecting its load commands. */
        struct loadcmd
         {
           ElfW(Addr) mapstart, mapend, dataend, allocend;
           off_t mapoff;
           int prot;
         } loadcmds[l->l_phnum], *c; // l is link_map struct described for each object 
                                        of dynamic linker 
        size_t nloadcmds = 0;
        bool has_holes = false;
        ...
        for (ph = phdr; ph < &phdr[l->l_phnum]; ++ph)
        switch (ph->p_type)
        {
        ...
        case PT_LOAD:
        ...
          c = &loadcmds[nloadcmds++];
          c->mapstart = ph->p_vaddr & ~(GLRO(dl_pagesize) - 1);
          c->mapend = ((ph->p_vaddr + ph->p_filesz + GLRO(dl_pagesize) - 1)
                           & ~(GLRO(dl_pagesize) - 1));
        ...
          if (nloadcmds > 1 && c[-1].mapend != c->mapstart)
              has_holes = true;
        ...
        }
        ...
          if (has_holes)
             __mprotect ((caddr_t) (l->l_addr + c->mapend),
                loadcmds[nloadcmds - 1].mapstart - c->mapend, PROT_NONE);
        ...
      }
      

      在上述代码中,for 语句中使用的 l_phnum 包含 ELF 程序头中的条目数。理想情况下,每次迭代都会映射每个条目段。当PT_LOAD 段案例第一次出现时,它基本上是一个.text.rodata 部分被映射(第一个mmap 在strace 中)和第二个PT_LOAD 段代表.datasection 被映射(第二个@ 987654344@ 在 strace 中)。在第二个 PT_LOAD 段被映射之前,mapstartmapend 被保留,它们指的是文本部分的开始和结束。在下一次PT_LOAD 迭代中,如果前一个段mapend 不等于当前(.data)段mapstart,那么它们是两个PT_LOAD 段之间的一个洞(意味着.text.data 部分之间的间隙)。因此,如果它们是具有空权限的内存区域之间的漏洞,加载程序将保护(mprotect 在 strace 中调用)它或使其不可访问。 64 位和 32 位进程的受保护区域是 511 而只是 1 页分别为 64 位库添加了巨大的内存块。

      64 位不可访问区域的证明:下面libc.so 的 Objdump 为我们提供了一些虚拟地址 (VA) 统计信息,这些统计信息经过适当的四舍五入,如下所示,

                       PT_LOAD(1)              PT_LOAD(2)                    
      mapstart VA   0x0000000000000000     0x00000000003b4000   
      mapend   VA   0x00000000001b5000     0x00000000003A0000
      

      这里PT_LOAD(1) mapend (0x00000000001b5000) 不等于PT_LOAD(2) mapstart (0x00000000003b4000) 导致内存孔为0x00000000001FF000(十进制2093056B)。

      esunboj@L9AGC12:~/32_64bit$objdump -x -s -d -D /lib/x86_64-linux-gnu/libc.so.6 
      Program Header: 
      ...
        LOAD off    0x0000000000000000 vaddr 0x0000000000000000 paddr 0x0000000000000000 align 2**21
             filesz 0x00000000001b411c memsz 0x00000000001b411c flags r-x
        LOAD off    0x00000000001b4700 vaddr 0x00000000003b4700 paddr 0x00000000003b4700 align 2**21
             filesz 0x0000000000005160 memsz 0x0000000000009dd8 flags rw- 
      ...
      

      与 32 位相比,64 位文本采用更高的指令字节表示。同样,64 位上的指针大小为8B 加上4 更多字节。此外,数据结构对齐是8B 在 64 位中对齐,从而使映射区域更大。

      二进制文件上的简单size 命令可以显示32/64位程序内存区域之间的差异,如下所示,

      esunboj@L9AGC12:~/32_64bit$ ls -lrt
      total 10368
      -rwxrwxrwx 1 esunboj ei 5758776 Oct 10 11:35 crash-x86-64
      -rwxrwxrwx 1 esunboj ei 4855676 Oct 10 11:36 crash
      esunboj@L9AGC12:~/32_64bit$ size crash
         text    data     bss     dec     hex filename
      4771286   82468  308704 5162458  4ec5da crash
      esunboj@L9AGC12:~/32_64bit$ size crash-x86-64 
         text    data     bss     dec     hex filename
      5634861  121164 1623728 7379753  709b29 crash-x86-64
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-07
        • 2012-09-30
        • 1970-01-01
        • 2015-03-05
        • 1970-01-01
        • 2010-10-06
        相关资源
        最近更新 更多