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

本文涉及的产品
语种识别,语种识别 100万字符
文档翻译,文档翻译 1千页
文本翻译,文本翻译 100万字符
简介: 内存学习(七):伙伴分配器(正式版)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内核深度解析》

目录
相关文章
|
3月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
267 13
|
4月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
175 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
4月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
84 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
6月前
|
存储 JavaScript 前端开发
学习JavaScript 内存机制
【8月更文挑战第23天】学习JavaScript 内存机制
52 3
|
6月前
|
关系型数据库 MySQL
MySQl优化:使用 jemalloc 分配内存
MySQl优化:使用 jemalloc 分配内存
|
6月前
|
缓存 Java 编译器
Go 中的内存布局和分配原理
Go 中的内存布局和分配原理
|
7月前
|
存储 缓存 算法
(五)JVM成神路之对象内存布局、分配过程、从生至死历程、强弱软虚引用全面剖析
在上篇文章中曾详细谈到了JVM的内存区域,其中也曾提及了:Java程序运行过程中,绝大部分创建的对象都会被分配在堆空间内。而本篇文章则会站在对象实例的角度,阐述一个Java对象从生到死的历程、Java对象在内存中的布局以及对象引用类型。
187 8
|
7月前
|
NoSQL Redis C++
c++开发redis module问题之在复杂的Redis模块中,特别是使用第三方库或C++开发时,接管内存统计有哪些困难
c++开发redis module问题之在复杂的Redis模块中,特别是使用第三方库或C++开发时,接管内存统计有哪些困难
|
7月前
|
Java 运维
开发与运维内存问题之在堆内存中新创建的对象通常首先分配如何解决
开发与运维内存问题之在堆内存中新创建的对象通常首先分配如何解决
33 1
|
6月前
|
存储 NoSQL Java
Tair的发展问题之Tair对于不同存储介质(如内存和磁盘)的线程分配是如何处理的
Tair的发展问题之Tair对于不同存储介质(如内存和磁盘)的线程分配是如何处理的

热门文章

最新文章