内存管理之页分配器机制

Linux内存管理是操作系统的核心功能,目的是为了实现对物理内存的有效利用,包括:内存分配、内存回收。基于物理内存在内核空间中的映射原理,物理内存的管理方式也不完全相同。内核中的物理内存管理主要分为如下几种:

  • 连续性大块内存管理
  • per-CPU页框高速缓存管理
  • slab缓存管理

下面主要讲述大块连续物理内存的管理,其使用伙伴算法逻辑、页表实现管理,以页框为基本单位,使用页管理器(Page Allocator)实现。

【文章福利】小编推荐自己的Linux内核源码交流群:【 869634926】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前50名可进群领取!!!并额外赠送一份价值600的内核资料包(含视频教程、电子书、实战项目及代码)!!!

学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

一、连续性大块内存管理策略详解

1、关于内存寻址

  • 逻辑地址(Logical Address)

以应用程序的视角记录内存地址关系的一种方式,一般由段(Segment)+段内偏移(Offset)构成。

  • 线性地址(Linear/Flat Address)

也称为虚拟地址,是一种将内存地址空间视作一个单一连续性地址空间的方式。以x86_64为例,使用其中的48位用来寻址,一共可表征256TB的空间。

  • 物理地址(Physical Address)

是内存设备的电气特性表示,用于内存设备物理内存单元寻址。在80x86 CPU中,采用分段和分页进行寻址管理,从而实现把逻辑地址经过分段单元处理,转换为线性地址,再由分页单元处理,转换为物理地址。需要说明的是,上述过程均有硬件完成,而无需操作系统负责转换。例如,在Linux Kernel中使用如下方法进行物理地址到虚拟地址的转换:

_va = x - PHYS_OFFSET +PAGE_OFFSET

我们将用于将线性地址转换为物理地址的部分,称为分页单元(Paging Unit),下面来详细讲述关于分页单元处理过程:

线性地址被划分为以固定长度为单位的组,称为页(Page),它是一个逻辑上的概念。同时,分页单元会将物理内存分割成和页一样大小的组,称为页框(Page Frame)。二者大小一致,目的是为了更好地对应映射,便于将页装入不同的页框中。

在硬件设计中,为了解决虚拟地址空间到物理地址空间的映射,并尽可能提高在映射查找的同时加速内存页面的查找速度,遂建立了多级页表机制,实现快速索引访问。以x86_64位为例,使用四级页表项,每一级占9位,一共寻址64T个页。

2、连续性内存管理的原理

连续性能存管理,可以理解为对页框的管理,由于现代CPU都支持了多核特性,从而出现了非一致性内存访问(NUMA),故而出现了基于分区分页的管理。我们这里讨论在某个管理区内的页框的分配与释放管理。

(1)连续性内存分配

物理页面的分配会分配出一段连续的物理内存空间,使用来自伙伴系统的内存。分配方法如下:基于GFP掩码+分配阶数order。GFP掩码确定了要分配内存空间所属的zone(zone 在不同的架构下有不同的类型,比如在ARM下就仅有Normal和High两种),order确定了需要的内存空间大小,即order的2次幂数量个页框的内存空间。之所以具有不同大小类型的空间,也是为了提高内存空间的分配效率和回收效率,便于在接近于需求空间大小上进行分配和回收。在找到足够空闲大小的zone后,按照order大小,分配不同2^order的空间。分配剩余部分,则加入到同一zone的order-1级的空闲列表中去。

(2)连续性内存释放

基于当前所属的阶(order),计算其相邻的兄弟页框,并判断是否可回收,如果可回收,则将待释放的页合并到一起并继续循环,进入下一阶的循环,如果不可回收。则退出,并加入空闲队列。

 

二、Linux Kernel页管理实现

在内核中,页面管理初始化经过如下调用流程:

start_kernel->setup_arch->paging_init->prepare_page_table

物理内存管理:物理内存寻址、清零、验证。

下面简要的将kernel中实现上述页分配管理的逻辑予以说明,整体逻辑并不复杂,但本文中略去了不必要的如:参数检验、不相关代码逻辑、部分逻辑分支等,以更清楚地呈现整个逻辑。

