Linux内核-内存-伙伴系统算法

11 篇文章 1 订阅
3 篇文章 0 订阅

简介

内核在分配一组连续的页框时,必须解决一个著名的内存管理问题,也就是所谓的外碎片。本质上说,避免外碎片有两种方法:

  • 利用分页单元把一组非连续的空闲页框映射到连续的线性地址区间。
  • 开发一种适当的技术来记录现存的空闲连续页框块的情况,以尽量避免为满足小块的请求而分割大的空闲块。

Linux采用伙伴系统算法来解决外碎片问题,把所有的空闲框分组为11个块链表,每个块链表分别包含大小为1,2,4……1024个连续的页框,每个块的第一个页框的物理地址是该快大小的整数倍。

该算法的原理非常简单,假设请求的一个2^n大小的块,首先会检查链表中是否有空闲块,如果没有,算法会查找下一个更大的页块,即2^(n+1),如果存在这样的块,就把它分割成两等份,一半用作满足请求,另一半插入到2^n个页框的链表中;如果在2^(n+1)的链表中也没有空闲块,就继续找更大的块2^(n+m),将2^n满足请求,其余的2^n,2^(n+1),……,2^(n+m-1)插入对应的链表中。

以上的逆过程为页框块的释放过程,内核试图把大小为b的一对空闲伙伴块合并为一个大小为2b的单独块,满足以下条件的两个块称为伙伴:

  • 两个块具有相同的大小b
  • 它们的物理地址是连续的
  • 第一个块的第一个页框的物理地址是2*b*2^12的倍数

源码分析(Linux2.6)

  1. 数据结构
    Linux2.6为每个管理区使用不同的伙伴系统,每个伙伴系统使用的主要数据结构如下:

    • 每个管理区都关系到mem_map(存放内存所有的页框的数组)的子集,子集中的第一个元素和元素的个数分别由管理区描述符的zone_mem_map和size字段指定。
    • 包含有11个元素、元素类型为free_area的一个数组,每个元素对应一种块大小,存放在管理区描述符的free_area字段中。
  2. 分配块(mm/page_alloc.c)

    static struct page *__rmqueue(struct zone *zone, unsigned int order)
        {
            struct free_area * area;
            unsigned int current_order;
            struct page *page;
    
            /**
             * 从所请求的order开始,扫描每个可用块链表进行循环搜索。
             */
            for (current_order = order; current_order < MAX_ORDER; ++current_order) {
                area = zone->free_area + current_order;
                /**
                 * 对应的空闲块链表为空,在更大的空闲块链表中进行循环搜索。
                 */
                if (list_empty(&area->free_list))
                    continue;
    
                /**
                 * 运行到此,说明有合适的空闲块。
                 */
                page = list_entry(area->free_list.next, struct page, lru);
                /**
                 * 首先在空闲块链表中删除第一个页框描述符。
                 */
                list_del(&page->lru);
                rmv_page_order(page);
                area->nr_free--;
                /**
                 * 并减少空闲管理区的空闲页数量。
                 */
                zone->free_pages -= 1UL << order;
                /**
                 * 如果2^order空闲块链表中没有合适的空闲块,那么就是从更大的空闲链表中分配的。
                 * 将剩余的空闲块分散到合适的链表中去。
                 */
                return expand(zone, page, order, current_order, area);
            }
    
            /**
             * 直到循环结束都没有找到合适的空闲块,就返回NULL。
             */
            return NULL;
        }

    expand函数如下:

    static inline struct page * expand(struct zone *zone, struct page *page, int low, int high, struct free_area *area)
        {
            unsigned long size = 1 << high;
    
            while (high > low) {
                area--;
                high--;
                size >>= 1;
                BUG_ON(bad_range(zone, &page[size]));
                /**
                * 插入伙伴作为链表中的第一个元素
                */
                list_add(&page[size].lru, &area->free_list);
                area->nr_free++;
                set_page_order(&page[size], high);
            }
            return page;
        }

    set_page_order函数如下,其实就是设置空闲块的第一个页框的标志,表明从page开始的连续2^order个页框为空闲块:

        static inline void set_page_order(struct page *page, int order) {
            /**
            * 块大小为2^order
            */
            page->private = order;
            __SetPagePrivate(page);
        }
  3. 释放块(mm/page_alloc.c)

    static inline void __free_pages_bulk (struct page *page, struct page *base, struct zone *zone, unsigned int order)
        {
            /**
             * page_idx包含块中第一个页框的下标。
             * 这是相对于管理区中的第一个页框而言的。
             */
            unsigned long page_idx;
            struct page *coalesced;
            /**
             * order_size用于增加管理区中空闲页框的计数器。
             */
            int order_size = 1 << order;
    
            if (unlikely(order))
                destroy_compound_page(page, order);
            /**
            * base为管理区第一个页框,即zone_mem_map
            */
            page_idx = page - base;
    
            /**
             * 大小为2^k的块,它的线性地址都是2^k * 2 ^ 12的整数倍。
             * 相应的,它在管理区的偏移应该是2^k倍。
             */
            BUG_ON(page_idx & (order_size - 1));
            BUG_ON(bad_range(zone, page));
    
            /**
             * 增加管理区的空闲页数
             */
            zone->free_pages += order_size;
            /**
             * 最多循环10 - order次。每次都将一个块和它的伙伴进行合并。
             * 每次从最小的块开始,向上合并。
             */
            while (order < MAX_ORDER-1) {
                struct free_area *area;
                struct page *buddy;
                int buddy_idx;
    
                /**
                 * buddy_idx为要合并的块的伙伴相对管理区第一个页框的下标
                 *下面的异或操作转换page_idx的第order位的值,因此如果这个位原先是0,buddy_idx就等于page_idx + order_size;
                 *相反,如果是1,buddy_idx就等于page_idx - order_size。之所以可以这样运算,是因为最上面伙伴块
                 *的第三个条件约束:第一个块的第一个页框的物理地址是2*b*2^12的倍数
                 */
                buddy_idx = (page_idx ^ (1 << order));
                /**
                 * 通过伙伴的下标找到页描述符的地址。
                 */
                buddy = base + buddy_idx;
                if (bad_range(zone, buddy))
                    break;
                /**
                 * 判断伙伴块是否是大小为order的空闲页框的第一个页。
                 * 首先,伙伴的第一个页必须是空闲的(_count == -1)
                 * 同时,必须属于动态内存(PG_reserved被清0,PG_reserved为1表示留给内核或者没有使用)
                 * 最后,其private字段必须是order
                 */
                if (!page_is_buddy(buddy, order))
                    break;
                /**
                 * 运行到这里,说明伙伴块可以与当前块合并。
                 */
                /* Move the buddy up one level. */
                /**
                 * 伙伴将被合并,将它从现有链表中取下。
                 */
                list_del(&buddy->lru);
                area = zone->free_area + order;
                area->nr_free--;
                rmv_page_order(buddy);
                page_idx &= buddy_idx;
                /**
                 * 将合并了的块再与它的伙伴进行合并。
                 */
                order++;
            }
    
            /**
             * 伙伴不能与当前块合并。
             * 将块插入适当的链表,并以块大小的order更新第一个页框的private字段。
             */
            coalesced = base + page_idx;
            set_page_order(coalesced, order);
            list_add(&coalesced->lru, &zone->free_area[order].free_list);
            zone->free_area[order].nr_free++;
        }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值