3.10 内核 BUG_ON() at xfs_vm_writepage() -> page_buffers()

目录

前言

问题分析

page buffers创建

page buffers丢失

Write-Protect

Dirty Page w/o Buffers

问题解决


前言

这个问题发生在3.10.0-514.el7上,并且在RHEL的知识库中快速找到了对应的案例以及解决方案,但是,理解问题如何发生和解决则着实费了些功夫。

RHEL的链接如下:

RHEL7: kernel crash in xfs_vm_writepage - kernel BUG at fs/xfs/xfs_aops.c:1062! - Red Hat Customer Portal

调用栈为:

[1004630.854317] kernel BUG at fs/xfs/xfs_aops.c:1062!
[1004630.854894] invalid opcode: 0000 [#1] SMP 
[1004630.861333] CPU: 6 PID: 56715 Comm: kworker/u48:4 Tainted: G        W      ------------   3.10.0-514.el7.x86_64 #1
[1004630.862046] Hardware name: HP ProLiant BL460c Gen9, BIOS I36 12/28/2015
[1004630.862703] Workqueue: writeback bdi_writeback_workfn (flush-253:28)
[1004630.863414] task: ffff881f8436de20 ti: ffff881f23a4c000 task.ti: ffff881f23a4c000
[1004630.864117] RIP: 0010:[<ffffffffa083f2fb>]  [<ffffffffa083f2fb>] xfs_vm_writepage+0x58b/0x5d0 [xfs]
[1004630.864860] RSP: 0018:ffff881f23a4f948  EFLAGS: 00010246
[1004630.865749] RAX: 002fffff00040029 RBX: ffff881bedd50308 RCX: 000000000000000c
[1004630.866466] RDX: 0000000000000008 RSI: ffff881f23a4fc40 RDI: ffffea00296b7800
[1004630.867218] RBP: ffff881f23a4f9f0 R08: fffffffffffffffe R09: 000000000001a098
[1004630.867941] R10: ffff88207ffd6000 R11: 0000000000000000 R12: ffff881bedd50308
[1004630.868656] R13: ffff881f23a4fc40 R14: ffff881bedd501b8 R15: ffffea00296b7800
[1004630.869399] FS:  0000000000000000(0000) GS:ffff881fff180000(0000) knlGS:0000000000000000
[1004630.870147] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[1004630.870868] CR2: 0000000000eb3d30 CR3: 0000001ff79dc000 CR4: 00000000001407e0
[1004630.871610] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[1004630.872349] DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
[1004630.873072] Stack:
[1004630.873749]  0000000000008000 ffff880070b03644 ffff881f23a4fc40 ffff881f23a4fa68
[1004630.874480]  ffff881f23a4fa80 ffffea00296b7800 0000000000001000 000000000000000e
[1004630.875223]  0000000000001000 ffffffff81180981 0000000000000000 ffff881bedd50310
[1004630.875957] Call Trace:
[1004630.876665]  [<ffffffff81180981>] ? find_get_pages_tag+0xe1/0x1a0
[1004630.877417]  [<ffffffff8118b3b3>] __writepage+0x13/0x50
[1004630.878173]  [<ffffffff8118bed1>] write_cache_pages+0x251/0x4d0
[1004630.878915]  [<ffffffffa00c170a>] ? enqueue_cmd_and_start_io+0x3a/0x40 [hpsa]
[1004630.879626]  [<ffffffff8118b3a0>] ? global_dirtyable_memory+0x70/0x70
[1004630.880368]  [<ffffffff8118c19d>] generic_writepages+0x4d/0x80
[1004630.881157]  [<ffffffffa083e063>] xfs_vm_writepages+0x53/0x90 [xfs]
[1004630.881907]  [<ffffffff8118d24e>] do_writepages+0x1e/0x40
[1004630.882643]  [<ffffffff81228730>] __writeback_single_inode+0x40/0x210
[1004630.883403]  [<ffffffff8122941e>] writeback_sb_inodes+0x25e/0x420
[1004630.884141]  [<ffffffff8122967f>] __writeback_inodes_wb+0x9f/0xd0
[1004630.884863]  [<ffffffff81229ec3>] wb_writeback+0x263/0x2f0   
[1004630.885610]  [<ffffffff810ab776>] ? set_worker_desc+0x86/0xb0
[1004630.886378]  [<ffffffff8122bd05>] bdi_writeback_workfn+0x115/0x460
[1004630.887142]  [<ffffffff810c4cf8>] ? try_to_wake_up+0x1c8/0x330
[1004630.887875]  [<ffffffff810a7f3b>] process_one_work+0x17b/0x470
[1004630.888638]  [<ffffffff810a8d76>] worker_thread+0x126/0x410   
[1004630.889389]  [<ffffffff810a8c50>] ? rescuer_thread+0x460/0x460
[1004630.890126]  [<ffffffff810b052f>] kthread+0xcf/0xe0
[1004630.890816]  [<ffffffff810b0460>] ? kthread_create_on_node+0x140/0x140
[1004630.891521]  [<ffffffff81696418>] ret_from_fork+0x58/0x90
[1004630.892229]  [<ffffffff810b0460>] ? kthread_create_on_node+0x140/0x140
[1004630.892877] Code: e0 80 3d 4d b4 06 00 00 0f 85 a4 fe ff ff be d7 03 00 00 48 c7 c7 4a e0 88 a0 e8 61 66 84 e0 c6 05 2f b4 06 00 01 e9 87 fe ff ff <0f> 0b 8b 4d a4 e9 e8 fb ff ff 41 b9 01 00 00 00 e9 69 fd ff ff 
[1004630.894245] RIP  [<ffffffffa083f2fb>] xfs_vm_writepage+0x58b/0x5d0 [xfs]
[1004630.894890]  RSP <ffff881f23a4f94

问题发生的位置为:

xfs_vm_writepage()
---...bh = head = page_buffers(page);...
---#define page_buffers(page)                    \({                            \BUG_ON(!PagePrivate(page));            \((struct buffer_head *)page_private(page));    \})

问题分析

page buffers创建

在Linux内核中,buffer head的目的是为了对接在存储子系统和内存子系统的两个基本单位:

  • sector,这是存储的基本单位,512字节
  • page,这个是内存的基本单位,4096字节

文件缓存,即page cache,是存储子系统和内存子系统的结合部,buffer_head对应的就是page cache中的一个sector;当我们格式化文件系统,把fsblock设置为512、1024、2048字节时,或者操作raw block设备时,每个page就会对应多个buffer_head;

近些年,512字节的fsblock基本被主流文件系统抛弃,虽然还支持,但是都以支持4K为主要的优化方向,xfs甚至抛弃了buffer_head,直接使用page作为IO操作的基本单位;

那么,buffer_head都是在哪些契机被创建呢?

3.10.0-514.el7
【A】
generic_perform_write()-> aops->write_begin()xfs_vm_write_begin()-> grab_cache_page_write_begin()-> __block_write_begin()-> create_page_buffers()
【B】
do_shared_fault()-> __do_fault()-> vma->vm_ops->page_mkwrite()xfs_filemap_page_mkwrite()-> __block_page_mkwrite()-> __block_write_begin()-> create_page_buffers()do_mpage_readpage() -> block_read_full_page() //如果page中各个bh的状态不一致,不如有些map有些unmap,会进入到此路径,分别对bh进行操作-> create_page_buffers()

通常来讲【A】和【B】路径可以保证,在对page做写操作之前,保证page buffers已经创建,它们分别代表是通过系统调用和mmap对文件进行写的场景;

page buffers丢失

那本问题中,被设置了dirty标记的page的buffer是如何丢失的呢?

page buffer被释放的典型场景:

3.10.0-514.el7shrink_page_list()-> try_to_unmap()-> try_to_release_page()-> aops->releasepage()xfs_vm_releasepage()-> try_to_free_buffers()-> __remove_mapping()

在try_to_release_page()之前,try_to_unmap()会被调用,它会清理掉pte,并且检测是否需要page dirty

3.10.0-514.el7try_to_unmap()-> try_to_unmap_file()-> try_to_unmap_one()-> set_page_dirty() //pte_dirty()

这样就可以保证,在执行try_to_release_page()之前,给page及其buffer设置dirty 标记,

3.10.0-514.el7xfs_vm_set_page_dirty()
---spin_lock(&mapping->private_lock);if (page_has_buffers(page)) {struct buffer_head *head = page_buffers(page);struct buffer_head *bh = head;do {if (offset < end_offset)set_buffer_dirty(bh);bh = bh->b_this_page;offset += 1 << inode->i_blkbits;} while (bh != head);}
---try_to_free_buffers()-> drop_buffers()-> buffer_busy()-> atomic_read(&bh->b_count) | (bh->b_state & ((1 << BH_Dirty) | (1 << BH_Lock)))

如果buffer有dirty标记就不会被释放。另外,有truncate和invalidate的场景,也是类似的操作。

但是有一个机器特殊的场景:

3.10.0-514.el7shrink_active_list()
---if (unlikely(buffer_heads_over_limit)) {if (page_has_private(page) && trylock_page(page)) {if (page_has_private(page))try_to_release_page(page, 0);unlock_page(page);}}
---

这里对buffers直接进行释放。

Write-Protect

用对mmap的page的写操作,是通过下面的机器触发的page fault并给page设置dirty的

3.10.0-514.el7generic_writepages()-> clear_page_dirty_for_io()-> page_mkclean()-> page_mkclean_file()-> page_mkclean_one()-> pte_wrprotect()-> pte_mkclean()handle_pte_fault()-> do_wp_page() // pte_present() && !pte_write()-> wp_page_shared()-> do_page_mkwrite()-> xfs_filemap_page_mkwrite()-> __block_page_mkwrite()-> lock_page()-> __block_write_begin()-> block_commit_write()-> set_page_dirty(page);-> wait_for_stable_page(page);-> wp_page_reuse()-> set_page_dirty()-> unlock_page()

在执行writepage之前,在page_lock的保护之下,通过clean_page_dirty_for_io()清除page的dirty  flags以及mmap的pte的写权限,将相关page变为write-protect,这样,下次用户写这个page的时候,就会触发pagefault,内核在这里将相关的page设置为dirty,在此期间,会给page创建buffers;这样,就可以保证任何对mmap的写操作,都可以通过page fault提交到writeback子系统中。

write-protect page fault发生时,写操作还没有发生,所以,dirty bit并不会被设置;而一旦写操作发生,那么上面的代码所代表的过程,一定会发生,那么buffer也一定是具备的。

Dirty Page w/o Buffers

这里我们对比下ext4和xfs的writepages操作的调用栈:

ext4_writepages()-> mpage_prepare_extent_to_map()-> pagevec_lookup_tag()-> lock_page()-> wait_on_page_writeback()-> mpage_process_page_bufs()-> mpage_submit_page()-> clear_page_dirty_for_io(page)-> ext4_bio_write_page()-> set_page_writeback()-> io_submit_add_bh()-> clean_buffer_dirty()-> unlock_page()xfs_vm_writepages()-> generic_writepages()-> write_cache_pages()-> pagevec_lookup_tag()-> lock_page()-> xfs_vm_writepage()-> lock_buffer()-> xfs_add_to_ioend()-> xfs_start_page_writeback()-> clear_page_dirty_for_io()-> set_page_writeback()-> unlock_page() <<-----------------------HERE!!!!                    -> xfs_submit_ioend()-> xfs_start_page_writeback()-> mark_buffer_async_write()-> set_buffer_uptodate()-> clear_buffer_dirty()

在ext4调用栈中,Page Dirty和Buffer Dirty的清理都是在page_lock下进行的;而xfs中,buffer的清理是在page_lock之外,这时,我们如果引入page fault过程中的page_mkwrite调用链,就会产生以下竞态,

writeback workqueue             user page fault()
xfs_vm_writepages()             xfs_filemap_page_mkwrite()
lock_page()                     __block_page_mkwrite()
clear_page_dirty_for_io()
unlock_page()lock_page()xfs_vm_set_page_dirty()set_buffer_dirty()TestSetPageDirty()
clear_buffer_dirty()end_page_writeback()

于是这里,我们得到了一个page有dirty flags,但是buffer全是clean的;如果将此场景带入到ext4,就不会有这种问题,因为有page_lock的保护,最终的结果,要么是page buffer全部dirty,要是全是clean。

到这一步,产生了两个关键点:

  • page dirty + buffer clean
  • dirty bit,因为write-protect page fault已经发生过,所以,写操作已经完成

其中page dirty + buffer clean将继续推进问题的发生;

我们再回到shrink_active_list(),它可能会调用try_to_release_page(),

try_to_free_buffers()-> drop_buffers()-> buffer_busy() // dirty or lock-> cancel_dirty_page()

page dirty + buffer clean,在这里,因为buffer是clean的,所以,它可以被释放,然后page的dirty也被清除了;

但是,此时pte中的dirty bit是存在的,于是在后续的shrink_page_list()中:

tshrink_page_list()-> try_to_unmap()-> try_to_unmap_file()-> try_to_unmap_one()-> set_page_dirty() //pte_dirty()

page被设置dirty,然后回收中止;得到了一个page dirty + no buffers

所以,问题的关键是,xfs_vm_writepage()中,对page dirty和buffer dirty的clean操作并没有在page_lock的保护下

问题解决

在搜索社区代码和Commit记录之后,该问题在以下commit解决:

commit e10de3723c53378e7cf441529f563c316fdc0dd3
Author: Dave Chinner <dchinner@redhat.com>
Date:   Mon Feb 15 17:23:12 2016 +1100xfs: don't chain ioends during writepage submission@@ -565,6 +539,7 @@ xfs_add_to_ioend(bh->b_private = NULL;wpc->ioend->io_size += bh->b_size;wpc->last_block = bh->b_blocknr;
+       xfs_start_buffer_writeback(bh);}

