公需道德与能力建设培训网站/站长工具ping检测
在分析内核内存回收源码时,page引用计数并不显眼,但是page引用计数对page的内存回收至关重要。本文基于linux-4.18.0-240版本内核源码,总结下文件页page的引用计数的相关细节。首先是get_page()和put_page()函数,分别令page引用计数加1和减1.
- //page引用计数加1
- static inline void get_page(struct page *page)
- {
- page_ref_inc(page);
- }
- //page引用计数减1
- static inline void put_page(struct page *page)
- {
- if (put_page_testzero(page))
- __put_page(page);
- }
以read系统调用读文件为例,最后执行到generic_file_buffered_read函数,先page_cache_alloc()分配一个文件页page,此时的page引用计数是0。
- static ssize_t generic_file_buffered_read(struct kiocb *iocb,
- struct iov_iter *iter, ssize_t written)
- {
- page = page_cache_alloc(mapping);
- error = add_to_page_cache_lru(page, mapping, index,mapping_gfp_constraint(mapping, GFP_KERNEL));
- }
然后执行add_to_page_cache_lru函数把page添加到radix/xrray tree,接着把page添加到lru缓存和lru链表
- int add_to_page_cache_lru(struct page *page, struct address_space *mapping,
- pgoff_t offset, gfp_t gfp_mask)
- {
- //把page添加到radix/xrray tree时令page引用计数加1
- ret = __add_to_page_cache_locked(page, mapping, offset,gfp_mask, &shadow);
- //page添加到lru缓存时令page引用计数加1,把page从lru缓存移动到lru链表时再令page引用计数减1
- lru_cache_add(page);
- }
在把page添加到radix/xrray tree时令page引用计数加1
- static int __add_to_page_cache_locked(struct page *page,
- struct address_space *mapping,
- pgoff_t offset, gfp_t gfp_mask,
- void **shadowp)
- {
- XA_STATE(xas, &mapping->i_pages, offset);
- .........
- //page引用计数加1
- get_page(page);
- page->mapping = mapping;
- page->index = offset;
- .........
- old = xas_load(&xas);
- xas_store(&xas, page);
- mapping->nrpages++;
- .........
- }
然后执行lru_cache_add函数把page添加到lru缓存时令page引用计数加1,把page从lru缓存移动到lru链表时再令page引用计数减1,函数流程是lru_cache_add->__lru_cache_add->__pagevec_lru_add->release_pages,关键函数如下:
把page添加到lru缓存时令page引用计数加1
- static void __lru_cache_add(struct page *page)
- {
- struct pagevec *pvec = &get_cpu_var(lru_add_pvec);
- //page引用计数加1
- get_page(page);
- if (!pagevec_add(pvec, page) || PageCompound(page))
- __pagevec_lru_add(pvec);
- put_cpu_var(lru_add_pvec);
- }
- //把page添加到lru缓存
- static inline unsigned pagevec_add(struct pagevec *pvec, struct page *page)
- {
- pvec->pages[pvec->nr++] = page;
- return pagevec_space(pvec);
- }
接着把page添加到lru链表时令page引用计数减1
- void __pagevec_lru_add(struct pagevec *pvec)
- {
- pagevec_lru_move_fn(pvec, __pagevec_lru_add_fn, NULL);
- }
- static void __pagevec_lru_add_fn(struct page *page, struct lruvec *lruvec,
- void *arg)
- {
- SetPageLRU(page);
- //把page添加到lru链表
- add_page_to_lru_list(page, lruvec, lru);
- }
- void release_pages(struct page **pages, int nr)
- {
- for (i = 0; i < nr; i++) {
- struct page *page = pages[i];
- //令page引用计数减1。如果之后page引用计数是0说明没有进程使用该page了,然后执行free_unref_page_list()把page释放回伙伴系统。
- if (!put_page_testzero(page))
- continue;
- .........
- list_add(&page->lru, &pages_to_free);
- }
- free_unref_page_list(&pages_to_free);
- }
如果是write系统调用对文件页page有写操作,则还要为page分配buffer_head(即bh),然后建立文件页page和bh的联系,令page引用计数加1。源码流程如下(以ext4文件系统为例):vfs_write->new_sync_write->ext4_file_write_iter->__generic_file_write_iter->generic_perform_write->ext4_da_write_begin->__block_write_begin_int->create_page_buffers->create_empty_buffers->attach_page_buffers,
- static inline void attach_page_buffers(struct page *page,
- struct buffer_head *head)
- {
- //令page引用计数加1
- get_page(page);
- //标记page的private属性
- SetPagePrivate(page);
- //建立page与bh的联系,本质是page->private=bh
- set_page_private(page, (unsigned long)head);
- }
OK,此时page的引用计数是2。接着来到page的内存回收,执行shrink_inactive_list()函数,这里把该函数的关键源码列下:
- static unsigned long shrink_inactive_list(unsigned long nr_to_scan, struct lruvec *lruvec,
- struct scan_control *sc, enum lru_list lru)
- {
- spin_lock_irq(&pgdat->lru_lock);
- //根据nr_to_scan数目从inactive lru链表隔离page符合条件的page到page_list链表,同时都令这些page的引用计数加1
- nr_taken = isolate_lru_pages(nr_to_scan, lruvec, &page_list,&nr_scanned, sc, isolate_mode, lru);
- spin_unlock_irq(&pgdat->lru_lock);
- ..........
- nr_reclaimed = shrink_page_list(&page_list, pgdat, sc, 0,&stat, false);
- ..........
- spin_lock_irq(&pgdat->lru_lock);
- //没有成功内存回收的page再移动回 active/inactive lru链表,page引用计数减1。如果page引用计数是0说明没人用了,再移动回page_list
- putback_inactive_pages(lruvec, &page_list);
- spin_unlock_irq(&pgdat->lru_lock);
- ..........
- //释放page_list上引用计数是0的page
- free_unref_page_list(&page_list);
- }
看下隔离page执行的isolate_lru_pages函数,主要是令page引用计数加1
- static unsigned long isolate_lru_pages(unsigned long nr_to_scan,
- struct lruvec *lruvec, struct list_head *dst,
- unsigned long *nr_scanned, struct scan_control *sc,
- isolate_mode_t mode, enum lru_list lru)
- {
- //page符合内存回收条件则清理page的PageLRU属性,并令page引用计数加1,返回0,否则返回负数
- switch (__isolate_lru_page(page, mode)) {
- case 0:
- ........
- //把符合内存回收条件的page从lru链表移动到dst临时链表
- list_move(&page->lru, dst);
- break;
- case -EBUSY:
- list_move(&page->lru, src);
- continue;
- }
- }
- int __isolate_lru_page(struct page *page, isolate_mode_t mode)
- {
- int ret = -EINVAL;
- //关键点,如果page已经从lru链条剔除,page隔离失败
- if (!PageLRU(page))
- return ret;
- ret = -EBUSY;
- //page引用计数不是0则加1并返回true。否则说明page应用计数是0,返回false,这种page已经没进程在使用了,已经不在LRU链表了
- if (likely(get_page_unless_zero(page))){
- //page将要从active或inactive lru链表移除,于是清理page的PageLRU属性
- ClearPageLRU(page);
- ret = 0;
- }
- return ret;
- }
注意,隔离page时,在对spin_lock对lru_lock加锁后,要令page引用计数加1,这个非常重要。此时其他进程就无法释放这个page了!如果在隔离page前,这个page可能被其他进程释放回伙伴系统,那page将没有LRU属性,此时__isolate_lru_page函数里的if (!PageLRU(page))将起到作用,导致隔离page失败。如果隔离page时没有对page引用计数加1,那page将可能并发被其他进程释放回伙伴系统,或者被释放回伙伴系统并且被新的进程分配并加入新的lru链表。这种情况下,page->mapping将发生变化,与原始的mapping就不一样了,可以据此判断出这种异常。
好的,page引用计数此时是3,接着来到shrink_page_list()函数对page进行真正的内存回收。
- static unsigned long shrink_page_list(struct list_head *page_list,
- struct pglist_data *pgdat,
- struct scan_control *sc,
- enum ttu_flags ttu_flags,
- struct reclaim_stat *stat,
- bool force_reclaim)
- {
- while (!list_empty(page_list)) {
- ............
- //page有映射的bh
- if (page_has_private(page)) {
- //page和bh解除联系,并且令page引用计数减1
- if (!try_to_release_page(page, sc->gfp_mask))
- goto activate_locked;
- }
- ...........
- //把page从radix tree、address_space 剔除,如果page引用计数是2则清0,返回1,page可以释放。否则page还再被其他进程使用,返回0,不能释放
- else if (!mapping || !__remove_mapping(mapping, page, true))
- goto keep_locked;
- free_it:
- nr_reclaimed++;
- list_add(&page->lru, &free_pages);
- continue;
- activate_locked:
- //重新设置page active
- SetPageActive(page);
- keep_locked:
- unlock_page(page);
- keep:
- //到这里,page本轮不能回收,暂存ret_pages链表然后再移回active或inactive lru链表
- list_add(&page->lru, &ret_pages);
- }
- //释放free_pages上的page到伙伴系统
- free_unref_page_list(&free_pages);
- ...............
- }
如果page有bh则if (page_has_private(page))成立,然后执行try_to_release_page解除page和bh的联系,并令page引用计数减1,源码流程是try_to_release_page->ext4_releasepage->try_to_free_buffers->drop_buffers->__clear_page_buffers,
- static void __clear_page_buffers(struct page *page)
- {
- //解除page和bh的联系
- ClearPagePrivate(page);
- set_page_private(page, 0);
- //令page引用计数减1
- put_page(page);
- }
此时page的引用计数是2,然后执行到__remove_mapping()函数。
- static int __remove_mapping(struct address_space *mapping, struct page *page,
- bool reclaimed)
- {
- refcount = 2;
- //page引用计数是2则对page引用计数清0,并返回true,这个page可以释放了。否则page引用计数不是2则保持引用计数并返回false,这个page不能释放
- if (!page_ref_freeze(page, refcount))
- goto cannot_free;
- .............
- //把page从radix tree 剔除
- __delete_from_page_cache(page, shadow);
- return 1;
- cannot_free:
- return 0;
- }
主要作用是:把page从radix tree、address_space 剔除,如果page引用计数是2则清0,返回1,page可以释放。否则page还再被其他进程使用,返回0,不能释放
好的,正常情况page引用计数此时就是0了,然后就可以释放掉这个page了。如果page因为是脏页、writeback页等导致page回收失败,page就要暂存在page_list链表。shrink_page_list()函数执行后,再执行putback_inactive_pages()函数把page移动回lru链表,源码如下:
- static void putback_inactive_pages(struct lruvec *lruvec, struct list_head *page_list)
- {
- struct zone_reclaim_stat *reclaim_stat = &lruvec->reclaim_stat;
- struct pglist_data *pgdat = lruvec_pgdat(lruvec);
- LIST_HEAD(pages_to_free);
- while (!list_empty(page_list)) {
- struct page *page = lru_to_page(page_list);
- int lru;
- lruvec = mem_cgroup_page_lruvec(page, pgdat);
- //page要添加到inactive lru,设置LRU属性
- SetPageLRU(page);
- lru = page_lru(page);
- //把page添加到lru链表,并增加lru链表page数
- add_page_to_lru_list(page, lruvec, lru);
- //page引用计数减1,减1后如果是0就说明page没人用了,可以释放了
- if (put_page_testzero(page)) {
- //清理page的lRU和active属性
- __ClearPageLRU(page);
- __ClearPageActive(page);
- //把page从lru链表剔除,并减少lru链表的page数
- del_page_from_lru_list(page, lruvec, lru);
- //把page再移动到pages_to_free链表,之后就直接释放掉
- list_add(&page->lru, &pages_to_free);
- }
- list_splice(&pages_to_free, page_list);
- }
这里对page引用计数减1,因为之前隔离收该page时令page引用计数加1了,二者对冲掉。
OK,本文到这里基本就结束了。page引用计数可能有点复杂,简单说,当page要启用一个新功能时,就要对page引用计数加1,而回收page时要一一对应对page引用计数减1。水平有限,如有错误请指出。
相关文章:

内核内存回收关键隐藏变量之page引用计数
在分析内核内存回收源码时,page引用计数并不显眼,但是page引用计数对page的内存回收至关重要。本文基于linux-4.18.0-240版本内核源码,总结下文件页page的引用计数的相关细节。首先是get_page()和put_page()函数,分别令page引用计…...

数据结构---链表的基本操作
头插法遍历链表尾插法头删法尾删法按位置插入数据按位置删除数据直接插入排序 链表翻转快慢指针 linklist.c #include <stdio.h> #include <stdlib.h> #include "./linklist.h"linklist* create_linklist(void) {linklist* head (linklist*)malloc(siz…...

异步框架Celery在Django中的运用
参考博客:https://www.cnblogs.com/pyedu/p/12461819.html 参考视频:01 celery的工作机制_哔哩哔哩_bilibili 定义:简单灵活、处理大量消息的分布式系统,专注于实时处理异步队列,支持任务调度 主要架构: …...

YOLOv5代码解读[02] models/yolov5l.yaml文件解析
文章目录 YOLOv5代码解读[02] models/yolov5l.yaml文件解析yolov5l.yaml文件检测头1--->耦合头检测头2--->解耦头检测头3--->ASFF检测头Model类解析parse_model函数 YOLOv5代码解读[02] models/yolov5l.yaml文件解析 yolov5l.yaml文件 # YOLOv5 🚀 by Ult…...

智能搬运机器人|海格里斯将如何持续推进工业和物流的智能化升级与发展?
存取、搬运、分拣是物流行业中的通用功能,但具体到每个行业又十分不同,例如:新能源电池领域,它所搬运的东西是电池,50KG~200KG;快递行业领域,所要处理的物料是那种扁平件和信封等等,…...

linux之前后端项目部署与发布
目录 前言 简介 一、安装Nginx 二、后端部署 2.1多个tomcat负载均衡 2.2 负载均衡 2.3 后端项目部署 三、前端部署 1.解压前端 2.Nginx配置文件修改 3.IP域名映射 4.重启Nginx服务 前言 上篇博主已经讲解过了单机项目的部署linux之JAVA环境配置JDK&Tomcat&a…...

Python 高级语法:一切皆对象
1 “一切皆对象”是一种核心设计哲学 在编程领域,特别是面向对象编程(OOP)中,“一切皆对象”是一种核心设计哲学。这种哲学主张,无论是数据、函数、还是更复杂的结构,都可以被视为对象,并赋予…...

python jupyter notebook打开页面方便使用
如果没安装jupyter, 请安装: pip install jupyter notebook 运行jupyter notebook jupyter-notebook...

音视频开发之旅(69)-SD图生图
目录 1. 效果展示 2. ControlNet介绍 3. 图生图流程浅析 4. SDWebui图生图代码流程 5. 参考资料 一、效果展示 图生图的应用场景非常多,比较典型的应用场景有风格转化(真人与二次元)、线稿上色、换装和对图片进行扩图等,下面…...

數據集成平台:datax將hive數據步到mysql(全部列和指定列)
數據集成平台:datax將hive數據步到mysql(全部列和指定列) 1.py腳本 傳入參數: target_database:數據庫 target_table:表 target_columns:列 target_positions:hive列的下標&#x…...

pikachu靶场-File Inclusion
介绍: File Inclusion(文件包含漏洞)概述 文件包含,是一个功能。在各种开发语言中都提供了内置的文件包含函数,其可以使开发人员在一个代码文件中直接包含(引入)另外一个代码文件。 比如 在PHP中,提供了&…...

[今天跟AI聊聊职场] ~你能接受你的直接领导能力不如你,年纪还比你小很多吗?
知乎问题: 弟弟今年35岁,刚换了一份工作,直接领导小A比他小5岁,各方面经验没有他成熟。难的工作都是弟弟在做,功劳都被直接领导小A抢走了,有时候还要被直接领导小A打压。弟弟感觉升职加薪无望。现在找工作不…...

网络原理TCP之“三次握手“
TCP内核中的建立连接 众所周知,TCP是有连接的. 当我们在客户端敲出socket new Socket(serverIp,severPort)时,就在系统内核就在建立连接 真正建立连接是在系统内核中建立的,我们程序员只是调用相关的api. 在此处,我们把TCP的建立连接称为三次握手. 系统在内核建立连接时如上…...

990-03产品经理与程序员:什么是 IT 与业务协调以及为什么它很重要?
What is IT-business alignment and why is it important? 什么是IT-业务一致性?为什么它很重要? It’s more important than ever that IT and the business operate from the same playbook(剧本). So why do so many organizations struggle to ach…...

Java Web(七)__Tomcat(二)
Tomcat工作模式 Tomcat作为Servlet容器,有以下三种工作模式。 1)独立的Servlet容器,由Java虚拟机进程来运行 Tomcat作为独立的Web服务器来单独运行,Servlet容器组件作为Web服务器中的一部分而存在。这是Tomcat的默认工作模式。…...

