伙伴算法的实现-分配页框

内核中alloc_pages系列页框分配函数都是基于伙伴算法实现的,这些函数最终都会调用伙伴算法的入口函数buffered_rmqueue()。

Linux内核管理物理内存有三种方式,其一就是经典的伙伴算法。但是伙伴算法分配物理内存的基本单位是页框,因此内核又引入了slab机制,基于此机制实现的物理内存分配器可以快速有效的分配小于页框的物理内存,并且可以有效避免内部碎片。另外,内核常常会申请单个页框大小的物理内存,因此内核又引入了per-CPU机制,该机制专门用于快速分配单个页框。

1.__rmqueue()

其实buffered_rmqueue()函数仍然没有进行真正的页框分配,该函数首先判断分配阶是否为0,如果是则启用per-CPU机制来分配物理内存,否则调用__rmqueue()。

static struct page *__rmqueue(struct zone *zone, unsigned int order,
                                                    int migratetype)
    {
            struct page *page;
            retry_reserve:
            page = __rmqueue_smallest(zone, order, migratetype);
    
            if (unlikely(!page) && migratetype != MIGRATE_RESERVE) {
                    page = __rmqueue_fallback(zone, order, migratetype);
    
                    if (!page) {
                            migratetype = MIGRATE_RESERVE;
                            goto retry_reserve;
                    }
            }
    
            trace_mm_page_alloc_zone_locked(page, order, migratetype);
            return page;
    }

传递到此函数中的zone表示伙伴算法将从该内存管理区中分配页框,order即分配阶,migratetype表示迁移类型。该函数首选__rmqueue_smallest()进行内存分配,如果在指定的迁移类型上分配失败后,再选用其他备用的迁移列表进行内存分配,该过程通过__rmqueue_fallback()完成。总之内核总是在竭尽全力保证满足分配内存的请求。

2.__rmqueue_smallest()

该函数的实现比较简单,从当前指定的分配阶到最高分配阶依次进行遍历。在每次遍历的分配阶链表中,根据参数migratetype选择正确的迁移队列。根据以上的限定条件,当选定一个页框块链表后,只要该链表不为空,就说明可以分配该分配阶对应的页框块。

一旦选定在当前遍历的分配阶链表上分配页框,那么就通过list_entry()将该页框块从链表上移除。然后将页框块首页框的PG_buddy标志删除,删除该标志说明当前页框块已经不属于伙伴链表。并且将该首页框描述符中的priveate置0,该字段中本来保存的是其所处页框块的分配阶。以上这个过程通过rmv_page_order()完成。此外,还要更新页框块链表nr_free的值。

static inline
    struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
                                                    int migratetype)
    {
            unsigned int current_order;
            struct free_area * area;
            struct page *page;
    
            for (current_order = order; current_order < MAX_ORDER; ++current_order) {
                    area = &(zone->free_area[current_order]);
                    if (list_empty(&area->free_list[migratetype]))
                            continue;
    
                    page = list_entry(area->free_list[migratetype].next, struct page, lru);
                    list_del(&page->lru);
                    rmv_page_order(page);
                    area->nr_free--;
                    expand(zone, page, order, current_order, area, migratetype);
                    return page;
            }
    
            return NULL;
    }
    
    static inline void rmv_page_order(struct page *page)
    {
            __ClearPageBuddy(page);
            set_page_private(page, 0);
    }

__rmqueue_smallest()内部还有一个重要的函数expand()。进入该函数的条件是当所申请的分配阶order小于当前选中的分配阶current_order,也就是说指定的分配阶链表中没有空闲的页框块,只能选用较大的页框块。因此,expand()必须按照伙伴算法的分裂原理将比较大的页框块分割成较小的块。

3.expand()

分裂函数的实现也是显而易见的,它完全遵照伙伴算法的分裂原理。这里有两个分配阶,一个是申请页框时指定的low,一个是在上级函数中遍历时所选定的high。该函数从high分配阶开始递减向low遍历,也就是从较大的页框块开始依次分裂。

比如high为4,而low为2。那么第一遍历时,将大小为16(分配阶为4)的页框块一份为2。通过list_add()将后面的8个连续页框块加入下级链表(分配阶为3),下级链表通过将area指针自减即可得到,后8个页框块的指针也通过page+size获得,而page仍然指向最初的页框块首页框。此时还要对分配阶为3的链表更新nr_free,以及通过set_page_order()对后8个页框块设置一些标志。

第二次遍历将前面8个页框块继续一分为二,将后4个页框块加入area所指向的下级链表(分配阶为2)。第三次遍历时,循环条件已经不再满足,因此返回前4个页框块首页框的描述符地址page。

static inline void expand(struct zone *zone, struct page *page,
            int low, int high, struct free_area *area,
            int migratetype)
    {
            unsigned long size = 1 << high;
             while (high > low) {
                    area--;
                    high--;
                    size >>= 1;
                    VM_BUG_ON(bad_range(zone, &page[size]));
                    list_add(&page[size].lru, &area->free_list[migratetype]);
                    area->nr_free++;
                    set_page_order(&page[size], high);
            }
    }
    
    static inline void set_page_order(struct page *page, int order)
    {
            set_page_private(page, order);
            __SetPageBuddy(page);
    }

原文地址:https://www.cnblogs.com/alantu2018/p/8482915.html

时间: 2024-10-19 04:08:01

伙伴算法的实现-分配页框的相关文章

