理解内存分配

4

我正在尝试理解"内存如何工作"。据我所知,操作系统(在我的情况下为Linux)调用mmap创建MAP_ANONYMOUS映射时,会创建以下内容:

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

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

另外,据我所知,当CPU尝试访问尚未在页表中的内存页面时触发页面错误,然后才会将其映射到物理内存。

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

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

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


@PSkocik 它是否嵌入内核中? - Some Name
1
@SomeName 是的。这是一种内核机制,当内核超额分配内存并且需要紧急使用内存但没有足够的物理内存来支持承诺时,会选择受害者。 - Petr Skocik
据我所知,@PSkocik,VM_ACCOUNT标志可以防止OOM Killer的作用。自从2.6内核以来,大多数用户空间内存都被标记了。但问题是内核是否实际上为整个mmap区域创建页面条目,还是在第一页被使用后才创建。 - Some Name
1
@SomeName 我不知道 VM_ACCOUNT 是什么。除非你使用 MAP_POPULATE 进行 mmap,否则 mmap 不会填充(=prefault)整个映射。这在 man 手册中有说明。 - Petr Skocik
1
我对此并不是专家。实际上,我尽量避免使用它。我喜欢禁用过度承诺,并确信OOM killer永远不会触及我的任何进程。 :D - Petr Skocik
显示剩余2条评论
2个回答

2
首先,如果你禁用了交换空间(没有添加任何交换分区),这并不意味着你没有使用交换空间。请看下文。
你可以运行一个没有任何交换空间的系统,但这并不意味着你没有使用虚拟内存。你不能禁用虚拟内存,而虚拟内存是实现mmap(2)系统调用的基本概念。
mmap(2)使用文件来填充其用于内存段的页面的初始内容。但它做得更多...它为所分配的内存段页面使用正常的虚拟内存,并在内核需要其中一些页面时将它们交换出来。由于有一个文件来存储页面内容,因此您不需要进行交换,只需将页面内容写入文件中的适当位置即可。当其他进程附加到具有相同共享内存段的进程时,相同的页面映射在两个进程上,当一个进程写入该页面时,另一个进程立即看到它。此外,如果某个进程读取或写入文件,则由于所使用的块是用于读取磁盘文件的相同块,因此它将看到与两个进程共享的相同数据。就是这样工作的。
该机制使内核节省了大量交换空间,也使内核能够丢弃程序的文本段的部分而无需将它们交换到辅助设备中(因为它们已经在程序的文件文本段中)。
当你说
如果我映射了一些匿名内存(但没有触及任何页面),那么...
如果你没有触及任何页面,那么可能实际上还没有映射其中任何一个页面,只是准备使用资源,但尚未分配。当你在其中一个页面上出现错误(例如用于读取,你承诺不触及它们)时,该页面被映射到实际的内存页面,但磁盘备份(或其交换空间)实际上在文件中,而不在交换设备中。该页面实际上也是磁盘块(更精确地说是用于存储来自磁盘驱动器的数据的一组磁盘块),因此不会使用多个相同数据的副本。
编辑
匿名mmap(2)可能也使用磁盘块(在某个默认磁盘单元中)。因此,即使您没有使用交换设备,您可能也可以使用将虚拟空间映射到磁盘inode的mmap(2)。我还没有检查过这一点,但旧的Unix管道就是这样工作的。可以使用临时inode(没有在目录中分配条目,例如具有打开进程的已擦除文件)来进行此操作。

你好,感谢您提供详细的回复!我有点困惑。既然内核已经准备好了这个交换区域,为什么我们还需要自己创建交换空间呢?这两种交换机制有什么区别?谢谢! - John the Traveler
不,我并不是说你要使用自己的交换空间...我的意思是许多资源最初是由交换空间处理的,现在它们由某些文件系统维护,节省了交换空间,因为后备存储器不再从交换设备中借用。 - Luis Colorado
嗨!抱歉我有点跑题了。你说“如果你禁用交换分区(不添加任何交换分区),那并不意味着你没有使用交换空间”,这让我推断出存在默认的系统交换机制。所以我想知道这种机制与通过“sudo mkswap /data/swapfile; sudo swapon /data/swapfile”启用的交换文件之间的区别。如果可以的话,你能为我的问题解答一下吗? :) - John the Traveler
1
不,您没有使用交换。但虚拟内存系统仍在工作,并且许多利用交换解决的资源现在可以正常工作,而不应该使用交换。例如,共享内存、运行更多进程比实际内存所能容纳的数量(因为文本页面可以简单地被消除,它们不会改变,可以从程序文件重新加载等)。 - Luis Colorado

2

如果您使用mmap(MAP_ANONYMOUS)或malloc,在您的情况下不会有任何变化,如果您没有足够的空闲内存,mmap将返回MAP_FAILEDmalloc将返回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、交换空间为100MB的树莓派上,由于我使用的是SO,内存已经被Chromium占用。 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

If I do that :

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

750太大了,但是"最初的回答"。
pi@raspberrypi:/tmp $ ./a.out 600 &
[1] 1525
pi@raspberrypi:/tmp $ ok

使用的内存(top等)没有反映出600Mo,因为我没有在其中读/写

proc/meminfo的结果如下:

注: Mo是法语缩写,意思是兆字节。

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(给程序传递第二个参数),行为也是相同的。
另请参见mmap系统调用中MAP_ANONYMOUS标志的目的是什么?

问题是关于物理内存不足,而非虚拟内存。 - Barmar
@Barmar,最好不要在他没有交换空间的情况下谈论“虚拟”。 - bruno
没错,我没看到那个。但是它可以弹出映射到可执行文件中纯段的内存。 - Barmar
@Barmar 我已经删除了 "virtual" 这个词,因为你的评论。 - bruno
1
PTE包含的信息表明该VM页面没有与之关联的物理内存,并且必须在页面错误发生时创建。 - Barmar
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接