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

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

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

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

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

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

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_pages_prepare(page, 0))return;/* 获取页框所在pageblock的页框类型 */migratetype = get_pfnblock_migratetype(page, pfn);/* 设置页框类型为pageblock的页框类型,因为在页框使用过程中,这段pageblock可以移动到了其他类型(比如MIGRATE_MOVABLE -> MIGRATE_UNMOVABLE) */set_freepage_migratetype(page, migratetype);local_irq_save(flags);__count_vm_event(PGFREE);if (migratetype >= MIGRATE_PCPTYPES) {/* 如果不是高速缓存类型,则放回伙伴系统 */if (unlikely(is_migrate_isolate(migratetype))) {free_one_page(zone, page, pfn, 0, migratetype);goto out;}migratetype = MIGRATE_MOVABLE;}/* 放入当前CPU高速缓存中,要以migratetype区分开来 */pcp = &this_cpu_ptr(zone->pageset)->pcp;if (!cold)list_add(&page->lru, &pcp->lists[migratetype]);elselist_add_tail(&page->lru, &pcp->lists[migratetype]);pcp->count++;/* 当前CPU高速缓存中页框数量高于最大值,将pcp->batch数量的页框放回伙伴系统 */if (pcp->count >= pcp->high) {unsigned long batch = ACCESS_ONCE(pcp->batch);free_pcppages_bulk(zone, batch, pcp);pcp->count -= batch;}out:local_irq_restore(flags);
}

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

static void __free_pages_ok(struct page *page, unsigned int order)
{unsigned long flags;int migratetype;/* 获取页框号 */unsigned long pfn = page_to_pfn(page);/* 准备,各种检查 */if (!free_pages_prepare(page, order))return;/* 获取页框所在pageblock的页框类型 */migratetype = get_pfnblock_migratetype(page, pfn);/* 禁止中断 */local_irq_save(flags);/* 统计当前CPU一共释放的页框数 */__count_vm_events(PGFREE, 1 << order);/* 设置这块连续页框块的类型与所在pageblock类型一致,保存在page->index中 */set_freepage_migratetype(page, migratetype);/* 释放函数 */free_one_page(page_zone(page), page, pfn, order, migratetype);local_irq_restore(flags);
}

  需要注意,无论在释放单页框还是连续页框时,在释放时都会获取此页所在的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值:

static void free_one_page(struct zone *zone,struct page *page, unsigned long pfn,unsigned int order,int migratetype)
{unsigned long nr_scanned;/* 管理区上锁 */spin_lock(&zone->lock);/* 数据更新 */nr_scanned = zone_page_state(zone, NR_PAGES_SCANNED);if (nr_scanned)__mod_zone_page_state(zone, NR_PAGES_SCANNED, -nr_scanned);/* 内存隔离使用 */if (unlikely(has_isolate_pageblock(zone) ||is_migrate_isolate(migratetype))) {migratetype = get_pfnblock_migratetype(page, pfn);}/* 释放page开始的order次方个页框到伙伴系统,这些页框的类型时migratetype */__free_one_page(page, pfn, zone, order, migratetype);/* 管理区解锁 */spin_unlock(&zone->lock);
}

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

