找回密码
 立即注册
Qt开源社区 门户 查看内容

Linux Kernel - 内存分配与回收

2019-9-23 05:41| 发布者: admin| 查看: 1129| 评论: 0

摘要: 接着上周的请页机制,今天写一下内存分配与回收。在Linux中,CPU访问的不是物理内存而是虚拟内存。因此对于内存页面的管理,通常是先在虚拟内存空间中分配一个区间,然后才根据需要,为此区间分配相应的物理页面,并 ...
接着上周的请页机制,今天写一下内存分配与回收。

在Linux中,CPU访问的不是物理内存而是虚拟内存。因此对于内存页面的管理,通常是先在虚拟内存空间中分配一个区间,然后才根据需要,为此区间分配相应的物理页面,并建立映射。
进程的执行和加载

从操作系统的角度看,一个进程最关键的特征是拥有独立的虚拟地址空间。
当我们说一个进程在执行的时候,是在说如下步骤

  1. 建立可执行文件与虚拟地址空间的映射:

    当执行一个程序时,加载器读取可执行文件(ELF)的头,建立虚拟空间和可执行文件的映射,调用的是do_mmap()函数,同时虚拟地址空间所需的数据结构mm_structvm_area_struct也填充相应的值,如图左边虚拟内存部分

  2. 将指令寄存器设置为可执行文件入口,并启动运行



当我们说一个程序在装载时在说什么?在上述步骤后,执行文件的指令和数据加载进内存,但并没有真正的装入物理内存,只是通过ELF文件头部信息建立起可执行文件与虚拟地址空间的映射关系而已,真正加载过程将在发生缺页异常处理时才进行,装载执行过程如下:


  1. 内核根据上面建立的映射关系,找到所需的内容在可执行文件中的位置

  2. 分配一个物理内存页面,并将可执行文件内容装载到该内存页中

  3. 建立该物理页面和虚拟地址空间的映射关系,也就是说填充页表,然后把控制权交换给进程
物理内存的管理和分配

上周说的请页机制可以为进程请求物理内存,那么物理内存在内核中究竟是如何管理和分配的。
首先,我们看一下内核空间的划分。在X86-32体系架构上,内核空间的地址范围是PAGE_0FFSET(3GB) 到4GB。
内核空间的第一部分试图将系统的所有物理内存线性地映射到虚拟地址空间中,但最多只能映射high_memory(默认为896M)大小的物理内存。大于high_memory的物理内存将映射到内核空间的后部分。如图所示。



按照这样的映射规则,0M到high_memory的物理内存称为低端内存,大于high_memory的物理内存称为高端内存。
从图中可以看出,内核采用了三种机制将高端内存映射到内核空间:永久内核映射、固定映射、vmalloc机制。

那么, 内核虚拟地址和物理地址如何进行转换?
内核为线性映射的内存区提供物理地址和虚拟地址的转换函数:

  • __pa(vaddr):返回虚拟地址vaddr对应的物理地址

  • __va(paddr):返回物理地址paddr对应的虚拟地址
物理内存管理机制

基于物理内存在内核空间中的映射原理,物理内存的管理方式也有
所不同。内核中物理内存的管理机制主要有以下四种:

  • 伙伴算法:负责大块连续物理内存的分配和释放,以页框为基本单位。

    该机制可以避免外部碎片

  • per-CPU页框高速缓存:内核经常请求和释放单个页框,该缓存包含预先分配的页框,用于满足本地CPU发出的单一页框请求

  • slab缓存:负责小块物理内存的分配,并且它也作为高速缓存,主要针对内核中经常分配并释放的对象

  • vmalloc机制:vmalloc机制使得内核通过连续的线性地址来访问非连续的物理页框,这样可以最大限度的使用高端物理内存
伙伴算法

Linux伙伴算法负责大内存的分配,它把所有的空闲页面分为10个块链表,每个链表中的一个块含有2的幂次个页面,这种块称为“页块”。
伙伴系统采用的数据结构是一个叫free_area的数组
struct free_area_struct {
struct page *next;
struct page *prev;
unsigned int *map;
}

