内存学习(七):伙伴分配器(正式版)4

伙伴分配器

6 释放页

页分配器提供了以下释放页的接口。

  • (1)void __free_pages(struct page *page, unsigned int order),第一个参数是第一个物理页的page实例的地址,第二个参数是阶数。

  • (2)void free_pages(unsigned long addr, unsigned int order),第一个参数是第一个物理页的起始内核虚拟地址,第二个参数是阶数。函数__free_pages的代码如下:

1-__free_pages

函数__free_pages的代码如下:

    mm/page_alloc.c
    void __free_pages(struct page *page, unsigned int order)
    {
        if (put_page_testzero(page)) {
              if (order == 0)
                  free_hot_cold_page(page, false);
              else
                  __free_pages_ok(page, order);
        }
    }

首先把页的引用计数减1,只有页的引用计数变成零,才真正释放页:如果阶数是0,不还给伙伴分配器,而是当作缓存热页添加到每处理器页集合中;如果阶数大于0,调用函数__free_pages_ok以释放页。

2-free_hot_cold_page

函数free_hot_cold_page把一页添加到每处理器页集合中,如果页集合中的页数量大于或等于高水线,那么批量返还给伙伴分配器。第二个参数cold表示缓存冷热程度,主动释放的页作为缓存热页,回收的页作为缓存冷页,因为回收的是最近最少使用的页。

    mm/page_alloc.c
    void free_hot_cold_page(struct page *page, bool cold)
    {
          struct zone *zone = page_zone(page);
          struct per_cpu_pages *pcp;
          unsigned long flags;
          unsigned long pfn = page_to_pfn(page);
          int migratetype;
         if (! free_pcp_prepare(page))
                return;
         migratetype = get_pfnblock_migratetype(page, pfn); /* 得到页所属页块的迁移类型 */
          set_pcppage_migratetype(page, migratetype); /* page->index保存真实的迁移类型 */
          local_irq_save(flags);
          __count_vm_event(PGFREE);
         /*
            * 每处理器集合只存放不可移动、可回收和可移动这3种类型的页,
            * 如果页的类型不是这3种类型,处理方法是:
            * (1)如果是隔离类型的页,不需要添加到每处理器页集合,直接释放;
            * (2)其他类型的页添加到可移动类型链表中,page->index保存真实的迁移类型。
            */
          if (migratetype >= MIGRATE_PCPTYPES) {
                if (unlikely(is_migrate_isolate(migratetype))) {
                      free_one_page(zone, page, pfn, 0, migratetype);
                      goto out;
                }
                migratetype = MIGRATE_MOVABLE;
          }
         /* 添加到对应迁移类型的链表中,如果是缓存热页,添加到首部,否则添加到尾部 */
          pcp = &this_cpu_ptr(zone->pageset)->pcp;
          if (! cold)
                list_add(&page->lru, &pcp->lists[migratetype]);
          else
                list_add_tail(&page->lru, &pcp->lists[migratetype]);
          pcp->count++;
          /* 如果页集合中的页数量大于或等于高水线,那么批量返还给伙伴分配器 */
          if (pcp->count >= pcp->high) {
                unsigned long batch = READ_ONCE(pcp->batch);
                free_pcppages_bulk(zone, batch, pcp);
                pcp->count -= batch;
          }
   out:
          local_irq_restore(flags);
    }

3-__free_pages_ok

函数__free_pages_ok负责释放阶数大于0的页块,最终调用到释放页的核心函数__free_one_page,算法是:如果伙伴是空闲的,并且伙伴在同一个内存区域,那么和伙伴合并,注意隔离类型的页块和其他类型的页块不能合并。

算法还做了优化处理:假设最后合并成的页块阶数是order,如果order小于(MAX_ORDER−2),则检查(order+1)阶的伙伴是否空闲,如果空闲,那么order阶的伙伴可能正在释放,很快就可以合并成(order+2)阶的页块。为了防止当前页块很快被分配出去,把当前页块添加到空闲链表的尾部。