在该修改之后,buffer clean操作也放到了page lock之下。

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

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

相关文章

娱乐API:快速生成藏头诗、藏尾诗和藏中诗

引言 诗歌是中国传统文化的重要组成部分&#xff0c;其中藏头诗、藏尾诗和藏中诗因其独特的形式而备受喜爱。为了满足广大文学爱好者的需求&#xff0c;我们推出了一款娱乐API&#xff0c;支持快速生成藏头诗、藏尾诗和藏中诗。本文将详细介绍该API的功能、使用方法以及如何将…

vue中watch监听对象的某个属性

vue中watch监听对象的某个属性 第一种 &#xff1a; watch:{user.name(val){console.log(val)} }第二种&#xff08;不建议使用&#xff0c;对象有多个属性会一层一层遍历&#xff09;&#xff1a; watch:{user:{handle:function(newVal){console.log(newVal)},deep:true,//…

以达梦为数据库底座时部署的微服务页面报乱码,调整兼容模式

1.问题描述 部署微服务&#xff0c;文件、代码是延用的mysql类型的&#xff0c;部署前做了部分适配&#xff0c;但是在使用dm数据库进行安装的服务在页面上查询出的数据却都是乱码 2.查询官网&#xff0c;注意到一个参数COMPATIBLE_MODE兼容模式的配置 考虑是延用mysql&…

