fuse:纠结的page下刷流程之fuse_writepage_in_flight

news2024/11/18 11:41:06

fuse:纠结的page下刷流程细节之fuse_writepage_in_flight

  • fuse_writepage_in_flight
  • 硬爬代码
  • 自己理解消化
  • 作者本人如是说

fuse_writepage_in_flight

先说下这个函数,位于fs/fuse/file.c,这里以4.19内核来分析。因为这个函数里面藏了很多小细节,不看作者的pr comment真的很难完全理解到底是怎么想的。果然,如果再对比下5.10的版本这个函数的模样又变了不少。

static bool fuse_writepage_in_flight(struct fuse_req *new_req,
				     struct page *page);

参数new_req是一个刚刚填充好内容的,op是FUSE_WRITE类型的fuse request实例的指针,这个request里面也已经填充好了待下刷的若干页page,嗯提前剧透它肯定只包含一个page;
而第二个参数page是request里这“若干页page”中的最后一个page(其实也就是唯一的一个page)的copy。那看函数名称就是说需要来判断一下第二个参数这个page的内容,或者说fuse request对象里包含的最后一个page内容,是不是已经在发送流程中了(处于pending或sent状态);
从函数名来看只要求得到“是”或“否”的回答就可以了,但是C代码嘛,或者说内核代码嘛,函数名和其实际行为总是有那么一点点偏差。

硬爬代码

	struct fuse_conn *fc = get_fuse_conn(new_req->inode);
	struct fuse_inode *fi = get_fuse_inode(new_req->inode);
	struct fuse_req *tmp;
	struct fuse_req *old_req;
	bool found = false;
	pgoff_t curr_index;

	BUG_ON(new_req->num_pages != 0);

开始上来定义了一堆局部变量,都是和后续操作有关的,当然fuse内核模块几乎百分九十的函数都是从获取fuse的connect句柄和fuse inode对象开始。
这里两个fuse_req指针和curr_index是后续算法需要的,这里c代码必须在代码域开头一次性定义好局部变量的ugly就非常的扎眼了。
那句BUG_ON就非常有灵性,说明预期传入的fuse request实例必须是真的包含有至少一个单位的待刷page的。

spin_lock(&fc->lock);
list_del(&new_req->writepages_entry);
list_for_each_entry(old_req, &fi->writepages, writepages_entry) {
	BUG_ON(old_req->inode != new_req->inode);
	curr_index = old_req->misc.write.in.offset >> PAGE_SHIFT;
	if (curr_index <= page->index &&
		page->index < curr_index + old_req->num_pages) {
		found = true;
		break;
	}
}
if (!found) {
	list_add(&new_req->writepages_entry, &fi->writepages);
	goto out_unlock;
}

到这里整个人还是正常的,判断第二个参数指代的page内容是不是已经处于下刷过程中了。我们看到非常的笨啊,就是去遍历fuse inode的下刷请求链表writepages,一个个判断链表里面的fuse request,如果某个fuse request包含的一组连续pages中已经完全覆盖掉了本次期望下刷的page的内容,那就是yes in flight,否则就是not found。
简单的情况就是没找到,not found,found == false,那么把当前这个request对象也加到fuse inode的writepages链表中就完事了。

我们说这一段是非常清楚的,除了line2那句list_del(),先把当前fuse request对象从任何链表里摘出来,这就是典型的强耦合补丁式的行为,单看这个函数上下文,这里唯一的理解就是在插入list head前都一律要list_del一把,大家有兴趣自己爬代码吧。
接下来就有点烧脑了,

new_req->num_pages = 1;
for (tmp = old_req; tmp != NULL; tmp = tmp->misc.write.next) {
	BUG_ON(tmp->inode != new_req->inode);
	curr_index = tmp->misc.write.in.offset >> PAGE_SHIFT;
	if (tmp->num_pages == 1 &&
	    curr_index == page->index) {
		old_req = tmp;
	}
}

