Linux内存管理
一个进程执行时,Linux内核给其分配一部分内存区域。进程使用这个内存区域作为工作区执行必要的工作。这类似于你的单位给你分配办公桌,然后你在桌面分散文件、文档和备忘录来执行你的工作。不同的是内核使用动态的方式来分配空间。同时运行的进程数量有时是成千上万的,并且内存的数量通常也是有限的。因此,Linux内核必须有效地处理内存。
物理内存和虚拟内存
系统的内存管理是很复杂的,一般由内核来完成。高效的内存管理对于提升进程的性能至关重要。现代的计算机系统使用分页(page)技术安全和灵活地管理系统内存。
为了提高效率,计算机系统上的内存是由固定大小的chunk组成的,被称为分页(page)。分页的大小取决于处理器体系结构,i386和x86_64中的分页大小是4KB。系统上的物理内存被分成页帧(page frame),一个页帧包含数据的一个分页。
页帧分配
一个分页(page)是物理内存(page frame)或虚拟内存中一组连续线性地址。Linux内核以内存页为单位处理内存。一个内存页通常是4KB大小。当一个进程请求一定数量内存页的时候,如果有有效的内存页,Linux内核立刻将它们分配给进程。否则,需要从一些其他的进程或分页缓存中得到。内核知道有多少内存页是有效的,并且也知道它们在什么位置。
进程是不能直接对物理内存寻址的,取而代之的是,每个进程有一个虚拟地址空间。当为一个进程分配内存时,页帧的物理地址被映射到进程的虚拟地址。这样从进程的角度来看,它有一个私有内存空间,它只能看到已经对物理页帧做了映射的一个虚拟地址。除此之外,这样还有助于加强进程之间的安全限制和明确界限。
一个进程虚拟地址空间的大小取决于处理器架构。在32位i386系统上,一个进程的虚拟地址空间大小是232 B(4 GB);在64位x86系统上,是264 B(16 EB)。然而,一个进程通常不会使用它全部的地址空间,它的大部分地址空间是未分配的,并没有映射到任何物理内存。但是一个进程能拥有的虚拟地址空间的大小能够在最大内存之下设置一个限制。
今天我们正面临着32位系统和64位系统的选择。它们最重要的区别之一就是能否为企业级用户提供4GB以上的虚拟内存地址。那么Linux内核在32位系统和64位系统上是如何将物理内存映射到虚拟内存中的呢?
虚拟内存寻址布局
在32位架构上,单个进程可以访问的最大地址空间是4GB。这是由于32位虚拟地址本身具有的限制。在一个标准的实现中,虚拟地址空间被划分为3GB用户空间和1GB的内核空间。
另一方面,在64位架构上比如x86_64和IA-64中,没有这样的限制存在,每个进程都可以访问巨大的地址空间。
虚拟内存管理
一个操作系统的物理内存架构对于应用程序和用户来说通常是隐藏的,因为操作系统可以将任何物理内存映射到虚拟内存。如果你想理解在Linux操作系统中如何调优,则需要了解Linux是如何处理虚拟内存的。正如我们前面讲述的,系统不会给应用程序分配物理内存,但是它会向Linux内核请求一定大小的虚拟内存,并在虚拟内存中交换得到的映射。虚拟内存不必映射到物理内存。如果你的应用程序被分配了大量的内存,则有一部分可能被映射到磁盘子系统上的swap文件。
应用程序通常不直接向磁盘子系统写入,而是向高速缓存(cache)或者缓冲区(buffer)写入。当时间片到达时,或者一个文件的大小超出缓冲缓存时,内核线程pdflush/Per-BDI flush会将缓存/缓冲中的数据刷新到磁盘中。
与Linux内核处理写入物理磁盘子系统紧密相关的是Linux内核管理磁盘缓存的方式。其他操作系统仅分配某一部分内存作为磁盘缓存,而Linux可以更有效地处理内存资源。管理虚拟内存的默认配置是:分配所有有效的空闲内存空间作为磁盘缓存。因此,你有可能会看到有的生产环境的Linux系统拥有GB内存,但是只有20MB的内存是空闲的。
在同样的情况下,Linux也可以非常有效地处理swap空间。正在使用的swap空间并不一定是内存瓶颈,但是有时它证明了Linux如何有效地处理系统资源。
伙伴系统(buddy system)
Linux内核使用一种被称为伙伴系统(buddy system)的机制来维护它的空闲分页。伙伴系统维护空闲分页,并尝试给分页分配请求分配分页。它试图保持内存区域是连续的。如果不考虑分散的小分页,这可能会导致内存碎片,并会导致更加难以在连续的区域中分配一个很大的分页。它也可能导致低效的内存使用和性能下降。
当分配分页失败时,会进行分页回收。 你可以通过/proc/buddyinfo找到伙伴系统的信息。
分页回收
当一个进程请求映射一定数量分页的时候,如果没有有效的分页,Linux内核将尝试释放一定数量的分页(这是之前使用但是不再使用且基于某些原因仍被标记为活跃的分页),然后将这些分页分配给新的请求内存的进程。这个过程被称为分页回收(page reclaiming)。内核线程kswapd和内核函数try_to_free_page()负责分页回收。
kswapd在任务中通常处于可中断睡眠状态,当区域中的空闲分页低于一个阀值时它被称为伙伴系统。基于最近最少使用(Least Recently Used, LRU)原则,它试图找到候选分页并将其取出作为活跃分页。最近最少使用的分页被首先释放。活跃列表和非活跃列表用于维护候选分页。kswapd扫描活跃列表,并检查最近使用的分页,将最近没有使用的分页放入非活跃列表中。可以通过vmstat -a命令查看活跃和非活跃的内存有多少。
kswapd也遵循另一个原则。分页的使用主要有两个目的:分页缓存(page cache)和进程地址空间(porcess address space)。分页缓存是分页被映射到磁盘上的一个文件。分页属于一个进程地址空间(称为匿名内存,因为它不被映射到任何文件,并且它没有名称),它被用于堆(heap)和栈(stack)。当kswapd回收分页的时候,它宁可缩小分页缓存也不愿分页移出(page out或swap out)进程拥有的分页。
短语page out和swap out有些时候令人困惑。page out意味着一些分页(整个地址空间的一部分)被放入swap空间而swap out意味着整个地址空间放入swap空间。它们有些时候可以互换使用。
分页缓存的分页回收和进程地址空间的回收在很大程度上依赖于使用场景,这将会影响性能。你可以通过使用/proc/sys/vm/swappiness来控制这些行为。
SWAP
如前所述,当发生分页回收时,在非活跃列表中属于进程地址空间的候选分页可以被分页移出。交换这种情况本身并不是问题。在其他操作系统中,交换无非是为了保证主内存的分配,而Linux使用交换技术能够更加有效地使用空间。虚拟内存由物理内存和磁盘子系统或swap分区组成。如果在Linux中虚拟内存管理器发现内存分页已经被分配,但是大量时间还没有使用完,它会将这个内存分页移动到swap空间。
一些守护进程,比如getty,当系统启动时它就启动,但是很少被使用。看来,更有效的方法是,释放一个昂贵的主内存分页并将其移动到swap区。这就是Linux中处理swap分区的方法。所以如果你发现swap分区被填充到50%,也不需要惊慌。swap空间的使用率高并不一定表明内存存在瓶颈,反而有时它证明了Linux如何有效地利用系统资源。