本文为原创,转载请注明:http://www.cnblogs.com/tolimit/

  翻了一下之前的文章,发现竟然忘记写内核是如何释放页框的,罪过。

  释放页框很简单,其实只有几步

  1. 检查此页是否被其他进程使用(检查页描述符的_count是否为0)。
  2. 如果是释放单个页框,则优先把它放回到该CPU的单页框高速缓存链表中,如果该CPU的单页框高速缓存的页框过多,则把该CPU的页框高速缓存中的pcp->batch个页框放回伙伴系统链表中。
  3. 在放回伙伴系统的过程中,会与旁边的空闲页框合并,放入更高等级的order链表中,比如释放的是两个连续页框,会检查前后是否能合成为4个连续页框,再检查是否能合成为8个,直到不能合成位置,并将这些连续页框放入对应的链表中。

  释放页框的操作最后都会调用到__free_pages()函数,我们主要从这个函数跟踪下去,看看内核是怎么执行的。

  1. /* 释放页框 */
  2. void __free_pages(struct page *page, unsigned int order)
  3. {
  4. /* 检查页框是否还有进程在使用,就是检查_count变量的值是否为0 */
  5. if (put_page_testzero(page)) {
  6. /* 如果是1个页框,则放回每CPU高速缓存中,如果是多个页框,则放回伙伴系统,放回CPU高速缓存中优先把其设置为热页,而不是冷页 */
  7. if (order == )
  8. free_hot_cold_page(page, false);
  9. else
  10. __free_pages_ok(page, order);
  11. }
  12. }

  热页冷页的意思就是:当一个页被释放时,默认设置为热页,因为该页可能有些地址的数据还处于映射到CPUcache的情况,当该CPU上有进程申请单个页框时,优先把这些热页分配出去,这样能提高cache命中率,提高效率。而实现方法也很简单,如果是热页,则把它加入到CPU页框高速缓存链表的链表头,如果是冷页,则加入到链表尾,如下:

  1. void free_hot_cold_page(struct page *page, bool cold)
  2. {
  3. /* 页框所处管理区 */
  4. struct zone *zone = page_zone(page);
  5. struct per_cpu_pages *pcp;
  6. unsigned long flags;
  7. /* 页框号 */
  8. unsigned long pfn = page_to_pfn(page);
  9. int migratetype;
  10.  
  11. /* 检查 */
  12. if (!free_pages_prepare(page, ))
  13. return;
  14.  
  15. /* 获取页框所在pageblock的页框类型 */
  16. migratetype = get_pfnblock_migratetype(page, pfn);
  17. /* 设置页框类型为pageblock的页框类型,因为在页框使用过程中,这段pageblock可以移动到了其他类型(比如MIGRATE_MOVABLE -> MIGRATE_UNMOVABLE) */
  18. set_freepage_migratetype(page, migratetype);
  19. local_irq_save(flags);
  20. __count_vm_event(PGFREE);
  21.  
  22. if (migratetype >= MIGRATE_PCPTYPES) {
  23. /* 如果不是高速缓存类型,则放回伙伴系统 */
  24. if (unlikely(is_migrate_isolate(migratetype))) {
  25. free_one_page(zone, page, pfn, , migratetype);
  26. goto out;
  27. }
  28. migratetype = MIGRATE_MOVABLE;
  29. }
  30.  
  31. /* 放入当前CPU高速缓存中,要以migratetype区分开来 */
  32. pcp = &this_cpu_ptr(zone->pageset)->pcp;
  33. if (!cold)
  34. list_add(&page->lru, &pcp->lists[migratetype]);
  35. else
  36. list_add_tail(&page->lru, &pcp->lists[migratetype]);
  37. pcp->count++;
  38.  
  39. /* 当前CPU高速缓存中页框数量高于最大值,将pcp->batch数量的页框放回伙伴系统 */
  40. if (pcp->count >= pcp->high) {
  41. unsigned long batch = ACCESS_ONCE(pcp->batch);
  42. free_pcppages_bulk(zone, batch, pcp);
  43. pcp->count -= batch;
  44. }
  45.  
  46. out:
  47. local_irq_restore(flags);
  48. }

  我们再看看连续页框的释放,连续页框释放主要是__free_pages_ok()函数:

  1. static void __free_pages_ok(struct page *page, unsigned int order)
  2. {
  3. unsigned long flags;
  4. int migratetype;
  5. /* 获取页框号 */
  6. unsigned long pfn = page_to_pfn(page);
  7.  
  8. /* 准备,各种检查 */
  9. if (!free_pages_prepare(page, order))
  10. return;
  11.  
  12. /* 获取页框所在pageblock的页框类型 */
  13. migratetype = get_pfnblock_migratetype(page, pfn);
  14. /* 禁止中断 */
  15. local_irq_save(flags);
  16. /* 统计当前CPU一共释放的页框数 */
  17. __count_vm_events(PGFREE, << order);
  18. /* 设置这块连续页框块的类型与所在pageblock类型一致,保存在page->index中 */
  19. set_freepage_migratetype(page, migratetype);
  20. /* 释放函数 */
  21. free_one_page(page_zone(page), page, pfn, order, migratetype);
  22. local_irq_restore(flags);
  23. }

  需要注意,无论在释放单页框还是连续页框时,在释放时都会获取此页所在的pageblock的类型,pageblock大小为大页的大小或者2^MAX_ORDER-1的大小,表明这段大小的内存都为一种类型(MIGRATE_MOVABALE,MIGRATE_RECLAIMABLE等),当释放时,都会获取页所在的pageblock的类型,然后把此页设置为与pageblock一致的类型,因为有种情况是:比如一个pageblock为MIGRATE_MOVABLE类型,并且有部分页已经被使用(这些正在被使用的页都为MIGRATE_MOVABLE),然后MIGRATE_RECLAIMABLE类型的页不足,需要从MIGRATE_MOVABLE这里获取这个pageblock到MIGRATE_RECLAIMABLE类型中,这个pageblock的类型就被修改成了MIGRATE_RECLAIMABLE,这样就造成了正在使用的页的类型会与pageblock的类型不一致。在多个连续页框释放的时候也会遇到这种情况,所以在__free_pages_ok()函数也会在释放页框的时候校对pageblock的类型并进行更改。页的类型保存在页描述page->index中。

  无论是单个页框的释放,还是连续多个页框的释放,最后都是调用到free_one_page()函数,这个函数的第四个参数指明了order值:

  1. static void free_one_page(struct zone *zone,
  2. struct page *page, unsigned long pfn,
  3. unsigned int order,
  4. int migratetype)
  5. {
  6. unsigned long nr_scanned;
  7. /* 管理区上锁 */
  8. spin_lock(&zone->lock);
  9.  
  10. /* 数据更新 */
  11. nr_scanned = zone_page_state(zone, NR_PAGES_SCANNED);
  12. if (nr_scanned)
  13. __mod_zone_page_state(zone, NR_PAGES_SCANNED, -nr_scanned);
  14.  
  15. /* 内存隔离使用 */
  16. if (unlikely(has_isolate_pageblock(zone) ||
  17. is_migrate_isolate(migratetype))) {
  18. migratetype = get_pfnblock_migratetype(page, pfn);
  19. }
  20. /* 释放page开始的order次方个页框到伙伴系统,这些页框的类型时migratetype */
  21. __free_one_page(page, pfn, zone, order, migratetype);
  22. /* 管理区解锁 */
  23. spin_unlock(&zone->lock);
  24. }

  整个释放过程的核心函数就是__free_one_page(),里面有个算法是分析是否能够对此段页框附近的页框进行合并的,其实原理很简单,往前检查order次方个连续页框是否为空闲页框,再往后检查order次方个连续页框是否为空闲页框,如果其中一者成立,则合并,并order++,继续检查,但是注意,这些页框都必须为同一个管理区,因为伙伴系统是以管理区为单位的。如下:

  1. static inline void __free_one_page(struct page *page,
  2. unsigned long pfn,
  3. struct zone *zone, unsigned int order,
  4. int migratetype)
  5. {
  6. /* 保存块中第一个页框的下标,这个下标相对于管理区而言,而不是node */
  7. unsigned long page_idx;
  8. unsigned long combined_idx;
  9. unsigned long uninitialized_var(buddy_idx);
  10. struct page *buddy;
  11. int max_order = MAX_ORDER;
  12.  
  13. VM_BUG_ON(!zone_is_initialized(zone));
  14.  
  15. if (unlikely(PageCompound(page)))
  16. if (unlikely(destroy_compound_page(page, order)))
  17. return;
  18.  
  19. VM_BUG_ON(migratetype == -);
  20. if (is_migrate_isolate(migratetype)) {
  21. /*
  22. * We restrict max order of merging to prevent merge
  23. * between freepages on isolate pageblock and normal
  24. * pageblock. Without this, pageblock isolation
  25. * could cause incorrect freepage accounting.
  26. */
  27. /* 如果使用了内存隔离,则最大的order应该为MAX_ORDER与pageblock_order+1中最小那个,实际上在没有大页的情况下,这两个值相等,如果有大页的情况下,则不一定 */
  28. max_order = min(MAX_ORDER, pageblock_order + );
  29. } else {
  30. __mod_zone_freepage_state(zone, << order, migratetype);
  31. }
  32.  
  33. /* page的pfn号 */
  34. page_idx = pfn & (( << max_order) - );
  35.  
  36. VM_BUG_ON_PAGE(page_idx & (( << order) - ), page);
  37. VM_BUG_ON_PAGE(bad_range(zone, page), page);
  38.  
  39. /* 主要,最多循环10次,每次都尽量把一个块和它的伙伴进行合并,以最小块开始 */
  40. while (order < max_order - ) {
  41. /* buddy_idx = page_idx ^ (1 << order) */
  42. /* buddy_idx是page_idx的伙伴的页框号 */
  43. /* 伙伴的页框号就是page_idx的第(1 << order)位的相反数,比如(1<<order)是4,page_idx是01110,则buddy_idx是01010,由此可见伙伴并不一定是之后的区间 */
  44. /*
  45. * 对于000000 ~ 001000这个页框号区间,假设order是3,左边是第一种情况,右边是另一种情况
  46. *
  47. * -----------
  48. * | |
  49. * | |
  50. * | |
  51. * page_idx = 000100 ------> |-----------| 计算后buddy_idx = 000100
  52. * | |
  53. * | |
  54. * | |
  55. * 计算后buddy_idx = 000000 ----------- page_idx = 000000
  56. */
  57. buddy_idx = __find_buddy_index(page_idx, order);
  58. /* 伙伴的页描述符,就是buddy_idx对应的页描述符 */
  59. buddy = page + (buddy_idx - page_idx);
  60.  
  61. /* 检查buddy是否描述了大小为order的空闲页框块的第一个页 */
  62. if (!page_is_buddy(page, buddy, order))
  63. break;
  64. /*
  65. * Our buddy is free or it is CONFIG_DEBUG_PAGEALLOC guard page,
  66. * merge with it and move up one order.
  67. */
  68. if (page_is_guard(buddy)) {
  69. /* 设置了PAGE_DEBUG_FLAG_GUARD */
  70. clear_page_guard_flag(buddy);
  71. set_page_private(buddy, );
  72. if (!is_migrate_isolate(migratetype)) {
  73. __mod_zone_freepage_state(zone, << order,
  74. migratetype);
  75. }
  76. } else {
  77. /* 将伙伴从当前空闲链表中移除出来 */
  78. list_del(&buddy->lru);
  79. zone->free_area[order].nr_free--;
  80. rmv_page_order(buddy);
  81. }
  82. /* combined_idx 是 buddy_idx 与 page_idx 中最小的那个idx */
  83. combined_idx = buddy_idx & page_idx;
  84. page = page + (combined_idx - page_idx);
  85. page_idx = combined_idx;
  86. order++;
  87. }
  88. set_page_order(page, order);
  89. /* 循环结束,标记了释放的连续page已经和之后的连续页形成了一个2的order次方的连续页框块 */
  90.  
  91. /* 检查能否再进一步合并 */
  92. if ((order < MAX_ORDER-) && pfn_valid_within(page_to_pfn(buddy))) {
  93. struct page *higher_page, *higher_buddy;
  94. combined_idx = buddy_idx & page_idx;
  95. higher_page = page + (combined_idx - page_idx);
  96. buddy_idx = __find_buddy_index(combined_idx, order + );
  97. higher_buddy = higher_page + (buddy_idx - combined_idx);
  98. if (page_is_buddy(higher_page, higher_buddy, order + )) {
  99. list_add_tail(&page->lru,
  100. &zone->free_area[order].free_list[migratetype]);
  101. goto out;
  102. }
  103. }
  104.  
  105. /* 加入空闲块链表 */
  106. list_add(&page->lru, &zone->free_area[order].free_list[migratetype]);
  107. out:
  108. /* 对应空闲链表中空闲块数量加1 */
  109. zone->free_area[order].nr_free++;
  110. }

  整个页框释放过程就是这样,也比较简单,或许就最后那个合并算法会稍微复杂一些。