数组free_area包含三个域:next, prev, map。指针next和prev用于将物理结构页面struct page链接成一个双向链表,其中的数字表示内存块的起始页面号。而map域指向一个位图。
申请过程:比如,我要分配4(2^2)页(16k)的内存空间,算法会先从free_area[2]中查看nr_free是否为空,如果有空闲块,则从中分配,如果没有空闲块,就从它的上一级free_area[3](每块32K)中分配出16K,并将多余的内存(16K)加入到free_area[2]中去。如果free_area[3]也没有空闲,则从更上一级申请空间,依次递推,直到free_area[max_order],如果顶级都没有空间,那么就报告分配失败。
释放就是申请的逆过程。

满足以下条件的块称为伙伴

  • 两个块的大小相同

  • 两个块的物理空间连续
per-CPU页框高速缓存

内核经常请求和释放单个页框。为了提升系统性能,每个内存管理区定义了一个“每CPU”页框高速缓存。所有“每CPU”高速缓存包含一些预先分配的页框,它们被用于满足本地CPU发出的单一内存请求。
在内存管理区中,分配单页使用per-cpu机制,分配多页使用伙伴算法。
slab缓存

使用伙伴算法,每次分配至少一个页面,而当请求分配的内存为几十个字节的时候,怎么处理呢?Linux引入了slab机制。
伙伴算法的迁移机制很好的解决了外部碎片。
当我们申请几十个字节的时候,内核也是给我们分配一个页,这样在每个页中就形成了很大的浪费。称之为内部碎片。
内核中引入了slab机制去尽力的减少这种内部碎片。

slab分配器是基于对象进行管理的,所谓的对象就是内核中的数据结构(例如:task_struct, file_struct 等)。相同类型的对象归为一类,每当要申请这样一个对象时,slab分配器就从一个slab列表中分配一个这样大小的单元出去,而当要释放时,将其重新保存在该列表中,而不是直接返回给伙伴系统,从而避免内部碎片。slab分配器并不丢弃已经分配的对象,而是释放并把它们保存在内存中。slab分配对象时,会使用最近释放的对象的内存块,因此其驻留在cpu高速缓存中的概率会大大提高。

每种对象的高速缓存是由若干个slab组成,每个slab是由若干个页框组成的。虽然slab分配器可以分配比单个页框更小的内存块,但它所需的所有内存都是通过伙伴算法分配的。
vmalloc机制

非连续内存处于3G到4G之间的内核空间的高端内存区,也就是内核空间。
如图所示



我们知道物理上连续的映射对内核是最好的,但不是总能成功。在分配一大块内存时,可能无法找到连续的内存块。内核使用vmalloc()接口函数,来分配在虚拟内存中连续但在物理内存中不一定连续的内存。

vmalloc()函数的原型是
void * vmalloc(unsigned long size)
函数首先把size参数取整为页面大小(4KB)的一个倍数,也就是按照页的大小对齐,然后进行有效性检查,如果有合适大小的内存,就调用get_vm_area()获得一个内存区的结构,最后调用vmalloc_area_pages()真正的进行非连续内存的分配,该函数实际上是建立了非连续内存区到物理页面的映射。

简单说一下vmalloc()kmalloc()的区别
首先vmalloc()与 kmalloc()都可用于内核空间分配内存。
kmalloc()分配的内存处于3GB~high_memory之间,这段内核空间与物理内存的映射一一对应,而vmalloc()分配的内存在VMALLOC_START~VMALLOC_END之间,这段非连续内存区映射到物理内存也可能是非连续的。
如图所示



总的来说就是vmalloc()分配的物理地址无需连续,而kmalloc()确保页在物理上是连续。

用户进程发出内存分配请求,到内核最终分配物理内存,这中间内核要做大量的工作,这里介绍了vmalloc()和kmalloc(),但它们最终都要调用伙伴算法,通过get_free_page()内核函数获得物理内存。如图所示:



当用户程序通过调用系统调用申请内存时,首先陷入内核,建立虚拟地址空间的映射,获得一块虚拟内存区VMA。当进程对这块虚存区进行访问时,如果物理内存尚未分配,那么此时发生一个缺页异常, 通过get_free_pages申请一个或多个物理页面,并将此物理内存和虚拟内存的映射关系写入页表。

内存分配与回收先介绍到这里,这里面的水很深,我目前理解的很肤浅,后续有机会多写几篇来介绍内存管理相关的知识。


----------------------------------------------------------------------------------------------------------------------
我们尊重原创,也注重分享,文章来源于微信公众号:暴走的耗子19,建议关注公众号查看原文。如若侵权请联系qter@qter.org。
----------------------------------------------------------------------------------------------------------------------

鲜花

握手

雷人

路过

鸡蛋

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了