离大谱的是你凭什么确定new_req里面只包含一个page的?整个函数哪里说了new_req里一定只有一个函数的?好吧其实是全靠caller函数fuse_writepages_fill在调用本文主角的方式保证了传入的new request一定只携带了一个page,真的TM硬核耦合啊。
回头过来看这段,这里是已经找到了覆盖待刷page的一个old_req,但是这个old_req可能是所谓的“请求簇”,也就是多个fuse request通过misc.write的next字段组成一簇,第一个fuse request为主请求,后面的都是搭车的。现在我们要从这一簇中尝试找到满足如下两个条件的一个solo fuse request:

  1. 只携带单个page的数据;
  2. index正好也等于当前new request;

显然就是找到是否当前已经存在一个同样page的request,无外乎两种结果:
1). 是的当前page已经存在一个旧的fuse request;
2). 未能找到,此时old_req必然是一个携带了多个page的,且包含当前待刷page的fuse request;

下面来看下对于这两种结果分别如何处理

if (old_req->num_pages == 1 && test_bit(FR_PENDING, &old_req->flags)) {
	struct backing_dev_info *bdi = inode_to_bdi(page->mapping->host);
	copy_highpage(old_req->pages[0], page);
	spin_unlock(&fc->lock);

	dec_wb_stat(&bdi->wb, WB_WRITEBACK);
	dec_node_page_state(new_req->pages[0], NR_WRITEBACK_TEMP);
	wb_writeout_inc(&bdi->wb);
	fuse_writepage_free(fc, new_req);
	fuse_request_free(new_req);
	goto out;
} else {
	new_req->misc.write.next = old_req->misc.write.next;
	old_req->misc.write.next = new_req;
}

如果找到了下刷同一page的request且request还未发送到userspace,那么直接把当前最新的request的page负载覆盖掉旧request的负载即可;
如果没找到,或者找到了但是已经发送到userspace了,那么把当前新request直接链接到这个old reqeust后面;

自己理解消化

综上,就是三种结果:

  • 新的request插入到fuse inode对象的writepages链表中;
  • 新的request插入到某旧的request的簇组中;
  • 存在同一page的下刷request,直接覆盖其内容;
    在这里插入图片描述
    好,到这里可以开喷了,如开头所述,4.19版本的fuse_writepage_in_flight几乎违反了clean code所有的原则,特别是函数命名上,表面上是个仅回答yes or no的const方法,实际上它可能:
  • 把fuse request插入到fuse inode的writepages链表;
  • 把fuse request插入到某old request的簇组链表中;
  • 覆盖某old request的内容,本体被free;
    当然内核代码追求的是准确性、效率和稳定性,其他诸如可读性可扩展性都不在其主要考虑范围呢,高效准确的完成功能就好。

作者本人如是说

当时反复爬了这段代码好几次,始终觉得不是很透彻。俗话说解铃还须系铃人,特别是kernel代码,直接看作者的commit就清晰多了:

https://github.com/torvalds/linux/commit/8b284dc47291daf72fe300e1138a2e7ed56f38ab

所以这个函数所在的commit是为了解决这样一个问题:
有的时候,当我们准备下刷一个脏页时,这个脏页的一份copy可能正在被回写中。
讲真我想不出到底是什么场景,不过never mind先不要在意这些细节。这会带来的问题就是,导致对相同偏移的两个并发的写请求,如果最终写请求混在一起的话数据就会被破坏。
接下来Miklos Szeredi解释了这一段代码的逻辑如何来解决这个问题,思路就是delay掉其中一个req:

  • 针对某page,如果发现其copy正在under writeout,那么为其创建一个所谓secondary request,显然这个secondary request一定只会包含这一个page;
  • 尝试把这个secondary request加入到旧有的request的misc->write->next链表中;如果链表中已经存在针对同一页的一个secondary request,那么用当前的新request的page内容去覆盖旧的request的内容,然后丢弃掉新的secondary request。这样就能保证针对每个page,同一时间肯定只会有两个request在under writeout;
  • 当旧有的request被处理完毕后,依次发送后续的secondary requests即可;
    这里面内藏玄机,比如:
  • fuse inode->writepages里面的所有request对象,numpages总是为1,就是说都肯定只包含一个page;虽然代码上看似乎它处理了单个request包含多个page的情况;
  • 一簇request对象(也就是通过misc->write->next链起来的)的总数最大就是2,不会大于2;这两个request对象包含的是同一页;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/375475.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为OD机试模拟题 用 C++ 实现 - 删除指定目录(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 最多获得的短信条数(2023.Q1)) 文章目录 最近更新的博客使用说明删除指定目录题目输入输出示例一输入输出说明Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为…

