Linux内核

Stella981
• 阅读 583

本文转载自: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++; }

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

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这