本文转载自:http://www.cnblogs.com/tolimit/

感觉原博分析的不错,借花献佛。

-------------------------------------------------------------------------------

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

  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]);
else
list_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内核-页高速缓存

    页高速缓存:1.磁盘高速缓存的一种 2.一种对完整的数据页进行操作的磁盘高速缓存.3.将一页数据写到块设备的时候,内核首先检查对应的页是否已经在高速缓存中,不在就添加并填充数据.4.I\O数据的传送并 ...

  2. 现在的 Linux 内核和 Linux 2.6 的内核有多大区别?

    作者:larmbr宇链接:https://www.zhihu.com/question/35484429/answer/62964898来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转 ...

  3. 20169207《Linux内核原理与分析》第十周作业

    这周除了阅读学习教材「Linux内核设计与实现 (Linux Kernel Development)」第教材第15,16章外.我们还需要接着完成学习MOOC「Linux内核分析」第八讲「Linux系统 ...

  4. linux内核地址mapping

    linux内核采用页式存储管理,虚拟地址空间划分成固定大小的页面,由MMU(memory manager unit)在运行时将virtual address mapping to (或者说是变化成)某 ...

  5. 20169211《Linux内核原理与分析》 第十周作业

    一.Linux内核之进程地址空间学习总结 Linux内核除了要管理物理内存还需要管理虚拟内存.用户进程的地址空间就是虚拟内存的一部分.每个用户进程都独有一个地址空间.由于是虚拟化的内存,所以从每个进程 ...

  6. linux 内核源代码情景分析——地址映射的全过程

    linux 内核采用页式存储管理.虚拟地址空间划分成固定大小的"页面",由MMU在运行时将虚拟地址映射成某个物理内存页面中的地址.页式内存管理比段式内存管理有很多好处,但是由于In ...

  7. Linux内核入门到放弃-页面回收和页交换-《深入Linux内核架构》笔记

    概述 可换出页 只有少量几种页可以换出到交换区,对其他页来说,换出到块设备上与之对应的后备存储器即可,如下所述. 类别为 MAP_ANONYMOUS 的页,没有关联到文件,例如,这可能是进程的栈或是使 ...

  8. linux内核的冷热页分配器

    先说说cpu的cache,和cpu的cache比起来访问主内存是非常慢的,为了加快速度根据本地性原则,cpu在访问主内存的时候会把附近的一块数据都加载到cpu的cache里,之后读写这块数据都是在ca ...

  9. 《Linux内核设计与实现》读书笔记(十六)- 页高速缓存和页回写

    好久没有更新了... 主要内容: 缓存简介 页高速缓存 页回写 1. 缓存简介 在编程中,缓存是很常见也很有效的一种提高程序性能的机制. linux内核也不例外,为了提高I/O性能,也引入了缓存机制, ...

随机推荐

  1. react知识点总结(持续更新。。。)

    一.webpack 1. 什么是以及为什么要使用webpack 现在的网页功能越来越丰富,所需要的JavaScript和模块也会很多,为开发更加简洁,出现了以下方法,如模块化,scss,typescr ...

  2. cratedb 做为prometheus 的后端存储

    prometheus 提供了remote_write 以及remote_read 的数据存储方式,可以帮助我们进行数据的长时间存储.方便查询 cratedb 提供了对应的adapter,可以直接进行适 ...

  3. 超实用文件监控多线程FTP上传工具

    这是自己很久以前写的一个多线程FTP 上传工具,支持多账户,自定义线程数,自定义文件监控目录,可用做文件发布使用,非常实用,今天有小伙伴问起,现分享出来: using System; using Sy ...

  4. poj2398 Toy Storage 计算几何,叉积,二分

    poj2398 Toy Storage 链接 poj 题目大意 这道题的大概意思是先输入6个数字:n,m,x1,y1,x2,y2.n代表卡片的数量,卡片竖直(或倾斜)放置在盒内,可把盒子分为n+1块区 ...

  5. Spring事务经典案例-银行转账

    1.entity实体类 2.dao层 3.dao实现类 4.service层 5.serviceimpl层 6.大配置.xml <?xml version="1.0" enc ...

  6. Cisco路由器用SSH替代Telnet连接

    本文告诉你若何用SSH替代Telnet. 使用Telnet这个用来访谒远程计较机的TCP/IP和你的用户名和口令.很快地,会有人进行监听,而且他们会操作你平安是因为你意识的缺乏. SSH是替代Teln ...

  7. leetcode:7. 整数反转

    题目描述: 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转. 示例: 输入: 123 输出: 321 输入: -123 输出: -321 输入: 120 输出: 21 注意:假 ...

  8. Java 内存排查,慢慢收集总结

    Java堆外内存排查小结: https://mp.weixin.qq.com/s?__biz=MzA4MTc4NTUxNQ==&mid=2650518612&idx=2&sn= ...

  9. php 面试必备:各种缓存技术详解

    这门课程以电商网站为例,通过具体场景模块实战,让你更系统的掌握缓存原理.使用场景等相关知识,帮助你构建完整的缓存知识体系,胜任实际开发中缓存的处理,提升代码性能!    从原理到场景 系统讲解PHP缓 ...

  10. C#中得到每周,每月,每季,每年的年初末日期

    DateTime表示时间上的一刻,通常以日期和当天的时间表示.借用这个结构,我们可以实现较丰富的功能,本文给出得到每周每天的方法,及得到本月第一天,本月最后一天,本季第一天,本季最后一天,本年第一天及 ...