linux内存源码分析 - 伙伴系统(释放页框)的更多相关文章

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

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 之前的文章已经介绍了伙伴系统,这篇我们主要看看源码中是如何初始化伙伴系统.从伙伴系统中分配页框,返回页框于伙伴系 ...

  2. linux内存源码分析 - 内存回收(整体流程)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 当linux系统内存压力就大时,就会对系统的每个压力大的zone进程内存回收,内存回收主要是针对匿名页和文 ...

  3. linux内存源码分析 - 内存压缩(实现流程)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 本文章最好结合linux内存管理源码分析 - 页框分配器与linux内存源码分析 -伙伴系统(初始化和申请 ...

  4. (转)linux内存源码分析 - 内存回收(整体流程)

    http://www.cnblogs.com/tolimit/p/5435068.html------------linux内存源码分析 - 内存回收(整体流程) 概述 当linux系统内存压力就大时 ...

  5. linux内存源码分析 - 内存压缩(同步关系)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 最近在看内存回收,内存回收在进行同步的一些情况非常复杂,然后就想,不会内存压缩的页面迁移过程中的同步关系也 ...

  6. linux内存源码分析 - SLUB分配器概述

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ SLUB和SLAB的区别 首先为什么要说slub分配器,内核里小内存分配一共有三种,SLAB/SLUB/SLOB ...

  7. linux内存源码分析 - SLAB分配器概述【转】

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 之前说了管理区页框分配器,这里我们简称为页框分配器,在页框分配器中主要是管理物理内存,将物理内存的页框分配给申请 ...

  8. linux内存源码分析 - SLAB分配器概述

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 之前说了管理区页框分配器,这里我们简称为页框分配器,在页框分配器中主要是管理物理内存,将物理内存的页框分配给申请 ...

  9. linux内存源码分析 - 零散知识点

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 直接内存回收中的等待队列 内存回收详解见linux内存源码分析 - 内存回收(整体流程),在直接内存回收过程中, ...

随机推荐

  1. Link Between SAP SD, MM & FI

    Link Between SAP SD, MM & FI 1. In SAP you will always get integration with other modules. SD wi ...

  2. 8.Odoo产品分析 (二) – 商业板块(3) –CRM(2)

    查看Odoo产品分析系列--目录 接上一篇Odoo产品分析 (二) – 商业板块(3) –CRM(1) 4. 设置 在配置–>设置中:    在分析"销售"模块时已经将其他的 ...

  3. Java用户自定义函数

    用户除了可以使用JavaScript的内置函数之外,还可以自己定义函数.自定义函数有3种方法. 使用关键字 function 构造 语法: function funcName([param1][,pa ...

  4. Android为TV端助力 post带数据请求方式,传递的数据格式包括json和map

    如下: public static String httpPost(String url, String json) { try { URL u = new URL(url); HttpURLConn ...

  5. 使用adb命令通过IP地址连接手机

    前提:已经通过USB设备线连接过电脑,并成功安装驱动. adb连接手机进行调试有两种方式,一种是使用USB线,另一种是使用无线WiFi. 第一种  使用USB线连接 1. 在手机上启用USB调试 2. ...

  6. vue缓存页面

    vue如何和ionic的缓存机制一样,可以缓存页面,在A页面跳转至B页面后返回A页面时A页面的数据还在? 在app.vue中将router-view使用keep-alive包起来,使用v-if来判断使 ...

  7. MySQL 基本语句(1)

    一.cmd命令行的常用命令: 当我们使用MySQL 5.5 Command Line Client这个客户端登陆时,只能登陆root用户.如果今后创建了别的用户,就很麻烦了,所以我们不用MySQL 5 ...

  8. Unity端游无法下载资源问题

    问题:用Unity编辑器Build的游戏(MyGame.exe)无法下载服务器上资源文件: starting www download: http://10.123.102.142/resources ...

  9. Eclipse启动时发生An internal error occurred duri ng: "Initializing Java Tooling ----网上的坑爹的一个方法

    补充一下: 上面的方法不行. 我的个人解决方法 出现这种问题的原因,我的是eclipse换了,工作目录还是用之前的那个 把build Automatically的钩去掉 假设我们是用之前的worksp ...

  10. UGUI Set Anchor And Pivot

    我的环境 Unity 5.3.7p4 在运行时动态的设置UI元素的锚点和中心点. 设置Anchor 修改offsetMax不生效 使用下面这段代码设置Anchor并不会生效,尽管他和你在属性面板看到的 ...