linux内存源码分析 - 伙伴系统(初始化和申请页框)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 之前的文章已经介绍了伙伴系统,这篇我们主要看看源码中是如何初始化伙伴系统.从伙伴系统中分配页框,返回页框于伙伴系统中的. 我们知道,每个管理区都有自己的伙伴系统管理属于这个管理区的页框,这也说明了,在伙伴系统初始化时,管理区必须要已经存在(初始化完成)了.在管理区描述符(struct zone)中,struct free_area就专门用于描述伙伴系统的.在一个管理区中,伙伴系统一共维护着包含1,2,4,8,

per-CPU分配和释放单页框

内核经常请求和释放单个页框.为了提升系统性能,每个内存管理区定义了一个"每CPU"页框高速缓存.所有"每CPU"高速缓存包含一些预先分配的页框,它们被用于满足本地CPU发出的单一内存请求. 为每个内存管理区和每CPU提供了两个高速缓存:一个热高速缓存,它存放页框中所包含的内容很可能就在CPU硬件高速缓存中:还有一个冷高速缓存. 在内存管理区中,分配单页使用per-cpu机制,分配多页使用伙伴算法,结构图如下: 1.相关结构体 zone结构体中pageset成员指向内

探讨排序算法的实现

排序算法是我们工作中使用最普遍的算法,常见的语言库中基本都会有排序算法的实现,比如c标准库的qsort,stl的sort函数等.本文首先介绍直接插入排序,归并排序,堆排序,快速排序和基数排序等比较排序算法,然后介绍计数排序,基数排序等具有线性时间的排序算法.本文主要讨论算法的实现方法,并不会过多介绍基本理论. 评价一个排序算法优劣适用与否,一般需要从三个方面来分析 时间复杂度.用比较操作和移动操作数的最高次项表示,由于在实际应用中最在乎的是运行时间的上限,所以一般取输入最坏情况的下的运行时间作为

精髓——高度概括几种页框到线性地址的映射技术

第一种:就是页框到线性地址的一 一映射关系.这是在分页阶段,已经建立好的(这部分我可以深入讲一下,但是这部分内容不是很难,而且我的肩膀好酸痛,就不写了,以后在补上),就是为物理内存前896M的每个页框建立页表,并写进页表项.当进程请求这部分内存时,可以直接访问到这些页表,而不并现场创建页表. 第二种:高端内存永久内核映射.就是上一篇博文讲到的.这种技术的映射可以阻塞进程,使进程去睡眠. 第三种:临时内核映射.其实道理差不多.任何一个页框与权限合成的页表可以写进预留的几个页表项中,但这种技术是不能

操作系统内存管理之 分页与虚存(页表、页框、内存)

一 页面与页表 1 页面 分页存储管理是将作业的逻辑地址划分为一系列同等大小的部分,称为页.并为各页加以编号,每个作业的页的编号都是从0开始的.与之类似,把可用的物理内存也划分为同样大小的连续的部分,称为块或页框.同样为块也进行标号,从0#开始.在为进程分配内存空间时,以页为单位,每个内存中的块存放一页用户作业.只要内存中有足够多的块,这些块可以相邻也可以不相邻,就可以存放整个作业了. 页面的大小对于内存利用和系统开销来说非常重要,页面太大,在作业的最后一页必然会剩余较大不能利用的空间--内碎片

通用固定长度编码格式的字符串查找算法的实现

通用固定长度编码格式的字符串查找算法的实现 字符串的查找是数据库应用中必不可少的操作,而且每种数据库产品(ORACLE.DB2.SYBASE.MS SQL SERVER.MYSQL等等)也都提供了对应的字符串处理函数,比如DB2的LOCATE函数. 但在实际的工作中,还是会遇到一些特殊情况的处理,这使得直接使用字符串查找函数,得到的结果可能是错误的,比如本文中提到的固定长度编码格式的字符串的查找.值得注意的是,本文提出的算法可以稍加修改即移植到其它关系数据库系统或者前端开发工具中. 在实际数据库

七种排序算法的实现和总结

最近把七种排序算法集中在一起写了一遍. 注释里有比较详细的说明. 1 /*排序算法大集合**/ 2 #include <stdio.h> 3 #include <string.h> 4 #include <stdlib.h> 5 6 //------------------快速排序------------------// 7 /* 8 核心: 9 如果你知道多少人该站你前面,多少人站你后面,你一定知道你该站哪个位置. 10 算法: 11 1.选取分界数,参考这个分界数,

Linux内核剖析 之 回收页框

一.页框回收算法 1.为何要有页框回收算法? Linux在为用户态与内核分配动态内存时,检查得并不严谨. 例如: (1).对单个用户创建的进程的RAM使用的总量并不作严格的检查(进程资源的限制只针对单个进程): (2).对内核使用的许多磁盘高速缓存和内存高速缓存大小也同样不做限制. 2.为何要减少控制? 可以使内核以最好的可行方式使用可用的RAM: (1).当系统负载较低时,RAM的大部分由磁盘高速缓存占用,较少的正在运行的进程可以获益: (2).当系统负载增加时,RAM的大部分则由进程页占用,

深入理解Linux内核-回收页框

Linux 系统在为用户态进程和内核分配动态内存的时候,所作的检查是马马虎虎的对内核使用的许多磁盘高速缓存和内存高速缓存大小也同样不作限制. 页框回收算法(PFRA):1.在所有内存使用完之前,就必须执行页框回收算法2.选择目标页,它获取页框,并且使之空闲3.候选回收页:任何属于磁盘和内存高速缓存的页,以及属于进程用户态地址空间的页4.首先释放‘无害’页:先释放没有被任何进程使用的磁盘与内存高速缓存中的页5.将用户态进程的所有页定为可回收页.6.同时取消引用一个共享页框的所有页表项的映射,就可以