个人博客接入github issue风格的评论,utteranc,gitment

在做个人博客的时候&#xff0c;如果你需要评论功能&#xff0c;但是又不想构建用户体系和评论模块&#xff0c;那么可以直接使用github的issue提供的接口&#xff0c;对应的开源项目有utteranc和gitment&#xff0c;尤其是前者。 它们的原理是一样的&#xff1a;在博客文章下…

go 和java 编写方式的理解

1. go 推荐写流水账式的代码&#xff08;非贬义&#xff09;&#xff0c;自己管自己。java喜欢封装各种接口供外部调用&#xff0c;让别人来管自己。 2. 因为协程的存在&#xff0c; go的变量作用域聚集在方法内部&#xff0c;即函数不可重入&#xff0c;而java线程的限制&…

redis中的bigkey及读取优化

一、bigKey介绍 1、简介 在 Redis 中,Big Key(大键)指的是占用大量内存的单个键。通常,Redis 是一个高性能的内存数据库,但是当某些键变得非常大时,会带来性能上的影响。例如,大量的内存消耗、长时间的操作延迟,甚至可能导致 Redis 停止响应或崩溃。 通俗的来说,指…

NeurIPS 2024 有效投稿达 15,671 篇,数据集版块内容丰富

NeurIPS&#xff0c;全称 Neural Information Processing Systems Conference&#xff0c;是神经信息处理系统的年度学术会议。该会议始于 1987 年&#xff0c;当时名为 NIPS。随着人工智能领域的快速发展&#xff0c;其影响力逐渐扩大&#xff0c;被越来越多的研究者和企业关注…

