Linux内存架构
为了执行一个进程,Linux内核为请求的进程分配一部分内存区域。该进程使用该内存区域作为其工作区并执行请求的工作。它与你的申请一个办公桌,然后使用办公桌来摆放纸张、文档和备忘录来执行你的工作类似。不同之处是内核必须使用更动态的方式来分配内存空间。有时运行的进程数会达到数万个,但内存的数量是有限的。因此,Linux内核必须有效地处理内存。在本节,我们将会讲述Linux的内存结构、地址分布和Linux如何有效地管理内存空间。
1 物理和虚拟内存
今天我们已经要面对选择32位和64位系统的问题。对于企业级客户的其中一个最重要的不同是虚拟内存的地址是否能超过4GB。从性能的角度来看,理解32位和64位系统中Linux内核如何把物理内存映射到虚拟内核是重要的。
从图1中,可以看出Linux内核在处理32位和64位系统内存的方式上的明显的差别。介绍内存内存到虚拟内存的映射细节已经超出了本文的范围,所以本文着重介绍Linux内存结构的部分细节。
在32位的架构上,如IA-32,Linux内核只能直接访问物理内存的前1GB(当考虑部分保留是为896MB)。所谓的 ZONE_NORMAL之上的内存必须要被映射到1GB以上的内存中。该映射对于应用来说是完全透明的,但是在ZONE_HIGHMEM中申请内存页会导致一个性能的稍微下降。
另一方面,在64位的架构上,如x86-64(也叫x64),ZONE_HIGHMEM可以一直延伸到64GB,或者在IA-64系统上可以延伸到128GB。正如你所见到的,通过64位的架构,内存页从ZONE_HIGHMEM到ZONE_NORMAL的映射开销可以被消除。
图1 32位和64位系统的Linux内核内存布局
虚拟内存地址布局
图2展示了32位和64位架构的Linux虚拟地址布局。
在32位架构上,一个进程能访问的最大的地址空间为4GB。这是32位虚拟地址的一个限制。在标准的实现里,虚拟地址空间被分为3GB的用户空间和1GB的内核空间。这有一点类似于4G/4G寻址布局实现的变种。
另一方面,在64位架构中,如x86-64和IA64,没有此限制。每个单独进程都能得益于于广阔而巨大的地址空间。
图2 32位和64位架构的虚拟内存地址布局
需要C/C++ Linux服务器架构师学习资料私信“资料”(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享
1.2.2 虚拟内存管理
操作系统 的物理内存架构对于应用和用户来说通常是不可见的,因为操作系统会把任何的物理内存都映射到虚拟内存中。如果我们想要理解在Linux操作系统中的调优的可能性,我们必须理解Linux如何处理虚拟内存。正如1.2.1中“物理内存和虚拟内存”的介绍,应用并不能申请物理内存,但当向Linux内核请求一定大小的内存映射,得到的是一个虚拟内存的映射。如图3所示,虚拟内存不一定要映射到物理内存中。如果你的应用申请了大量的内存,这些内存中的一部分可能映射到磁盘的swap文件中。
图3展示了,应用程序通常直接写不直接写磁盘,而是直接写缓存(cache)或缓冲(buffer)。当 pdflush 内核线程空闲或者文件大小超出了缓存缓冲大小时,pdfflush内核线程会将缓存/缓冲的数据清空并写入到磁盘中。参阅“清空脏缓冲”。
图3 Linux虚拟内存管理
Linux内核处理物理磁盘的写操作与Linux管理磁盘缓存紧密相连。其他的操作系统只分配部分内存作为磁盘缓存,而Linux处理内存资源则更加有效。默认的虚拟内存管理配置分配所有可用的空闲内存作为磁盘的缓存。因此在拥有大量内存的Linux系统中,经常看到只有20MB的空闲内存。
在相同的情况下,Linux管理swap空间也非常有效率。swap空间被使用时并不意味着出现内存的瓶颈,它恰恰证明了Linux管理系统资源如何的有效。详见“页帧回收”。
页帧的分配
一页是一组连续线性的物理内存(页帧)或虚拟内存。Linux内核以页为单位管理内存。一页的大小通常为4K字节。当一个进程申请一定数量的页时,如果可用的页足够,Linux内核马上分配给进程。否则,内存页必须从其他一些进程或内存页缓存中获取。Linux内存知道可用的内存页的数量及位置。
伙伴系统
Linux内核通过一种被称作 伙伴系统 的机制管理空闲页。伙伴系统管理空闲页并尽力为分配请求分配页。它尽最大努力保持内存区域的连续。如果不考虑分散的小页,将会导致内存碎片,并导致在连续区域内申请一大段的页变得困难。它将导致效率低下的内存使用和性能下降。
图4说明了伙伴系统如何分配页。
图4 伙伴系统
当尝试分配页失败,页回收会被激活。参阅“页帧回收”。
你可以通过/proc/buddyinfo查找伙伴系统的信息。详见“Memory used in a zone”。
页帧回收
当一个进程请求一定数量的页的映射时,如果页不可用,Linux内核新的请求尝试通过释放某些页(先前使用过但现在不再使用,但基于某些原则仍然被标记为活动状态的页)并分配内存给该进程。这个过程被称为面帧回收。 kswapd 内核线程和try_to_free_page()内核函数被用来负责页的回收。
kswapd线程通常处于可中断的睡眠状态,当某一区域中的自由页低于一个阈值时,kswapd线程会被伙伴系统调用。它尝试基于最近最少使用 算法 从活动页中找出候选页。最近最少使用的页将会被首先释放。活动列表和非活动列表被用于维护候选页。kswapd扫描部分活动列表并检查页的使用情况,把最近没有使用的页放到非活动列表中。你可以使用vmstat -a命令查看哪些内存是活动的和哪些内存是非活动的。
kswapd也遵循其他原则。页的使用主要是为了两个用途:页缓存和进程地址空间。页缓存是页映射到一个磁盘文件。属于一个进程地址空间的页(被称为匿名内存,因为它没有映射到任何文件,也没有名字)被用于堆和栈。当kswapd回收页时,它将会尽量压缩页缓存而不是把进程的页page out(或者swap out)。
Page out 和 swap out :“page out”和“swap out”很多时候都会被混淆。“page out”是指把页(整个地址空间的一部分)放到swap区,而“swap out”是指把整个地址空间放到swap区。但是它们有时候可以交换使用。
大部分被回收和进程地址空间的页缓存的回收取决于其使用场景,并将对性能产生影响。你可以通过使用/proc/sys/vm/swappiness对该行为进行一些控制。
swap(交换区)
如前所述,当页回收发生时,在非活动列表中属于该进程地址空间的候选页将会被page out。发生交换本身并不意味着发生了什么状况。虽然在其他系统中,swap只不过是万一发生了主要内存的过度分配的一种保障,但是Linux更有效地使用swap空间。如图3所示,虚拟内存由物理内存和磁盘或者swap分区共同组成。在Linux的虚拟内存管理的实现中,如果一个内存页已经被分配,但是在一段时间内都没有被使用,Linux会把该内存页移动至swap空间中。
你经常可以看到如getty的守护进程,它们通常当系统启动时被启动,但几乎不被使用。释放页所占的珍贵的主内存并把它移至交换区似乎是更加高效的。这正是Linux管理swap的方式,因此当你发现交换区已经使用了50%并不需要惊慌。事实上,swap空间开始被使用并不意味着内存瓶颈;相反地,它证明了Linux如何高效地管理系统资源。