【问题标题】:Understanding memory allocations了解内存分配
【发布时间】:2019-07-08 19:59:16
【问题描述】:

我试图理解“记忆是如何工作的”。据我了解调用mmap 以创建MAP_ANONYMOUS 映射它创建的操作系统(在我的情况下为Linux):

mmap() 在虚拟地址中创建一个新映射 调用进程的空间

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

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

操作系统捕获页面错误并在页面目录中创建一个条目。

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

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

【问题讨论】:

  • Google OOM 杀手。
  • @PSkocik 是否嵌入到内核中?
  • @SomeName 是的。这是一种内核机制,当内核过度使用内存并且迫切需要内存但没有足够的物理内存来支持承诺时,它会选择受害者。
  • @SomeName IDK 关于 VM_ACCOUNT。除非您使用 MAP_POPULATE 进行 mmap,否则 mmap 不会填充(=prefault)整个映射。它在手册页中。
  • 我不是这方面的专家。我尽量避免,真的。我喜欢禁用过度使用并放心 OOM 杀手永远不会触及我的任何进程。 :D

标签: c memory linux-kernel mmap


【解决方案1】:

使用 mmap (MAP_ANONYMOUS) 或 malloc 不会改变你的情况,如果你没有足够的可用内存 mmap 返回MAP_FAILED malloc 返回 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,因为我不读/写它们

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 消失了(shell 上没有消息)

如果我 malloc(给程序提供第二个参数),行为是相同的


另见What is the purpose of MAP_ANONYMOUS flag in mmap system call?

【讨论】:

  • 问题是物理内存耗尽,而不是虚拟内存。
  • @Barmar 是的,最好不要在没有交换的情况下谈论“虚拟”
  • 对,没看到。但它可以弹出映射到可执行文件中纯段的内存。
  • @Barmar 因为你的评论,我还是删除了“虚拟”这个词
  • PTE 包含的信息表明没有与该 VM 页面关联的物理内存,并且必须在页面错误发生时创建它。
【解决方案2】:

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

您可以在没有任何交换辅助空间的情况下运行系统,但这并不意味着您没有使用虚拟内存。您不能禁用虚拟内存,而虚拟内存是实现mmap(2) syscall 的基本概念。

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

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

当你说

如果我映射了一些匿名内存(但没有触及任何页面)会发生什么...

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

编辑

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

【讨论】:

  • 您好,感谢您的详细回复!我有点困惑。既然内核已经准备好这个交换区,为什么我们还需要自己创建交换区呢?两种交换机制有什么区别?谢谢!
  • 不,我不是说你使用自己的交换...我说的是许多资源最初是用交换处理的,现在它们由一些文件系统维护,节省交换空间,因为后备存储不是从交换设备借来的。
  • 嗨!抱歉,我有点跑题了。你说“......如果你禁用交换(你不添加任何交换分区)并不意味着你没有使用交换”,这让我推断出有一个默认的系统交换机制。所以我想知道它与“sudo mkswap /data/swapfile; sudo swapon /data/swapfile”启用的交换文件之间的区别如果没关系,你能解释一下我的问题吗? :)
  • 不,您没有使用交换。但是虚拟内存系统继续工作,许多使用交换解决的资源,不应该工作,现在可以了。例如,共享内存,运行比实际内存更多的进程(因为文本页面可以简单地消除,它们不会改变,可以从程序文件中重新加载等)
猜你喜欢
  • 2021-06-18
  • 2011-05-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-23
  • 2013-03-04
  • 2018-03-22
相关资源
最近更新 更多