为什么内核关心发出物理上连续的页



当一个进程向Linux内核请求物理内存页面时,内核会尽力提供内存中物理上连续的页面块。我想知道为什么这些页面在物理上是连续的;毕竟,内核可以通过简单地提供实际上连续的页面来掩盖这一事实。

然而,内核肯定会尽最大努力提供物理上连续的页面,所以我试图弄清楚为什么物理上的连续如此重要。我做了一些研究,从一些资源中发现了以下原因:

1)可以更好地利用缓存&实现更低的平均内存访问时间(GigaQuantum:我不明白:如何?)

2)你必须摆弄内核页表,以映射页不是物理上连续的(GigaQuantum:我不明白这一点:是不是每个页单独映射?有什么好折腾的?)

3)物理上不连续的映射页面会导致更大的TLB抖动(GigaQuantum:我不明白:如何?)

根据我插入的评论,我真的不理解这3个原因。我的任何研究来源也没有充分解释/证明这3个原因。谁能更详细地解释一下这些?

谢谢!将帮助我更好地理解内核…

主要的答案其实在你的第二点。通常,在内核中分配内存时,不会在分配时对其进行映射——相反,内核会使用简单的线性映射预先映射尽可能多的物理内存。在分配时,它只是为分配雕刻出一些内存-由于映射没有改变,它必须已经是连续的。

物理内存的大的线性映射是有效的:因为大的页面可以用于它(占用更少的页表条目空间和更少的TLB条目),因为修改页表是一个缓慢的过程(所以你想避免在分配/释放时这样做)。

使用vmalloc()接口而不是kmalloc()接口,只能请求逻辑线性的分配

在64位系统上,内核的映射可以包含整个物理内存——在32位系统上(除了那些具有少量物理内存的系统),只有一部分物理内存被直接映射。

实际上,您描述的内存分配行为在许多操作系统内核中都很常见,主要原因是内核物理分页分配器。通常,内核有一个物理页面分配器,用于为内核空间(包括DMA页面)和用户空间分配页面。在内核空间中,您需要连续的内存,因为每次需要时都映射页面(对于内核内代码)是非常昂贵的。例如,在x86_64上,它完全没有价值,因为内核可以看到整个地址空间(在32位系统上,虚拟地址空间有4G限制,所以通常顶部1G专用于内核,底部3G专用于用户空间)。

Linux内核使用伙伴算法进行页面分配,因此分配较大的块比分配较小的块需要更少的迭代(嗯,通过拆分较大的块来获得较小的块)。此外,对内核空间和用户空间使用一个分配器可以使内核减少碎片。假设每次迭代为用户空间分配1个页面。如果用户空间需要N个页面,则进行N次迭代。如果内核需要一些连续内存怎么办?如果你从每个大块中偷取1页并将它们留给用户空间,它如何构建足够大的连续块?

(更新)

实际上,内核为用户空间分配连续的内存块并不像您想象的那么频繁。当然,当它为文件构建ELF映像时,当它在用户进程读取文件时创建readahead时,当它为IPC操作(管道,套接字缓冲区)或当用户将MAP_POPULATE标志传递给mmap系统调用时,它会分配它们。但是内核通常使用"惰性"页面加载方案。它将虚拟内存的连续空间提供给用户空间(当用户第一次执行malloc或执行mmap时),但它不会用物理页面填充空间。它只在发生页面错误时分配页面。当用户进程分叉时也是如此。在这种情况下,子进程将具有"只读"地址空间。当子节点修改一些数据时,发生页面错误,内核用新的地址空间替换子节点地址空间中的页面(这样父节点和子节点就有不同的页面了)。在这种情况下,内核通常只分配一个页面。

当然存在内存碎片的大问题。内核空间总是需要连续内存。如果内核从"随机"物理位置为用户空间分配页面,那么在一段时间后(例如在系统正常运行一周后)在内核中获得大块连续内存就会困难得多。在这种情况下,内存会太碎片化。

为了解决这个问题,内核使用"预读"方案。当某个进程的地址空间出现页面错误时,内核会分配和映射多个页面(因为进程有可能从下一页读取/写入数据)。当然,在这种情况下,它使用物理上连续的内存块(如果可能的话)。只是为了减少潜在的分裂。

我能想到的几个:

  • DMA硬件通常根据物理地址访问内存。如果需要从硬件传输多个页面的数据,则需要一个连续的物理内存块来完成此操作。一些旧的DMA控制器甚至要求内存位于低物理地址。
  • 它允许操作系统利用大页面。一些内存管理单元允许在页表项中使用更大的页大小。这允许您使用更少的页表条目(和TLB槽)来访问相同数量的虚拟内存。这减少了TLB丢失的可能性。当然,如果您想分配一个4MB的页面,您将需要4MB的连续物理内存来支持它。
  • 内存映射I/O。一些设备可以映射到I/O范围,这些范围需要跨越多个帧的连续内存范围。

内核的连续或非连续内存分配请求取决于您的应用程序。

。连续内存分配:如果你需要执行DMA操作,那么你将通过kmalloc()调用请求连续内存,因为DMA操作需要一个物理上连续的内存,因为在DMA中,你将只提供内存块的起始地址,其他设备将从该位置读取或写入。

一些操作不需要连续内存,所以你可以通过vmalloc()请求一个内存块,它给出了一个指向非传染性物理内存的指针。

所以它完全依赖于请求内存的应用程序。

请记住,这是一个很好的做法,如果你正在请求连续内存,它应该是需要的,因为内核正在尽力分配物理上连续的内存。kmalloc()和vmalloc()也有它们的限制

  1. 把我们要读的东西放在一起可以利用空间局域性,我们需要的东西更有可能被缓存。

  2. 不确定这个

  3. 我相信这意味着如果页面不是连续的,TLB必须做更多的工作来找出它们都在哪里。如果它们是连续的,我们可以将一个进程的所有页面表示为PAGES_START + PAGE_OFFSET。如果不是,我们需要为给定进程的所有页面存储单独的索引。因为TLB的大小是有限的,我们需要访问更多的数据,这意味着我们将交换更多的数据。

内核不需要物理上连续的页面,实际上它只需要效率和稳定性。单片内核倾向于在进程之间共享一个页表的内核空间并且不希望在内核空间上出现使内核设计过于复杂的页面错误

所以通常32位架构的实现总是3g/1g分割为4g地址空间对于1g内核空间,代码和数据的正常映射不应该产生太复杂而无法管理的递归页面错误:您需要查找空页帧,在mmu上创建映射,并在每个内核侧页故障时处理新映射的TLB刷新内核已经忙于处理用户端页面错误

此外,1:1线性映射可以有更少的页表项,因为它可以利用更大的页单位大小(>4kb)表项越少,TLB漏失次数越少。

所以内核线性地址空间上的伙伴分配器总是提供物理上连续的页帧甚至大多数代码都不需要连续帧但是许多需要连续页帧的设备驱动程序已经相信通过通用内核分配器分配的缓冲区在物理上是连续的

最新更新