了解内存分配

Understanding memory allocations

我正在努力理解 "how memory works"。据我了解 OS (在我的例子中是 Linux )调用 mmap 来创建 MAP_ANONYMOUS 映射时它会创建:

mmap() creates a new mapping in the virtual address space of the calling process

据我所知,一个进程的虚拟地址 space 可能超过 tge 实际可用的物理内存。

据我所知,当 CPU 尝试访问不在页面 table 中的内存页面时触发页面错误时,会发生实际映射到物理内存的情况。

OS 捕获页面错误并在页面目录中创建一个条目。

如果我 mmap 编辑了一些匿名内存(但没有触及任何页面),那么 应该 会发生什么,然后其他进程耗尽了所有物理内存,然后我尝试使用其中一个页面 mmaped(我已禁用交换)?

CPU 应该触发页面错误然后尝试在页面目录中创建一个条目。但是由于没有留下物理内存,它将无法这样做...

使用 mmap (MAP_ANONYMOUS) 或 malloc 如果您没有足够的可用内存,则不会改变您的情况mmap returns MAP_FAILEDmalloc returns NULL

如果我使用那个程序:

#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char ** argv)
{
  int n = atoi(argv[1]);
  void * m;

  if (argc == 1) {
    m = mmap(NULL, n*1024*1024, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

    if (m == MAP_FAILED) {
      puts("ko");
      return 0;
    }
  }
  else {
    m = malloc(n*1024*1024);
    if (m == 0) {
      puts("ko");
      return 0;
    }
  }

  puts("ok");
  getchar();

  char * p = (char *) m;
  char * sup = p + n*1024*1024;

  while (p < sup) {
    *p = 0;
    p += 512;
  }

  puts("done");
  getchar();

  return 0;
}

我在 1Gb 内存和 100Mo 交换空间的树莓派上,内存已经被 chromium 使用,因为我在 SO

proc/meminfo 给出:

MemTotal:         949448 kB
MemFree:          295008 kB
MemAvailable:     633560 kB
Buffers:           39296 kB
Cached:           360372 kB
SwapCached:            0 kB
Active:           350416 kB
Inactive:         260960 kB
Active(anon):     191976 kB
Inactive(anon):    41908 kB
Active(file):     158440 kB
Inactive(file):   219052 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:        102396 kB
SwapFree:         102396 kB
Dirty:               352 kB
Writeback:             0 kB
AnonPages:        211704 kB
Mapped:           215924 kB
Shmem:             42304 kB
Slab:              24528 kB
SReclaimable:      12108 kB
SUnreclaim:        12420 kB
KernelStack:        2128 kB
PageTables:         5676 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      577120 kB
Committed_AS:    1675164 kB
VmallocTotal:    1114112 kB
VmallocUsed:           0 kB
VmallocChunk:          0 kB
CmaTotal:           8192 kB
CmaFree:            6796 kB

如果我这样做:

pi@raspberrypi:/tmp $ ./a.out 750
ko

750 有点大,但是

pi@raspberrypi:/tmp $ ./a.out 600 &
[1] 1525
pi@raspberrypi:/tmp $ ok

使用的内存(top 等)不反映 600Mo 因为我没有 read/write 在他们

proc/meminfo 给出:

MemTotal:         949448 kB
MemFree:          282860 kB
MemAvailable:     626016 kB
Buffers:           39432 kB
Cached:           362860 kB
SwapCached:            0 kB
Active:           362696 kB
Inactive:         260580 kB
Active(anon):     199880 kB
Inactive(anon):    41392 kB
Active(file):     162816 kB
Inactive(file):   219188 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:        102396 kB
SwapFree:         102396 kB
Dirty:               624 kB
Writeback:             0 kB
AnonPages:        220988 kB
Mapped:           215672 kB
Shmem:             41788 kB
Slab:              24788 kB
SReclaimable:      12296 kB
SUnreclaim:        12492 kB
KernelStack:        2136 kB
PageTables:         5692 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      577120 kB
Committed_AS:    2288564 kB
VmallocTotal:    1114112 kB
VmallocUsed:           0 kB
VmallocChunk:          0 kB
CmaTotal:           8192 kB
CmaFree:            6796 kB

我又可以了

pi@raspberrypi:/tmp $ ./a.out 600 &
[2] 7088
pi@raspberrypi:/tmp $ ok

pi@raspberrypi:/tmp $ jobs
[1]-  stopped                 ./a.out 600
[2]+  stopped                 ./a.out 600
pi@raspberrypi:/tmp $ 

即使总数对于内存 + 交换也太大了,/proc/meminfo 给出:

MemTotal:         949448 kB
MemFree:          282532 kB
MemAvailable:     626112 kB
Buffers:           39432 kB
Cached:           359980 kB
SwapCached:            0 kB
Active:           365200 kB
Inactive:         257736 kB
Active(anon):     202280 kB
Inactive(anon):    38320 kB
Active(file):     162920 kB
Inactive(file):   219416 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:        102396 kB
SwapFree:         102396 kB
Dirty:                52 kB
Writeback:             0 kB
AnonPages:        223520 kB
Mapped:           212600 kB
Shmem:             38716 kB
Slab:              24956 kB
SReclaimable:      12476 kB
SUnreclaim:        12480 kB
KernelStack:        2120 kB
PageTables:         5736 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      577120 kB
Committed_AS:    2876612 kB
VmallocTotal:    1114112 kB
VmallocUsed:           0 kB
VmallocChunk:          0 kB
CmaTotal:           8192 kB
CmaFree:            6796 kB

如果我写入 %1 的内存然后停止它我在闪存上完成了很多交换

pi@raspberrypi:/tmp $ %1
./a.out 600

done
^Z
[1]+  stopped                 ./a.out 600

现在几乎没有空闲交换空间,几乎没有空闲内存,/proc/meminfo 给出

MemTotal:         949448 kB
MemFree:           33884 kB
MemAvailable:      32544 kB
Buffers:             796 kB
Cached:            66032 kB
SwapCached:        66608 kB
Active:           483668 kB
Inactive:         390360 kB
Active(anon):     462456 kB
Inactive(anon):   374188 kB
Active(file):      21212 kB
Inactive(file):    16172 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:        102396 kB
SwapFree:           3080 kB
Dirty:                96 kB
Writeback:             0 kB
AnonPages:        740984 kB
Mapped:            61176 kB
Shmem:             29288 kB
Slab:              21932 kB
SReclaimable:       9084 kB
SUnreclaim:        12848 kB
KernelStack:        2064 kB
PageTables:         7012 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      577120 kB
Committed_AS:    2873112 kB
VmallocTotal:    1114112 kB
VmallocUsed:           0 kB
VmallocChunk:          0 kB
CmaTotal:           8192 kB
CmaFree:            6796 kB

%1 仍在等待 getchar,如果我对 %2 执行相同的操作,它会起作用,但实际上是因为进程 %1 消失了([= 上没有消息80=])

如果我 malloc(为程序提供第二个参数)

,行为是相同的

另见 mmap 系统调用中 MAP_ANONYMOUS 标志的用途是什么?

首先,如果您禁用交换(您不添加任何交换分区)并不意味着您没有使用交换。阅读下文。

您可以 运行 一个没有任何次要交换的系统 space,但这并不意味着您没有使用虚拟内存。您不能禁用虚拟内存,虚拟内存是实现 mmap(2) 系统调用的基本概念。

mmap(2) 使用一个文件来填充其用于内存段的页面的初始内容。但它做的更多......它为该段的分配页面使用普通虚拟内存,并在内核需要其中一些页面时将它们换出。由于有一个文件存放页面内容,所以不需要交换它,只需将页面内容写入文件中适当的位置即可。当其他进程附加了相同的共享内存段时,同一个页面被映射到两个进程上,当一个进程写入该页面时,另一个进程会立即看到它。此外,如果某个进程读取或写入文件,由于使用的块与读取磁盘文件的块相同,因此它将看到的数据是与两个进程共享的相同数据。这就是它的工作原理。

内核使用这种机制节省了大量的交换空间,而且这允许内核能够丢弃程序文本段的一部分,而不必将它们换出到辅助设备(因为它们已经在程序的文件文本段中)

当你说

What should happen if I mmaped some anonymous memory (but did not touch any of the pages)...

如果你没有接触到任何一个页面,那么可能这两个页面实际上都还没有被映射,只是资源准备使用,但还没有分配。当你在其中一个页面上出错时(例如,为了阅读,你承诺不会触摸它们)是映射到实际内存页面的页面,但磁盘备份(或它的交换 space)实际上是在文件,不在交换设备中。该页面实际上也是用于存储来自磁盘驱动程序的数据的磁盘块(更准确地说是一组磁盘块),因此不会使用相同数据的多个副本。

编辑

Anonymous mmap(2) 可能也使用磁盘块(在某些默认磁盘单元中)。因此,即使在您不使用交换设备的情况下,也可能允许您使用 mmap(2) 并将虚拟 space 映射到磁盘 inode。我没有检查过这个,但是旧的 unix 管道是这样工作的。为此可以使用一个临时 inode(没有在目录中分配的条目,例如带有打开进程的已擦除文件)。