服务器作业4

[rootlocalhost ~]# vim 11.sh #关闭防火墙 systemctl stop firewalld setenforce 0 #1.接收用户部署的服务名称 read -p "服务名称:(nginx)" server_name if [ $server_name ! nginx ];then echo "输入的不是nginx,脚本退出" exit 1 fi # 判断…

二,[ACTF2020 新生赛]Include1感谢 Y1ng 师傅供题。

进入靶场后&#xff0c;发现tips可以点击 点击后进入此页面 猜测此为文件包含漏洞,构造payload&#xff0c;并成功得到base64编码后的源码 详解payload&#xff1a; php://filter/readconvert.base64-encode/resourceflag.php 1.php://filter是PHP中的一个流封装协议&#xf…

APP聊天项目介绍

项目结构说明 res/layout目录&#xff1a;存放布局相关的 XML 文件&#xff0c;用于定义界面的外观&#xff0c;包含activity_main.xml&#xff08;主界面布局&#xff09;和message_item.xml&#xff08;聊天消息项布局&#xff09;。 res/drawable目录&#xff1a;存放一些…

vue3.0 根据富文本html页面生成压缩包(含视频在线地址、图片在线地址、前端截图、前端文档)

vue3.0生成压缩包&#xff08;含在线地址、前端截图、前端文档&#xff09; 需求描述效果开始下载插件包基本代码构造 点击下载按钮1.截图content元素&#xff0c;并转化为pdfcanvas putImageData、getImageDatagetImageData 获取指定矩形区域的像素信息putImageData 将这些数据…

