Linux使用虚拟内存技术,因此在应用层所能看到的、访问的都是虚拟地址。对于32位系统来讲(本文涉及的都是32位系统),每个进程能够寻址的地址空间都是4G,不管物理内存有多大。应用开发者实际上是能够不用关系内存空间的划分,仅仅使用封装后的接口就能够完成开发。但在工做中,若是对地址空间没有基本的了解,在程序设计和解决问题时可能会引发方向性错误。这里对地址空间进行简单介绍,下图时网上常见的x86架构的内存区域划分。linux
物理内存是映射到Kernel地址空间的,这个空间也是虚拟的,分布在进程虚拟地址空间的3G~4G,0~3G为用户地址空间。下图也是从网络上得到的。编程
Linux中的用户空间看到的是连续的虚拟地址,在被真正使用时仍是须要转换为真实的物理地址。根据虚拟地址来查询物理地址的过程被称为walking page tables。由于Linux使用分页机制来管理内存,基本的结构以下图。缓存
当前的Linux设计了四级页表,分别是PGD -> PUD -> PMD -> PTE。其中PUD和PMD不是必须的,因此系统能够根据硬件构架使用二级、三级或四级的页表模式。一般在32位系统中,二级页表就能够知足需求。下图是一个二级页表的查找过程。网络
能够看到,一个虚拟地址被分为一级查找索引,二级查找索引和页索引三部分。根据一级页表PGD的基地址(保存在协处理器CP15:C2中),结合一级查找索引能够获取到二级页表PTE的基地址。再将二级页表基址与二级查找索引结合,能够获取到物理页的基地址。物理页基址与页索引结合就是须要查找的物理页。架构
页表的查找过程在系统中很是频繁,所以须要经过硬件来完成,这个硬件就是MMU。MMU被称为内存管理单元,它不只仅负责虚拟地址到物理地址的转换,还负责内存访问权限和高速缓存的管理。编程语言
上文已经说到了内存管理单元MMU,其最主要的工做就是进行地址转换。为了加快地址转换的速度,一般将最近访问的虚拟地址和转换后的物理地址的对应保存在一个高速缓存中,这个缓存就是TLB(Translation Lookaside Buffer)。由于在程序运行时,其访问过的地址被再次访问的几率很高,当TLB中保存了须要访问的地址时,就能够免去页表查找的过程,能够大大提升系统性能。ide
在进行地址转换时, 首先访问的是TLB,在TLB查找是否有该虚拟地址的缓存。若是找到(a TLB hit),直接返回物理地址。若是在TLB中没法找到(a TLB miss),则须要经过MMU进行页表查找得到物理地址,同时将新的查找更新到TLB中。某些状况下,例如使用磁盘作为swap时,须要访问的内存页可能不存在page table中。这时须要将访问页回写到物理内存中,同时更新page table和TLB。函数
内核中常见的内存分配函数有vmalloc、kmalloc、__get_free_pages等,分别介绍一下。工具
内核中最经常使用的内存分配函数就是kmalloc(),当须要申请小块内存时,优先考虑使用kmalloc。kmalloc是基于slab分配器进行工做的,先简单介绍一下slab。性能
kmalloc就是经过slab的普通高速缓存来分配内存的。kmalloc的实现也很简单,就是在slab的普通高速缓存中寻找一个大小最匹配缓存。由于kmalloc分配的内存是物理连续的,而物理连续的内存是很是珍贵的,因此除非必要,不然大块内存(以页为单位)分配应该使用vmalloc和get_free_pages。kmalloc能够分配的最大值在不一样的硬件架构上是不一样的,而且使用的分配器类型也有影响。例如在ARM32上使用slub分配器时,kmalloc()可接受的最大size为8M。可是当size大于8K时,kmalloc()的内部实现调用了__get_free_pages()。
上面讲到过,highmem中有一块vmalloc区域,这个地址空间就是经过vmalloc()分配使用的。vmalloc的特色以下,
__get_free_pages从Buddy系统中分配的页面,其分配的页数是2的幂数。Buddy系统是Linux用来组织和管理内存页面的方法,用来解决内存外部碎片问题。Buddy系统把全部的空闲页框分组为11个块链表,每一个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块。最大能够申请1024个连续页框,对应4MB大小的连续内存。每一个页框块的第一个页框的物理地址是该块大小的整数倍。
假设要申请一个256个页框的块,先从256个页框的链表中查找空闲块,若是没有,就去512个页框的链表中找,找到了则将页框块分为2个256个页框的块,一个分配给应用,另一个移到256个页框的链表中。若是512个页框的链表中仍没有空闲块,继续向1024个页框的链表查找,若是仍然没有,则返回错误。页框块在释放时,会主动将两个连续的页框块合并为一个较大的页框块。
__get_free_pages分配的是物理内存上连续的页面,因此没有特殊需求时也不该该一次性分配较大的内存。在须要分配大内存时,若是不需求物理上连续,能够一页一页分配,而后映射到连续的虚拟地址空间上。
开发驱动时,有时须要为硬件设备分配一段内存用于DMA传输,这时可能就会用到dma_alloc_coherent()。为DMA分配的内存会有如下特色,
ION是google为了解决不一样Android设备的内存碎片问题,提出的一种内存管理器。它支持不一样的内存分配机制,如CARVOUT(PMEM),物理连续内存(kmalloc),虚拟地址连续但物理不连续内存(vmalloc), IOMMU等。
ION经过Heap来管理不一样的内存空间,每一个Heap须要实现本身的操做内存方法,好比allocate, free, map等。对ION的使用须要经过Client完成,用户空间和内核空间均可以成为Client。内核空间经过ion_client_create()获取Client,用户空间经过打开/dev/ion来获取Client的fd。
本着谁分配谁释放的原则,不管时应用中经过malloc分配的内存仍是内核中经过kmalloc分配的,在使用完成时必定记得使用free类函数进行释放。代码上很简单,重要的是养成良好的编程习惯。同时也要注意不要对分配的内存屡次释放,这样一样会致使异常。内存的分配释放提及来很简单,但也是最容易出问题的地方。因此就出现了大量的内存检测工具。若是软件开发已经开始使用内存检测工具了,就已经太晚了。但咱们又没法避免内存问题,即便带有自动回收的编程语言也是不能彻底避免。内存问题在软件开发中一直是个难题。
Linux系统中使用free命令来查看内存时,能够看到有两项是“buffers”和“cached”。这两项的意义是,
buff/cache是用来缓存磁盘文件数据的,用来提升IO访问速度。当系统运行一段时间后,会发现buff/cache占用的内存不少,但这些内存被认为是能够available的。当内存短缺时,系统会触发内存回收,这部份内存就会释放。还有一种手动清理Cache的方法,经过下面这个命令。
$ echo 3 > /proc/sys/vm/drop_caches
但咱们不建议用命令行来强行回收Cache,这样会破坏系统内存管理。Cache回收更多仍是应该依赖系统的内存回收机制。也能够扩展原生的内存回收,增长本身的回收机制,像Android的lowmemorykiller那样。若是内存回收仍然没法解决内存短缺问题(在嵌入式系统中常常发生),能够试图去调整vm的一些参数,在“/proc/sys/vm/”下,这就须要对内存管理有一个正确的理解。也能够试图去调整磁盘挂载的参数,这也可能影响到Cache。具体的调整方法不在这里详细说明,若是之后写到内存优化时会单独写一篇。
当系统内存不足时,内核会启动内存回收。内存回收的时机有如下三种,
kswapd是内存回收机制中最重要的方式,它做为守护进程在后台周期运行,根据预约的水位进行回收。Linux系统中每个内存区域(zone)都会存在一个kswapd,同时每一个区域也定义了一组watermark来作为参考水位。
内存回收的目的是为了保证系统有足够的内存能够正常运行,但当kswapd频繁回收时也会对系统形成压力,有时能够看到kswapd的CPU占用率很高,就是由于回收过于频繁。这种状况下就须要根据系统状态来进行内存调优,主要是调整watermark。基本原则是避免内存低于WMARK_MIN,根据系统运行状态设置合理的WMARK_HIGH,选择合适的时机启动后台内存回收。这些又是内存调优的话题,须要另外一篇来阐述。
本文只是对内核的内存管理作简单的介绍,目的是对其有一个总体的认识。其中的每一部分展开来都是很大的课题,本人水平有限再也不深刻分析。文章中的图片所有来自网络,源头也不是很清楚。