Macbook M1 安装PDI(Kettle) 9.3

Macbook M1 安装PDI(Kettle) 9.3 当前 PDI&#xff08;Kettle&#xff09;最新版为9.3&#xff0c;依赖Java JDK 11。因为没有专门用于 M1的程序&#xff0c;需要下载并安装x86_64架构的JDK及依赖软件&#xff0c;并 “强制在Intel模式下运行shell” 的方式来实现 Kettle 的正…

骨传导蓝牙耳机排行,盘点几款性能不错的骨传导耳机

随着蓝牙耳机的普及&#xff0c;骨传导耳机也越来越受到欢迎&#xff0c;很多人也都开始在了解并尝试骨传导耳机。相比于其他类型耳机&#xff0c;在舒适度、安全方面有一定优势。尤其是在户外运动时&#xff0c;或者长时间佩戴运动时&#xff0c;使用骨传导耳机可以避免耳朵因…

从“入门”到“专家”,一份3000字完整的性能测试体系的知识分享

随着科技的飞速发展&#xff0c;软件产品广泛应用于各个行业领域&#xff0c;人们对计算机和网络的依赖性越来越大&#xff0c;对新奇事物也越来越感兴趣&#xff0c;成千上万的用户活跃在庞大的网络系统中&#xff0c;这给提供服务的系统带来严重的负荷&#xff0c;"高并…

QT之图形视图框架概述——Graphics View Framework

QT之图形视图框架概述——Graphics View Framework1. 概述2. 核心类3. 事件传递4. Graphics View 坐标系统5. 参考1. 概述 Graphics View Framework是子Qt 4.2引入的&#xff0c;用来取代之前版本中的QCanvas。Graphics View Framework提拱了用于大量2D图形项的管理和交互的能…

Spring Boot 统一功能处理(用户登录权限效验-拦截器、异常处理、数据格式返回)

文章目录1. 统一用户登录权限效验1.1 最初用户登录权限效验1.2 Spring AOP 统一用户登录验证1.3 Spring 拦截器1.4 练习&#xff1a;登录拦截器1.5 拦截器实现原理1.6 统一访问前缀添加2. 统一异常处理3. 统一数据格式返回3.1 统一数据格式返回的实现3.2 ControllerAdvice 源码…

day21_IO

今日内容 上课同步视频:CuteN饕餮的个人空间_哔哩哔哩_bilibili 同步笔记沐沐霸的博客_CSDN博客-Java2301 零、 复习昨日 一、作业 二、File 三、IO流 四、字节输入&输出流 零、 复习昨日 见晨考 一、作业 见答案二、File 2.1 介绍 File,通过一个路径代表文件或者文件夹 …

Panda Farm:首个部署在 Arbitrum 上的轻量化 GameFi 游戏

在2月16日&#xff0c;Bitget平台宣布 Launchpad 重新启动&#xff0c;并推出了重启后的首个项目 Panda Farm&#xff08;BBO&#xff09;&#xff0c;该 Launchpad 启动后得到了较高的关注。 Panda Farm 是部署在 Arbitrum 上的 GameFi应用&#xff0c;这可能首先意味着 Bitge…

技术干货 | Modelica建模秘籍之状态变量

在很多领域都有“系统”这个概念&#xff0c;它描述的往往是一些复杂关系的总和。假如我们将系统看做一个黑箱&#xff0c;那么&#xff0c;在系统的作用下&#xff0c;外界的输入有时会产生令人意想不到的输出&#xff0c;“蝴蝶效应”就是其中的典型案例。图1 一只南美洲亚马…

RPC编程:RPC框架设计目标

一&#xff1a;前导知识 Http是超文本传输协议&#xff0c;跨平台性非常好。Http可以传输文本&#xff0c;更多的时候传输的是文本&#xff0c;我们也是可以传输二进制的&#xff0c;我们基于Http进行下载的时候&#xff0c;就是走的Http协议。 Tcp协议&#xff0c;处理的时候…