图像小波去噪与总变分去噪详解与Python实现

目录 图像小波去噪与总变分去噪详解与实现1. 基础概念1.1 噪声类型及去噪问题定义1.2 小波去噪算法基础1.3 总变分去噪算法基础2. 小波去噪算法2.1 理论介绍2.2 Python实现及代码详解2.3 案例分析3. 总变分去噪算法3.1 理论介绍3.2 Python实现及代码详解3.3 案例分析4. 两种算法…

单细胞细胞通讯全流程分析教程,代做分析和辅导

0. 分析参数文件和细胞通讯的演示数据 0.1 细胞通讯分析总的参数文件&#xff0c;后面部分细胞通讯分析模块会用到 分析参数文件 参数文件名称&#xff1a;total_analysis_params_demo.xlsx &#xff0c;很多分析模块都是这个总的参数文件&#xff0c;我的这个总的参数文件如…

auto与decltype

auto: 1.定义&#xff1a; 在C中&#xff0c; auto 是一个类型说明符&#xff0c;它让编译器在编译阶段自动推导变量的类型&#xff0c;其类型取决于初始化表达式的类型。auto 在声明变量时使用&#xff0c;编译器会根据变量初始化表达式自动推断类型。 #include<iostrea…

[Code]R2U-Net中的眼部血管分割

DenseUnet.py import torch import torch.nn as nn import torch.nn.functional as F# 定义一个名为Single_level_densenet的类,继承自nn.Module,它构建了一个单层级的DenseNet结构 class Single_level_densenet(nn.Module):def __init__(self, filters, num_conv=4):super…

Java中的“封装“详解

封装&#xff08;Encapsulation&#xff09;是面向对象编程&#xff08;OOP&#xff09;的四大基本特性之一。它通过将数据和操作数据的方法绑定在一起&#xff0c;并隐藏对象的内部实现细节&#xff0c;只提供有限的访问接口来实现。这种机制不仅提高了代码的安全性&#xff0…

深度学习常用方法(一)

1. Dropout 的原理 Dropout 是一种防止神经网络过拟合&#xff08;学习得过于复杂&#xff0c;导致泛化能力差&#xff09;的方法。 原理&#xff1a;在每次训练时&#xff0c;随机“丢弃”一部分神经元&#xff08;即暂时让它们失效&#xff0c;设置为零&#xff09;&#x…

C++趣味编程:基于树莓派Pico的模拟沙漏-倾斜开关与LED的互动实现

沙漏,作为一种古老的计时工具,利用重力让沙子通过狭小通道,形成了计时效果。在现代,我们可以通过电子元件模拟沙漏的工作原理。本项目利用树莓派Pico、倾斜开关和LED,实现了一个电子沙漏。以下是项目的详细技术解析与C++代码实现。 一、项目概述 1. 项目目标 通过倾斜开关…

【Oracle】个人收集整理的Oracle常用SQL及命令

【建表】 create table emp( id number(12), name nvarchar2(20), primary key(id) ); 【充值一】 insert into emp select rownum,dbms_random.string(*,dbms_random.value(6,20)) from dual connect by level<101; 【充值二】 begin for i in 1..100 loop inser…

RAG (Retrieval Augmented Generation) 检索增强和生成

1 RAG技术简介 1.1 RAG技术概述 RAG&#xff08;Retrieval Augmented Generation&#xff09; 是一种结合了检索&#xff08;Retrieval&#xff09;和生成&#xff08;Generation&#xff09;的技术&#xff0c;旨在通过利用外部知识库来增强大型语言模型&#xff08;LLMs&am…