【项目实战】帮美女老师做一个点名小程序(Python tkinter)
前言 博主有一个非常漂亮的老师朋友😍。最近,她急需一个能够实现随机点名的小程序,而博主正好擅长这方面的技术🤏。所以,今天博主决定为她制作一个专门用于点名的小程序💪。 博主在美女老师面前吹完牛皮之…...

Elasticsearch 去重后求和
标题的要求可以用如下 SQL 表示 select sum(column2) from (select distinct(column1),column2 from table)t 要如何用 DSL 实现呢,先准备下索引和数据 PUT test_index {"mappings": {"properties": {"column1": {"type"…...

考研数学——高数:函数与极限(3)
函数的连续性与间断点 函数的连续性 左连续 右连续 区间上的连续性 在xo处连续 函数的间断点 第一类间断点(左右极限都存在) 可去间断点: f(xo-0)= f(xo+0) 跳跃间断点: f(xo-0)≠ f(xo+0) 第二类间断点(震荡间断点、无穷间断点)...

LeetCode49 字母异位词分组
LeetCode49 字母异位词分组 在这篇博客中,我们将探讨 LeetCode 上的一道经典算法问题:字母异位词分组。这个问题要求将给定的字符串数组中的字母异位词组合在一起,并以任意顺序返回结果列表。 问题描述 给定一个字符串数组 strs࿰…...

【Python】Windows本地映射远程Linux服务器上的端口(解决jupyter notebook无法启动问题)
创作日志: 学习深度学习不想在本地破电脑上再安装各种软件,我就用实验室的服务器配置环境,启动jupyter notebook时脑子又瓦特了,在自己Windows电脑上打开服务器提供的网址,那肯定打不开啊,以前在其它电脑上…...

C++面试:用户态和内核态的基本概念、区别
目录 一、基本概念 概念: 区别: 二、Windows示例 基础介绍 用户态到内核态的切换过程: 程序实例 三、Linux示例 特权级别: 用户态到内核态的切换过程: 调度和中断处理: 程序实例 总结 在操作系…...

Vue计算属性computed()
1. 计算属性定义 获取计算属性值 <div>{{ 计算属性名称}}</div>创建计算属性 let 定义的属性ref/reactive....let 计算属性名称 computed(() > {//这里写函数式,函数式里面包含定义属性//只有这个包含的定义属性被修改时才出发此函数式//通过计算属性名称co…...

JWT学习笔记
了解 JWT Token 释义及使用 | Authing 文档 JSON Web Token Introduction - jwt.io JSON Web Token (JWT,RFC 7519 (opens new window)),是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准((RFC 7519)。该 token 被设计为紧凑…...

WSL里的Ubuntu 登录密码忘了怎么更改
环境: Win10 专业版 WSL2 如何 Ubuntu22.04 问题描述: WSL里的Ubuntu 登录密码忘了怎么更改 解决方案: 在WSL中的Ubuntu系统中,忘记了密码,可以通过以下步骤重置密码: 1.打开命令提示符或PowerShel…...

【软件测试面试】要你介绍项目-如何说?完美面试攻略...
目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 1、测试面试时&am…...

【Crypto | CTF】RSA打法 集合
天命:我发现题题不一样,已知跟求知的需求都不一样 题目一:已知 p q E ,计算T,最后求D 已知两个质数p q 和 公钥E ,通过p和q计算出欧拉函数T,最后求私钥D 【密码学 | CTF】BUUCTF RSA-CSDN…...

在springboot中调用openai Api并实现流式响应
之前在《在springboot项目中调用openai API及我遇到的问题》这篇博客中,我实现了在springboot中调用openai接口,但是在这里的返回的信息是一次性全部返回的,如果返回的文字比较多,我们可能需要等很久。 所以需要考虑将请求接口响应…...

C++构造函数重难点解析
一、C构造函数是什么 C的构造函数是一种特殊的成员函数,用于初始化类的对象。它具有与类相同的名称,并且没有返回类型。构造函数在创建对象时自动调用,并且可以执行必要的初始化操作。 二、C构造函数特点 类的构造函数不能被继承,…...

QT day3 作业2.22
思维导图: 作业: 完善对话框,点击登录对话框,如果账号和密码匹配,则弹出信息对话框,给出提示”登录成功“,提供一个Ok按钮,用户点击Ok后,关闭登录界面,跳转到…...

AR汽车行业解决方案系列之2-远程汽修
在汽车行业中,AR技术的应用正悄然改变着整个产业链的运作方式,应用涵盖培训、汽修、汽车售后、PDI交付、质检以及汽车装配等,AR技术为多个环节都带来了前所未有的便利与效率提升。 安宝特AR将以系列推文的形式为读者逐一介绍在汽车行业中安宝…...