当前位置: 首页 > news >正文

linux内核内存管理中的pagevec结构体

linux内核的内存管理中有一个2.6内核才加入的并不很张扬的结构体,那就是pagevec:
struct pagevec {
unsigned long nr;
unsigned long cold;
struct page *pages[14];
};
以往要加入到lru链表的page都要加入到这个pagevec了,并不再直接往lru中加入了。可是不加入lru的page就不会被内存管理机制所管理,因此仅仅这样是不行的,除非给pagevec结构加上lru的功能,然而这又势必会使这个结构体复杂化,再一个,这个结构体使用的最大范围就是“每cpu”,更多的它都是局部使用的,这样就使得锁的粒度细化了很多,而lru则是全局的,设计pagevec的目的之一正是因为这个。
我们知道,虽然lru机制理论上很完美,并且对于内存管理还有很多理论上完美的机制,然而linux内核在运行时却是有实际上的扫描时机的,与其说将页面一个一个的单独加入lru链表,不如使用懒惰的思想,在内核实际扫描lru的时候再加入。然而pagevec作为一个批量暂存的容器又不能过于庞大,应该将它调整为刚刚好的大小,关于此的解释我想没有比下面的解释更好并且通俗的了:
instead of carrying water from the well to the house in a spoon, it is more efficient to carry it in a bucket. this is because of the constant time for walking and opening doors and getting exclusive access to the well. but the full bucket (i.e. the contents _and_ the bucket together) should have a good size to handle; together with nr and cold you have 16 pointer-sized members in the structure which fits nicely into the cache lines and is easy to calculate with (if you don't count thumbs and big toes (which may have a special use as carry flags) most humans have 16 fingers and toes altogether).
是这样的,如果pagevec暂存的page数目过多,在vmscan的时候就会导致负担过重,并且pagevec本身的维护费用也会随之增加的,因此就刚好将之定为了2^4这么大。
我们可以从shrink_active_list和shrink_inactive_list的最后看到,都会将扫描到的page加入一个局部变量pvec(struct pagevec pvec)中,这个举动其实有两个层次的含义,第一个含义请看这两个函数的开始都有隔离链表的动作(isolate_lru_pages),这个动作的目的也是为了使锁的粒度更小一些,否则在操作链表的时候就要锁住全局的链表了。在隔离函数中我们看到递增了page的引用计数:
对于active或者inactive链表的每一个page:
list_del(&page->lru);
get_page_unless_zero(page);
list_add(&page->lru, target);
这个get_page的操作意思是说,现在vmscan正在扫描这个页面,每一个引用计数都代表了一条内核使用路径。因此我们必须在另一个地方能进行一个UNisolate_lru_pages的操作,在这个函数中递减page的引用计数,然而没有这样的函数,也不需要这样的函数,pagevec相关的接口函数就能做到这一点,在这两个shrink函数的最后,都有:
if (!pagevec_add(&pvec, page)) {
spin_unlock_irq(&zone->lru_lock);
__pagevec_release(&pvec);
spin_lock_irq(&zone->lru_lock);
}
其中加入到pvec的page一般是可以full的,因此__pagevec_release总能被调到
void __pagevec_release(struct pagevec *pvec)
{
lru_add_drain();
release_pages(pvec->pages, pagevec_count(pvec), pvec->cold);
pagevec_reinit(pvec);
}
即使它没有被调用到,也就是page数量不足14,那么函数结尾也要调用pagevec_release的。release函数中的第一行lru_add_drain就是将原先加入到“每cpu”各个pagevec的page们转移到它们真正应该处于的相应的lru链表上,同时将当时它们加入pagevec时递增的计数减下去(加入pagevec就说明有一条pagevec这个路径在引用这个page),__pagevec_release的第二行就是shrink函数中pvec相应举动的第二层意义,那就是批量地递减在isolate中被递增的引用计数。
shrink函数调用pagevec_add的真实目的其实就是为了最终调用__pagevec_release,这种代码如果不从全局理解是很难弄懂的。还有一个很值得注意的地方就是shrink_inactive_list和shrink_active_list的返回值的不同,扫描active返回空,而扫描inactive返回实际释放的页面数量,这也是合理的,因为扫描active链表的目的是为了补充inactive链表,也就是说参与内存管理的page们必然存在一个“被分配->active->inactive->free”的序列,只有最终地在inactive链表中的页面会被free,然而在shrink_active_list的最后由于pvec这个局部变量的举动,可能真的会有一个page被释放,为何不将它们的数量累加到释放的数量呢,也就是返回它们的数量呢?这是因为凡是这种方式被释放的页面都是在别的内核路径被put_page的,由于它们在加入“每cpu”的pagevec的时候递增了一个引用计数才导致当时被别的路经put_page的时候没有真的被free掉,因此它早就应该被free了,根本不是这次scan的功劳,它们由于一直暂时安全的呆在pagevec上,因此才活到了现在,它们被free的时刻应该是以前的某个时间(如果它们当初直接加在全局的lru而不是每cpu的pagevec上的话),所以它们当然是被直接的释放而不发出任何通知。
总之,shrink_active的时候,其目的是将“这次”牵扯的page“释放”到inactive链表,而只有在shrink_inactive的时候才会将page通过shrink_page_list“释放”到伙伴系统(广义上的“释放”)。pagevec其实本质上就是一个暂存容器,和上述的那段英文说明一样,它就是一个桶,代替了汤勺

相关文章:

  • poj_2352 线段树
  • Mac周边环境 goBASIC语言HelloWorld
  • linux内存管理系统后期的内核对zonelist的简化
  • bzoj3809: Gty的二逼妹子序列
  • linux内核page结构体的PG_referenced和PG_active标志
  • 解決BufferedReader读取UTF-8文件中文乱码(转)
  • 问题以及发现问题和解决问题
  • bitmap格式分析(转)
  • 关于数组或集合中判断存在某个元素
  • kexec机制
  • Spring事务配置的五种方式
  • buffer_head和bio
  • 关于人脸识别,稀疏表示的若干论文的小结
  • asp.net——正则表达式
  • 开始iOS 7中自动布局教程(一)
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • CSS实用技巧
  • docker python 配置
  • ES6语法详解(一)
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • PermissionScope Swift4 兼容问题
  • SpringCloud(第 039 篇)链接Mysql数据库,通过JpaRepository编写数据库访问
  • TypeScript迭代器
  • Vue.js源码(2):初探List Rendering
  • 动态魔术使用DBMS_SQL
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 前端工程化(Gulp、Webpack)-webpack
  • 前言-如何学习区块链
  • 让你的分享飞起来——极光推出社会化分享组件
  • 如何优雅地使用 Sublime Text
  • 通过来模仿稀土掘金个人页面的布局来学习使用CoordinatorLayout
  • 一、python与pycharm的安装
  • Java总结 - String - 这篇请使劲喷我
  • LIGO、Virgo第三轮探测告捷,同时探测到一对黑洞合并产生的引力波事件 ...
  • #include到底该写在哪
  • #LLM入门|Prompt#1.7_文本拓展_Expanding
  • #在线报价接单​再坚持一下 明天是真的周六.出现货 实单来谈
  • (2)nginx 安装、启停
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • (转)C#调用WebService 基础
  • .bat批处理(五):遍历指定目录下资源文件并更新
  • .class文件转换.java_从一个class文件深入理解Java字节码结构
  • .Net 高效开发之不可错过的实用工具
  • .NET成年了,然后呢?
  • [ 转载 ] SharePoint 资料
  • [20171101]rman to destination.txt
  • [C++]——带你学习类和对象
  • [CentOs7]搭建ftp服务器(2)——添加用户
  • [C语言]——C语言常见概念(1)
  • [c语言]小课堂 day2
  • [dart学习]第四篇:函数
  • [docker] Docker的私有仓库部署——Harbor
  • [ESP32] 编码旋钮驱动
  • [hive小技巧]同一份数据多种处理