本期“干货”栏目,小编为大家请来腾讯课堂coding学院大咖为大家进行分享,从linux的内存切入,对Linux的技术点进行组织,通过一份知识图谱,方便大家进行更深入的学习和总结,快跟随小编开始学习吧~
一、内存寻地址
1、分段与分页
我们一般的服务器大多是基于intel的x86体系的cpu平台。在x86体系的架构下,内存的寻址最早是基于分段来做的,而随着技术的进步,分段低效且缺乏灵活性的弊端逐渐暴露了诸多的问题。业界主流的硬件内存寻址方式都转向了分页,但intel为了兼容早期分段的产品,并没有完全抛弃分段的设计,而是采取了一个巧妙的办法:先进行分段,cpu运行在实模式,该模式下用以兼容旧的分段。在实模式的基础上,再次引入分页,进入保护模式。这一设计使得intel的产品也跟上主流。但是,随着产品的继续演进和技术的进步,intel的这一设计也成为了其历史包袱,除intel以外其它的平台都是采用分页,而x86的实模式也基本无人再使用,但仍不可回避的还是先分段再分页。这样,也使得基于x86体系cpu的操作系统都不可回避分段和分页的问题。linux也不除外。
linux下,针对x86的分段和分页下的寻址也做了一个欺骗式的设计。我们都知道,分段是基于段基址+偏移组成的,通过分段,逻辑地址映射到了线性地址(也即我们通常的虚拟地址),linux直接将所有地址的段基址直接置0,这样就使得linux所有的地址(包括用户空间和内核空)都是基于0段开始寻址,地址的偏移也即成了通常的意义下的虚拟地址。这种设计相当与跳过x86下没有什么用的分段机制,而直接进入了分页寻址。
分页寻址的基本思想是,将线性地址切分为几级,每级做为一个页目录项,用来查找相应的页表项,在页表项中查询下一级的地址或最终的页地址。linux下分页寻址的示意图如下:
图中的cr3,寄存器为x86下用以保存正在使用的页目录的物理地址,在进程空间切换时,有着巨大的作用。
早期的linux采用的是三级页表模型,在2.6.11以后采用的是四级模型。对于没有开PAE的32位系统,两级目录项已经够用,将页上级目录和页中间级目录置0。对于启用PAE的32位系统使用了三级页表,即取消了页上级目录。64位系统下,启动四级还是三级取决于硬件cpu平台。x86下采用了四级。
2、MMU和TLB
通过分页的计算,线性地址最终找到了硬件内存上真正的物理地址。但是,大家发现没有,虚拟地址的寻址在任何程序中都可以说是最基础的需求,寻址必然会相当的频繁。而如果每次都通过页表计算的方式来查找物理地址,对于cpu来说必定是一个庞大的开销。于是,在硬件技术进步的过程中,出现了MMU和TLB这两种硬件单元。
MMU可理解为cpu一个单独的模块,其功能就是用来计算虚拟地址到物理地址的查找过程。计算机技术的进步中,将可以从软件中剥离出来的计算分给硬件来做是一个性能提高的发展规律,MMU就是这样一个为了计算页表地址而生的硬件模块。
TLB则是为了保存虚拟地址和物理地址映射关系的一个缓存,对于经常需要访问到的虚拟地址,没必要每次都重新计算,查找物理地址。因此,保存了一个这样的缓存。就像cpu的cache一样,TLB保存的算法能否提高命中率,在计算机系统中对整体的性能也是相当重要的。
3、内核页表
这里再延伸一个问题,我们都知道页目录项及页表的内容是需要初始化,用户进程在创建时,操作系统会帮忙创建并初始化。那么问题来了,操作系统在启动时,谁又会去初始化内核自己的页表呢?
事实上,操作系统在启动之处,内核在编译过程中就静态的初始化了一个临时页表,该页表可以映射物理内存前8M空间。该临时的内核页表用以初始化内核的启动过程,在初始化完成之后,内核会根据实际的物理内存的大小选择不同的内核最终页表的建立方式。
在32位系统下,
当物理内存小于896M时,内核的最终页表会将从0开始到结束的所有物理地址线性映射至PAGE_OFFSET开始的线性地址(即默认线性地址的3GB位置),这里的线性映射即所有的物理地址按固定偏移映射到线性地址
当物理内存大于896M而小于4G时,内核的最终页表会将0开始到896M的物理地址同样线性映射,其余的物理内存等待需要使用时再进行动态映射。
当物理内存大于4G时,线性映射与上类似,大于4G的空间需要开启PAE功能才能访问。
内核下为何要建立线性映射呢?主要是考虑了内核下访问的效率,另一方面是内核所需的空间并不会很大,因为内核权限用户层无法访问,空间大小比较好把控。多级映射好处是更短的地址可以寻址更大的空间,这在内核空间下显得没有那么必要。当然,内核下还是保存一部分空间用以动态的非线性映射及永久映射和固定映射,即896M之后的空间。
二、内存管理
1、硬件内存模型
在内存的硬件结构上,内存可分为NUMA和UMA。UMA模型是指物理内存是连续的,SMP系统中的每个处理器访问各个内存区都是同样快的;而NUMA模型则是指SMP中的每个CPU都有自己的物理内存区,虽然CPU可以访问其他CPU的内存区,但是要比方位自己的内存区慢得多。我们一般使用的物理模型都是UMA模型。为了NUMA模型,Linux提供了三种可能的内存布局配置:Flat Memory, Sparse Memory, Discontiguous Memory。
2、Linux系统页大小的选择
主流的cpu硬件平台都是支持基于分页的寻址方案。x86主流的cpu支持的分页大小为4k,2M(PAE),4M。那在linux下,操作系统软件层面支持的页面大小理应为其整数倍,这样的传输才会更加高效。因此,我们的linux系统默认采用的页大小为4K,当然,linux也支持更多的4K整数倍的页大小,设置大页属性需要修改内核配置参数,并重新编译内核。大页在特定的场景下,可以带来性能更大程度上的提升,主要体现在:
① 大页的使用,会更大程度的减少缺页中断带来的额外开销
② 大页同理也能减少TLB对于页表映射的压力
当然,大页特性如果应用不当,也会带来更多内存浪费,如何选择则是在特定的场景和技术方案下才有意义。
3、Linux的内存管理
Linux下,物理内存的管理是按区划分的,一般来说32位系统的内存可以分为:
ZONE_DMA 低于16M的内存(用于DMA访问)
ZONE_NORMAL 高于16M低于896M的内存
ZONE_HIGHMEM 高于896M的内存 (通常称之为高端内存)
前面已经提到,低于896M的内存和虚拟内存是线性映射的,内核可以直接访问到,而ZONE_HIGHMEM则需要调用特定的接口动态映射使用。
在64位的系统下,由于虚拟地址空间远远大于可安装的物理内存,因此,64位下的ZONE_HIGHMEM总是空的。
高端内存的使用,可以通过三种机制来映射使用,分别永久内核映射,临时内核映射,非连续内存分配,该三种机制我们这里不详表。
linux对物理内存的管理主要还是体现在其分配和回收上。
这就要提到著名的伙伴系统算法了,接下来我们重点看下该算法为什么适用于linux下的内存管理。
对于内存这种频繁申请释放的资源,最大的问题应该是如何避免内存碎片问题。通常思路,为避免内存碎片的情况,可以有两种思路:
① 在分页时,使用非连续的空闲物理页映射到连续的线性地址空间
② 使用一种记录方式现存的空闲连续页的情况,尽量避免为满足小块内存的请求而分割连续的大空闲块
第一种思路在分配连续的物理内存时可能时常会遇到困难。同时,非连续的空闲物理页时常映射必然导致页表频繁的被修改,这样TLB的命中率也会下降,对性能是有比较大的影响的。
因此,linux选择了第二种思路做为解决方案,在具体的算法上选择了业界比较成熟的伙伴系统算法。linux把所有的物理空闲页分成了11个块链表,每个块链表分别包含了大小1,2,4,8,16,32,64,128,256,512和1024个连续的页。对1024个页的最大请求对应4M的连续内存块。每个块的第一个页的物理地址是该块大小的整数倍。如,大小为16个页的块,其起始地址是16*4096的倍数。
在伙伴算法的控制下,申请内存和释放内存块按照伙伴算法,选择合适大小的链表取内存块,如若没有合适大小的内存块,则从大小接近的链表中,合并或切分内存。释放内存过程则是逆过程,将释放的内存选择合适的链表并加入其中。具体伙伴算法的细节,大家可参考相关资料,这里不细表。
伙伴算法管理的内存管理的内存适合大块的内存,但如果对小内存管理,如几个几十几百个字节的空间。如直接给一个页则导致内存空间的浪费。linux针对小内存的管理,提供了另一个专门的分配器,slab分配器。linux的提供配置一组slab/slob/slub的选择,不同的分配器在不同的应用场景下有着不同的应用。我们这里简单的了解一下slab分配器的基本原理,slab分配器申请了一系列的连续物理内存,该内存保存在系统的高速缓存当中,当需要小内存从这些高速缓存中直接申请。slab分配器并不直接释放已分配的内容,而是等到系统的内核线程周期性的扫描高速缓存并释放slab的内容。
三、总结
作为linux内核中最为复杂,最难理解的子系统之一,内存管理子系统渗透到各个其它的子系统中,内存也是其它子系统的资源基础保障。本文聚焦在内存寻址和内存管理申请和释放上,内存其它相关的内容,如进程的内存空间分布,内存的回收及内存swap的相关内容后续再与大家一起继续分享。
作为结束,再和大家分享一张整理的32位下linux内存管理的图,大家可以一起感受一下。
温馨提醒:
1、微信端搜索课程
在“腾讯课堂”官方微信里,回复你想学习的内容,即可快速找到你期待的课程哦!
2、学习方式
【电脑端】
*可通过登录ke.qq.com进入学习;
*可通过windows PC版QQ客户端面板上的课堂入口进入学习。
【移动端】
*下载APP “腾讯课堂” 即可进入学习;
*关注微信公众号或者手Q公众号“腾讯课堂”,进入学习。
(注:微信和QQ的课程报名信息独立,登录时请选择对应的登录方式)