linux内存管理--内存管理机制综述

    在理解linux内存管理之前需要知道80x86的分段和分页单元把逻辑地址转换为物理地址的有关内容。整个系统的性能取决于如何优先地管理动态内存。从两个角度介绍:连续物理内存处理、非连续内存区的处理。

一、页框管理

    Linux采用4KB页框大小作为标准的内存分配单元。内核把物理页作为内存管理的基本单位。内存管理单元(MMU,管理内存并把虚拟地址转换为物理地址的硬件)通常以页为单位进行处理。MMU以页大小为单位来管理系统中的页表(这也是页表的由来)。从虚拟内存的角度来看,页就是最小单位。

  1)页描述符

    内核必须记录每个页框的当前状态。页框的状态信息保存在一个类型为page的页描述符中,所有的页描述符存放在meme_map数组中。下面为struct page的结构体,其中省去了两个联合结构体。

struct page{
     unsigned long               flags;
     atomic_t                         _count;
     atomic_t                         _mapcount;
     unsigned long                private;
     struct address_page      *mamming;
     pgoff_t                            index;
     struct list_head               lru;
     void                                 *virtual;

}
flags  一组标志,也对页框所在的管理区进行编号,包含多达32个用来描述页框状态的标志;

_count 页的引用计数器。为-1,则相应页框空闲,并可被分配给任一进程或 内核本身,如果大于0,说明页框被分配给了一个或多个进程,或用于存放一些内核数据结构。

 linux内存管理--内存管理机制综述_第1张图片

    上图为node、page、zones之间的区别联系

  2)内存管理区

    一个页框就是一个内存存储单元,可用于任何事情。任何种类的数据页都可以存放在任何页框中,没有限制。

    每个页描述符都有到内存节点和到节点内管理区(包含相应页框)的链接。

二、内存区管理

   内存区,也就是说具有连续的物理地址和任意长度的内存单元序列。伙伴算法采用页框作为基本内存区,这适合于对大块内存的请求,但对于小内存区的请求同样需要办法。

  1)slab分配器

    新的算法基于下列前提:

  • 所存放数据的类型可以影响内存区的分配方式。
  • 内核函数倾向于反复请求同一类型的内存区。
  • 对内存区的请求可以根据它们发生的频率来分类。
  • 在引入的对象大小不是几何分布的情况下。

    slaba分配器把对象分组放进高速缓存。包含高速缓存的主内存区被划分为多个slab,每个slab由一个或多个连续的页框组成,这些页框中既包含已分配的对象,页包含空闲的对象。

  1)高速缓存描述符

    每个高速缓存都是由kmem_cache_t类型的数据结构来描述的,高速缓存中的每个slab都有自己的类型为slab的描述符。

  2)普通和专业高速缓存

    高速缓存被分为两种类型:普通和专用。普通高速缓存只由slab分配器用于自己的目的,而专用高速缓存由内核的其余部分使用。

三、非连续内存区管理

    把内存区映射到一组连续的页框是组号的选择,这样利用高速缓存和较低的访问时间,不过,如果对内存区的请求不是很频繁,那么,通过连续的线性地址来访问非连续的页框这样一种分配模式就会很有意义。这种模式的有点避免了外碎片,缺点是必需打乱内核页表。linux在以下几个方面使用非连续内存区:为活动的交换区分配数据结构、为模块分配空间、给某些I/O驱动程序分配缓冲区。

  1)非连续内存区的线性地址

    要查找线性地址的一个空闲区,可以行PAGE_OFFSET开始查找(通常为0xc0000000,既第4个GB的起始地址),下图为第4个GB的线性地址:

  • 内存区的开始部分包含的是对前869MB RAM进行映射的线性地址(涉及“进程页表”的内容),直接映射的物理内存末尾所对应的线性地址保存在high_memory变量中。
  • 内存区的结尾部分包含的是固定映射的线性地址(涉及“固定映射的线性地址”)
  • 从PKMAP_BASE开始,我们查找用于高端内存页框的永久内核映射的线性地址(涉及“高端内存页框的内核映射”)
  • 其余线性地址可以用于非连续内存区。在物理内存映射的末尾与第一个内存区之间插入一个大小为8MB的(宏VMALLOC_OFFSET)安全区,目的是为了“捕获”对内存的越界访问。处于同样的理由,插入其他4KB大小的安全区来隔离非连续的内存区。
linux内存管理--内存管理机制综述_第2张图片

    为非连续内存区保留的线性地址空间的其实地址由VMALLOC_START宏定义,而末尾地址由VMALLOC_END宏定义。

  2)非连续内存区的描述符

    每个非连续内存区都对应着一个类型为vm_struct的描述符。这些描述符在一个简单的链表中。get_vm_area()函数在线性地址VMALLOC_STRAT和VMALLOC_END之间查找一个空闲区域。该函数使用两个参数:将被创建的内存区的字节大小(size)和指定空闲区类型的标志(flag)。步骤执行如下:

  • 调用kmalloc()为vm_struct类型的新描述符获得一个内存区。
  • 为写得到vmlist_lock锁,并扫描类型为vm_struct的描述符链表来查找线性地址一个空闲区域,只是覆盖size+4096个地址(4096是内存区之间的安全区间大小)。
  • 如果存在这样一个区间,函数就初始化描述符的字段,释放vmlist_lock锁,并以返回这个非连续内存区的其实地址而结束。
  • 否则,get_vm_area()释放先前得到的描述符,释放vmlist_lock,然后返回NULL。

  3)分配连续的内存区

    vmlloc()函数给内核分配一个非连续内存区,参数size表示请求内存区的大小。如果这个函数能够满足请求,就返回新内存区的起始地址,否则,返回一个NULL指针:

    函数首先将参数size设定为4096(页框大小)的整数倍。然后,vmalloc()调用get_vm_area()来创建一个新的描述符,并返回分配给这个内存区的线性地址。flags初始化为VM_ALLOC标志,意味着通过使用vmalloc()函数,非连续页框被映射到一个线性地址空间。然后vmalloc()函数调用kmalloc()来请求一组连续页框,这组连续页框足够包含一个页描述符指针数组。然后memset,接着重复调用alloc_page()函数,每一次为区间中nr_pages个页的每一个分配一个页框,并把对应页描述符的地址存放在area->pages数组中。到这里,已经得到一个新的连续线性地址空间,并且已经分配了一组非连续页框来映射这些线性地址,最后重要的是修改内核使用的页表项,以此表明分配给非连续内存区的每个页框现在对应着一个线性地址,这个线性地址被包含在vmalloc()产生的非连续线性地址区间中。这就是map_vm_area()所要做的。

  3)释放非连续内存区

    vfree()函数释放vmalloc()创建的非连续内存区。与vmalloc()一样,内核修改主内核页全局目录和它的子页表中的相应项,但是映射第4个GB的进程页表的项保持不变。


参考资料:《linux内核设计与实现》 《深入理解linux内核》

你可能感兴趣的:(linux内核设计与实现,C,linux内核,内存分配)