OpenShift 4 - 使用辅助安装器安装单节点 OpenShift

文章目录单节点 OpenShift 和 OpenShift 辅助安装器单节点 OpenShiftOpenShift 辅助安装器使用辅助安装器安装单节点 OpenShift本文使用的安装环境准备环境在宿主机上安装 KVM 环境创建 SSH 证书根据集群配置&#xff0c;用辅助安装器生成 Discovery ISO用 Discovery ISO 启动 …

SpringBoot整合(五)HikariCP、Druid数据库连接池—多数据源配置

在项目中&#xff0c;数据库连接池基本是必不可少的组件。在目前数据库连接池的选型中&#xff0c;主要是 Druid &#xff0c;为监控而生的数据库连接池。HikariCP &#xff0c;号称性能最好的数据库连接池。 在Spring Boot 2.X 版本&#xff0c;默认采用 HikariCP 连接池。而…

黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了

这篇文章没有什么套路。就是一套自学理论和方向&#xff0c;具体的需要配合网络黑白去学习。毕竟是有网络才会有黑白&#xff01; 有自学也有培训&#xff01; 1.打死也不要相信什么分分钟钟教你成为大黑阔的&#xff0c;各种包教包会的教程,就算打不死也不要去购买那些所谓的…

【C语言每日一题】判断字符串旋转结果(附加字符串左旋详解)

【C语言每日一题】—— 判断字符串旋转结果&#x1f60e;&#x1f60e;&#x1f60e; 目录 &#x1f4a1;前言&#x1f31e;&#xff1a; &#x1f49b;字符串左旋题目&#x1f49b; &#x1f4aa; 解题思路的分享&#x1f4aa; &#x1f60a;题目源码的分享&#x1…

Aspose.Slides for .NET 授权须知

Aspose.Slides是一款用于生成&#xff0c;管理和转换PowerPoint幻灯片的本机API&#xff0c;可以使用多种格式&#xff0c;而不需要Microsoft PowerPoint。并且可在任何平台上操作PowerPoint演示文稿。 Aspose.Slides for .NET是一款.NET PowerPoint管理API&#xff0c;用于读…

资产设备防拆标签安全防护和资产定位解决方案

随着社会经济的发展和高新技术的日新月异&#xff0c;对各方面的安全要求也在不断地提高&#xff0c;以物联网安防、入侵报警和出入口控制、应急系统等为主的安全防范系统日益成为各类文物场所智能化弱电工程不可或缺的组成部分&#xff0c;是重点资产管理场所内加强管理和安全…

spring源码编译

spring源码编译1、安装gradle2、拉取源码3、配置gradle文件来源及镜像仓库4、预编译5、验证6、可能遇到的报错6.1、jdk.jfr不存在6.2、checkstyleMain6.3、org.gradle.api.artifacts.result.ComponentSelectionReason.getDescription()Ljava/lang/String6.4、其他jdk&#xff1…

做「增长」必须懂的6大关键指标

无论你所从事的是哪个行业&#xff0c;增长都不是一件易事&#xff0c;SaaS公司想要维持长期的增长更是难上加难。这是因为SaaS公司对未来回报的依赖程度更大&#xff0c;反观那些传统商业模式的公司&#xff0c;主要的收入来源都集中在产品购买交付的时点上&#xff0c;而客户…

统计数据资源整理:最全中国2010年人口普查数据Excel文件

很多小伙伴在做数据分析项目时找不到数据&#xff0c;这里分享我从《国家统计局官网》下载的2010年人口普查数据&#xff0c;总共434个Excel文件&#xff0c;全部整理好啦。 数据仅供学习交流使用 获取方法 链接&#xff1a;https://pan.baidu.com/s/1gfXLg5e8hxNeit3IBA17sA…

西电面向对象程序设计核心考点汇总(期末真题)

文章目录前言一、往年真题与答案1.1 改错题1.2 读程题1.3 面向对象程序设计二、易错知识点2.1 构造函数2.2 静态成员变量和静态成员函数2.3 权限2.4 继承2.5 多态总结前言 主要针对西安电子科技大学《面向对象程序设计》的核心考点进行汇总&#xff0c;包含总共8章的核心简答。…