函数__free_pages_ok的代码如下:

    mm/page_alloc.c
    __free_pages_ok() -> free_one_page() -> __free_one_page()
    static inline void __free_one_page(struct page *page,
                unsigned long pfn,
                struct zone *zone, unsigned int order,
                int migratetype)
    {
          unsigned long combined_pfn;
          unsigned long uninitialized_var(buddy_pfn);
          struct page *buddy;
          unsigned int max_order;
         /* pageblock_order是按可移动性分组的阶数 */
          max_order = min_t(unsigned int, MAX_ORDER, pageblock_order + 1);
              …
        continue_merging:
              /*如果伙伴是空闲的,和伙伴合并,重复这个操作直到阶数等于(max_order-1)。*/
              while (order < max_order - 1) {
                  buddy_pfn = __find_buddy_pfn(pfn, order); /* 得到伙伴的起始物理页号 */
                  buddy = page + (buddy_pfn - pfn);        /* 得到伙伴的第一页的page实例 */
                 if (! pfn_valid_within(buddy_pfn))
                        goto done_merging;
                  /* 检查伙伴是空闲的并且在相同的内存区域 */
                  if (! page_is_buddy(page, buddy, order))
                        goto done_merging;
                  /*
                    * 开启了调试页分配的配置宏CONFIG_DEBUG_PAGEALLOC,伙伴充当警戒页。
                    */
                  if (page_is_guard(buddy)) {
                        clear_page_guard(zone, buddy, order, migratetype);
                  } else {/* 伙伴是空闲的,把伙伴从空闲链表中删除 */
                        list_del(&buddy->lru);
                        zone->free_area[order].nr_free--;
                        rmv_page_order(buddy);
                  }
                  combined_pfn = buddy_pfn & pfn;
                  page = page + (combined_pfn - pfn);
                  pfn = combined_pfn;
                  order++;
              }
              if (max_order < MAX_ORDER) {
                  /*
                    * 运行到这里,意味着阶数大于或等于分组阶数pageblock_order,
                    * 阻止把隔离类型的页块和其他类型的页块合并
                    */
                  if (unlikely(has_isolate_pageblock(zone))) {
                        int buddy_mt;
                        buddy_pfn = __find_buddy_pfn(pfn, order);
                        buddy = page + (buddy_pfn - pfn);
                        buddy_mt = get_pageblock_migratetype(buddy);
                        /*如果一个是隔离类型的页块,另一个是其他类型的页块,不能合并 */
                        if (migratetype ! = buddy_mt
                                  && (is_migrate_isolate(migratetype) ||
                                        is_migrate_isolate(buddy_mt)))
                            goto done_merging;
                  }
                  /* 如果两个都是隔离类型的页块,或者都是其他类型的页块,那么继续合并 */
                  max_order++;
                  goto continue_merging;
              }
       done_merging:
              set_page_order(page, order);
             /*
                * 最后合并成的页块阶数是order,如果order小于(MAX_ORDER-2),
                * 则检查(order+1)阶的伙伴是否空闲,如果空闲,那么order阶的伙伴可能正在释放,
                * 很快就可以合并成(order+2)阶的页块。为了防止当前页块很快被分配出去,
                * 把当前页块添加到空闲链表的尾部
                */
              if ((order < MAX_ORDER-2) && pfn_valid_within(buddy_pfn)) {
                    struct page *higher_page, *higher_buddy;
                    combined_pfn = buddy_pfn & pfn;
                higher_page = page + (combined_pfn - pfn);
                buddy_pfn = __find_buddy_pfn(combined_pfn, order + 1);
                higher_buddy = higher_page + (buddy_pfn - combined_pfn);
                if (pfn_valid_within(buddy_pfn) &&
                      page_is_buddy(higher_page, higher_buddy, order + 1)) {
                      list_add_tail(&page->lru,
                            &zone->free_area[order].free_list[migratetype]);
                      goto out;
                }
          }
         /* 添加到空闲链表的首部 */
          list_add(&page->lru, &zone->free_area[order].free_list[migratetype]);
    out:
          zone->free_area[order].nr_free++;
    }

参考来自前辈的书籍:《Linux内核深度解析》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TrustZone_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值