static inline void __free_one_page(struct page *page,unsigned long pfn,struct zone *zone, unsigned int order,int migratetype)
{/* 保存块中第一个页框的下标,这个下标相对于管理区而言,而不是node */unsigned long page_idx;unsigned long combined_idx;unsigned long uninitialized_var(buddy_idx);struct page *buddy;int max_order = MAX_ORDER;VM_BUG_ON(!zone_is_initialized(zone));if (unlikely(PageCompound(page)))if (unlikely(destroy_compound_page(page, order)))return;VM_BUG_ON(migratetype == -1);if (is_migrate_isolate(migratetype)) {/** We restrict max order of merging to prevent merge* between freepages on isolate pageblock and normal* pageblock. Without this, pageblock isolation* could cause incorrect freepage accounting.*//* 如果使用了内存隔离,则最大的order应该为MAX_ORDER与pageblock_order+1中最小那个,实际上在没有大页的情况下,这两个值相等,如果有大页的情况下,则不一定 */max_order = min(MAX_ORDER, pageblock_order + 1);} else {__mod_zone_freepage_state(zone, 1 << order, migratetype);}/* page的pfn号 */page_idx = pfn & ((1 << max_order) - 1);VM_BUG_ON_PAGE(page_idx & ((1 << order) - 1), page);VM_BUG_ON_PAGE(bad_range(zone, page), page);/* 主要,最多循环10次,每次都尽量把一个块和它的伙伴进行合并,以最小块开始 */while (order < max_order - 1) {/* buddy_idx = page_idx ^ (1 << order) *//* buddy_idx是page_idx的伙伴的页框号 *//* 伙伴的页框号就是page_idx的第(1 << order)位的相反数,比如(1<<order)是4,page_idx是01110,则buddy_idx是01010,由此可见伙伴并不一定是之后的区间 *//**      对于000000 ~ 001000这个页框号区间,假设order是3,左边是第一种情况,右边是另一种情况**                            -----------*                           |           |*                           |           |*                           |           |* page_idx = 000100 ------> |-----------|    计算后buddy_idx = 000100*                           |           |*                           |           |*                           |           |* 计算后buddy_idx = 000000   -----------     page_idx = 000000*/buddy_idx = __find_buddy_index(page_idx, order);/* 伙伴的页描述符,就是buddy_idx对应的页描述符 */buddy = page + (buddy_idx - page_idx);/* 检查buddy是否描述了大小为order的空闲页框块的第一个页 */if (!page_is_buddy(page, buddy, order))break;/** Our buddy is free or it is CONFIG_DEBUG_PAGEALLOC guard page,* merge with it and move up one order.*/if (page_is_guard(buddy)) {/* 设置了PAGE_DEBUG_FLAG_GUARD */clear_page_guard_flag(buddy);set_page_private(buddy, 0);if (!is_migrate_isolate(migratetype)) {__mod_zone_freepage_state(zone, 1 << order,migratetype);}} else {/* 将伙伴从当前空闲链表中移除出来 */list_del(&buddy->lru);zone->free_area[order].nr_free--;rmv_page_order(buddy);}/* combined_idx 是 buddy_idx 与 page_idx 中最小的那个idx */combined_idx = buddy_idx & page_idx;page = page + (combined_idx - page_idx);page_idx = combined_idx;order++;}set_page_order(page, order);/* 循环结束,标记了释放的连续page已经和之后的连续页形成了一个2的order次方的连续页框块 *//* 检查能否再进一步合并 */if ((order < MAX_ORDER-2) && pfn_valid_within(page_to_pfn(buddy))) {struct page *higher_page, *higher_buddy;combined_idx = buddy_idx & page_idx;higher_page = page + (combined_idx - page_idx);buddy_idx = __find_buddy_index(combined_idx, order + 1);higher_buddy = higher_page + (buddy_idx - combined_idx);if (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:/* 对应空闲链表中空闲块数量加1 */zone->free_area[order].nr_free++;
}

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

linux内存源码分析 - 伙伴系统(释放页框)相关推荐

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

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

  2. 一文给你解决linux内存源码分析- SLUB分配器概述(超详细)

    SLUB和SLAB的区别 首先为什么要说slub分配器,内核里小内存分配一共有三种,SLAB/SLUB/SLOB,slub分配器是slab分配器的进化版,而slob是一种精简的小内存分配算法,主要用于 ...

  3. linux内核源码剖析 博客,【Linux内存源码分析】页面迁移

    页面迁移其实是伙伴管理算法中的一部分,鉴于其特殊性,特地另行分析.它是2007年的时候,2.6.24内核版本开发时,新增碎片减少策略(the fragmentation reduction strat ...

  4. Linux内核源码分析之内存管理

    本文站的角度更底层,基本都是从Linux内核出发,会更深入.所以当你都读完,然后再次审视这些功能的实现和设计时,我相信你会有种豁然开朗的感觉. 1.页 内核把物理页作为内存管理的基本单元. 尽管处理器 ...

  5. Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 【转】...

    原文地址:Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinauni ...

  6. Linux内核源码分析《进程管理》

    Linux内核源码分析<进程管理> 前言 1. Linux 内核源码分析架构 2. 进程原理分析 2.1 进程基础知识 2.2 Linux进程四要素 2.3 进程描述符 task_stru ...

  7. Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7)【转】...

    原文地址:Linux内核源码分析--内核启动之(4)Image内核启动(setup_arch函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.c ...

  8. Linux内核源码分析—从用户空间复制数据到内核空间

    Linux内核源码分析-从用户空间复制数据到内核空间 本文主要参考<深入理解Linux内核>,结合2.6.11.1版的内核代码,分析从用户空间复制数据到内核空间函数. 1.不描述内核同步. ...

  9. Linux内核源码分析方法—程序员进阶必备

    一.内核源码之我见 Linux内核代码的庞大令不少人"望而生畏",也正因为如此,使得人们对Linux的了解仅处于泛泛的层次.如果想透析Linux,深入操作系统的本质,阅读内核源码是 ...

  10. iostat IO统计原理linux内核源码分析----基于单通道SATA盘

    iostat IO统计原理linux内核源码分析----基于单通道SATA盘 先上一个IO发送submit_bio流程图,本文基本就是围绕该流程讲解. 内核版本 3.10.96 详细的源码注释:htt ...

最新文章

  1. mysql 存储过程 数组参数_问个小问题,关于存储过程传递数组参数
  2. JAVA学习之常用集合List,Set,Map
  3. 最好用的硬盘搜索工具--Ava find pro
  4. Netty堆外内存泄露排查与总结 1
  5. oracle update范例,oracle 12c单范例数据库打12.1.0.2.4补丁记录
  6. 【winfrom】事件与委托
  7. ASP.NET 性能监控工具和优化技巧
  8. 多功能科学计算机在线使用,多功能科学计算器
  9. 594万元奖金 | “2020 年全国人工智能大赛”重磅启动
  10. 借助NetFlow Analyzer的IPAM SPM插件,实现IP和交换机端口管理
  11. 国产替代:T630 USB3.0接口芯片替换Cypress CYUSB3014
  12. M25F1 4G全网通终端的技术应用
  13. 【最终幻想15 国王之剑】制作介绍1:不再是游戏动画,而是“电影”制作
  14. thinksns java_ThinkSNS+ 更新播报
  15. 10.HTML基础——表格标签
  16. 云南师范大学计算机专硕,云南师范大学考研难吗?一般要什么水平才可以进入?...
  17. BB基础知识概念汇总和常见问题[最新补充JDE JDK主题
  18. 错换人生28年的主角姚策为什么临终前不愿见养母?
  19. ECMALL模板解析机制
  20. 如何学习调用股票量化交易API接口的方法?

热门文章

  1. 2011年智能手机:Android继续闪耀或暗淡?
  2. asp.net的条形码
  3. 编译器GCC的Windows版本 : MinGW-w64安装教程
  4. 基于墨刀实现的原型系统:多啦阅读
  5. 敏捷开发用户场景分析
  6. H5 data-* 属性,设置获取方法总结
  7. Spark SQL External Data Sources JDBC官方实现写测试
  8. Expression Blend 中的Sketchflow for Windows Phone 7
  9. Windows Server 2008官方(MSDN)简体中文正式版试用心得
  10. Rocketmq中Topic、Tag、GroupName的设计思想