在arch/ia64/kernel/setup.c中,进行页初始化准备,关键在于从内存驱动中读取分区、页框数等信息,并初始化数据结构:

// arch/ia64/kernel/setup.c 
setup_arch() { 
... efi_init(); 
io_port_init(); 
... 
cpu_init(); 
... 
paging_init(); 

include/linux/mmzone.h头文件中,详细定义了内存分区情况,以及在kernel中针对于内存页管理的数据结构描述:

// include/linux/mmzone.h
struct zone {
    unsigned long _watermark[NR_WMARK];
#ifdef CONFIG_NUMA
    int node;
#endif
    unsigned long       zone_start_pfn;
    const char      *name;
    ...
    struct free_area    free_area[MAX_ORDER];
    spinlock_t      lock;
    ...
}

struct free_area {
    struct list_head    free_list[MIGRATE_TYPES];
    unsigned long       nr_free;
}

(1)内核页分配实现

逻辑如下:

// include/linux/mmzone.h
struct zone {
    unsigned long _watermark[NR_WMARK];
#ifdef CONFIG_NUMA
    int node;
#endif
    unsigned long       zone_start_pfn;
    const char      *name;
    ...
    struct free_area    free_area[MAX_ORDER];
    spinlock_t      lock;
    ...
}

struct free_area {
    struct list_head    free_list[MIGRATE_TYPES];
    unsigned long       nr_free;
}

(1)内核页分配实现
逻辑如下:
// mm/page_alloc.c
/*
 * This is the 'heart' of the zoned buddy allocator.
 */
__alloc_pages() {
    ...
    gfp = current_gfp_context();
    ...
    page = get_page_from_freelist(alloc_gfp, order, alloc_flags, &ac);
    if (likely(page))
        goto out;
    ...
    page = __alloc_pages_slowpath(alloc_gfp, order, &ac);

out:
    if (memcg_kmem_enabled() && (gfp & __GFP_ACCOUNT) && page &&
        unlikely(__memcg_kmem_charge_page(page, gfp, order) != 0)) {
        __free_pages(page, order);
        page = NULL;
    }

    trace_mm_page_alloc(page, order, alloc_gfp, ac.migratetype);

    return page;

}

get_page_from_freelist()
    -> rmqueue() // 从Node缓冲的页队列中取出可用的页,页选择在Node的空闲页和脏页的阈值范围内
        -> __rmqueue() 
            -> __rmqueue_smallest() // 从0开始,循环每个order,并从满足的中间取出page
    -> prep_new_page()

取出最终可用page:

struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
                        int migratetype)
{
    unsigned int current_order;
    struct free_area *area;
    struct page *page;

    /* Find a page of the appropriate size in the preferred list */
for (current_order = order; current_order < MAX_ORDER; ++current_order) {
    // 循环遍历查找是否满足要求,如果不满足,则继续循环,如果满足,则从链表中取出最小的一个
        area = &(zone->free_area[current_order]);
        page = get_page_from_free_area(area, migratetype);
        if (!page)
            continue;
        del_page_from_free_list(page, zone, current_order);
    // expand即为将current_order内剩余的页框加入下一个order中
        expand(zone, page, order, current_order, migratetype);
        set_pcppage_migratetype(page, migratetype);
        return page;
    }
return NULL;
}

(2)内核页释放实现

释放一个页面的流程:

// mm/page_alloc.c
__free_one_page() {
    // 合并PageBuddy,并加入到空闲链表
continue_merging:
    while (order < max_order) {
        buddy_pfn = __find_buddy_pfn(pfn, order);
        buddy = page + (buddy_pfn - pfn);
        if (!page_is_buddy(page, buddy, order))
            goto done_merging;
        del_page_from_free_list(buddy, zone, order);
        combined_pfn = buddy_pfn & pfn;
        page = page + (combined_pfn - pfn);
        pfn = combined_pfn;
        order++;
    }

done_merging:
    set_buddy_order(page, order);
    to_tail = buddy_merge_likely(pfn, buddy_pfn, page, order);
    add_to_free_list(page, zone, order, migratetype);
}

__find_buddy_pfn(pfn, order) {
    return pfn ^ (1 << order);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值