前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一个ext4的Bug分析过程

一个ext4的Bug分析过程

作者头像
腾讯数据库技术
发布于 2018-06-05 09:16:03
发布于 2018-06-05 09:16:03
4.2K00
代码可运行
举报
运行总次数:0
代码可运行

1. 问题背景

日前,某客户反映他们的实例有一段时间内慢查询突增,监控页面上也显示那段时间内监控数据也没有上报,经查看系统日志,以下内容引起了我们的注意:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Mar 15 23:06:30 TENCENT64 kernel: BUG: soft lockup - CPU#2 stuck for 22s! [jbd2/md0-8:3661]
...
Mar 15 23:06:30 TENCENT64 kernel: CPU: 2 PID: 3661 Comm: jbd2/md0-8 Not tainted 3.10.104-1-tlinux2-0041.tl2 #1
...
Mar 15 23:06:30 TENCENT64 kernel: RIP: 0010:[<ffffffff819b9692>]  [<ffffffff819b9692>] _raw_spin_lock+0x22/0x30
...
Mar 15 23:06:30 TENCENT64 kernel: Call Trace:

Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff8125a4c0>] ext4_es_lru_add+0x30/0x70
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff8125a793>] ext4_es_insert_extent+0xc3/0x1b0
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff8125a4e8>] ? ext4_es_lru_add+0x58/0x70
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff8121ca19>] ext4_map_blocks+0x119/0x4f0
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff8121ce7b>] _ext4_get_block+0x8b/0x1b0
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff8121cfb6>] ext4_get_block+0x16/0x20
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff811a2aab>] generic_block_bmap+0x4b/0x70
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff8126d413>] ? jbd2_journal_file_buffer+0x43/0x70
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff8121c0f1>] ext4_bmap+0x71/0xe0
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff8118b08e>] bmap+0x1e/0x30
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff81275598>] jbd2_journal_bmap+0x28/0x80
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff81275662>] jbd2_journal_next_log_block+0x72/0x80
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff8126e138>] jbd2_journal_commit_transaction+0x7e8/0x1af0
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff81273749>] kjournald2+0xc9/0x260
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff8106b9f0>] ? wake_up_bit+0x30/0x30
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff81273680>] ? commit_timeout+0x10/0x10
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff8106aa9f>] kthread+0xcf/0xe0
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff8106a9d0>] ? insert_kthread_work+0x40/0x40
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff81a91c38>] ret_from_fork+0x58/0x90
Mar 15 23:06:30 TENCENT64 kernel: [<ffffffff8106a9d0>] ? insert_kthread_work+0x40/0x40

soft lockup(软死锁)通常被定义一种内核bug,也即让kernel在内核态循环超过20s,不给其他进程任何运行机会。CPU的watchdog守护进程(每CPU一个守护进程)在检测到这种情况后就会发送一个不可屏蔽中断(Non Maskable Interrupt, NMI)给所有的CPU核心,然后这些CPU会将它们正在运行的任务打印出来。所以,根据结合系统日志给出的信息,我们可以判断出,jbd2这一日志进程的callstack上的ext4_es_lru_add函数中使用的自旋锁持有时间过长导致了soft lockup这一问题。

2. 原理分析

首先我们看一下3.10.104的内核源码中ext4_es_lru_add函数的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void ext4_es_lru_add(struct inode *inode)                                                                                                 
{
    struct ext4_inode_info *ei = EXT4_I(inode);         // inode info
    struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);    // superblock info

    spin_lock(&sbi->s_es_lru_lock);
    if (list_empty(&ei->i_es_lru))
        list_add_tail(&ei->i_es_lru, &sbi->s_es_lru);
    else 
        list_move_tail(&ei->i_es_lru, &sbi->s_es_lru);
    spin_unlock(&sbi->s_es_lru_lock);
}

根据上一节我们的判断,sbi->s_es_lru_lock这一自旋锁可能自旋等待了超过20s都没有获得锁,那么是谁持有这把锁这么长时间都没有把锁释放呢,通过查阅资料ext4 extent tree LRU locking并结合代码分析,我们发现,当系统内存压力较大时,ext4文件系统中维持的一个有序LRU列表将会被用来从extent status tree中回收相当数量的extent,而s_es_lru_lock这把自旋锁被用来保护列表的遍历。看到这里,您也许会有疑问,我知道ext4中有一个extent tree来组织磁盘块,那么extent status tree是一个什么结构呢?extent status tree其实最初是为了优化延迟分配(delalloc)而引入的,所以有必要简要介绍一下ext4的延迟分配机制。

2.1 ext4延迟分配机制

ext4的延迟分配机制是将以前ext3中buffer I/O每次写操作涉及的磁盘块分配过程推迟到数据回写时进行,这一特性在其他文件系统例如XFS、ZFS和btrfs中也有。ext4中,当不启用延迟分配机制时,用户态程序的buffer write会使操作系统在page cache中分配相应的内存,然后等待系统定时触发回写任务或者用户调用fsync等系统调用强制将page cache中的内容刷到磁盘。为数据在磁盘上分配相应的块发生在用户态的数据拷贝到内核态的page cache这一过程中。而在使用延迟分配机制后,数据拷贝到page cache后,系统仅查询是否已经为这些页面分配过物理块,等到系统回写脏页或用户调用fsync等时才真正建立页面与物理磁盘块的映射,并且在块分配时尽量将逻辑上连续的页面组织成extent然后批量写入磁盘ext4延迟分配。这样文件系统就可以为这些属于同一个文件的数据分配尽量连续的磁盘空间,从而提高了文件的读写性能,并尽可能地减少磁盘碎片。

2.2 ext4块查找过程

回到第一节中的内核调用栈上来,关于jdb2日志的提交过程本文不做展开,但ext4_get_block函数作为VFS层get_block函数的适配实现,为我们提供物理块查找的功能,所以还是有必要简要分析一下: ext4_get_block调用链上的主要流程如下图所示:

ext4_get_block的函数定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int ext4_get_block(struct inode *inode, sector_t iblock, struct buffer_head *bh, int create)
{
    struct ext4_map_blocks map;
    ...

    map.m_lblk = iblock;
    map.m_len = bh->b_size >> inode->i_blkbits;
    ...
    ret = ext4_map_blocks(handle, inode, &map, flags);
    if (ret > 0) {
        map_bh(bh, inode->i_sb, map.m_pblk);
        bh->b_state = (bh->b_state & ~EXT4_MAP_FLAGS) | map.m_flags;
        bh->b_size = inode->i_sb->s_blocksize * map.m_len;
        ret = 0;
    }
    ...
}

这一函数的作用是,根据给定的inode以及申请的逻辑块号,得到与其对应的物理块号,然后与buffer_head建立映射,当找不到与逻辑块对应的物理块时,create参数用于判断是否执行块分配操作(延迟分配时create参数为0,即不分配物理块)。在这个函数中,首先初始化一个ext4_map_blocks的结构体,然后调用ext4_map_blocks函数实现块查找功能,结构体定义如下(各字段含义由注释可知):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct ext4_map_blocks {
    ext4_fsblk_t m_pblk;    //物理块号
    ext4_lblk_t m_lblk;     //逻辑块号
    unsigned int m_len;     //长度
    unsigned int m_flags;   //标记
};

ext4_map_blocks函数首先会调用ext4_es_lookup_extentextent status tree中查找一个extent(实际查找是先从status tree的cache中查找,查不到才遍历整个红黑树),然后将inode对应的ext4_inode_info添加到super block结构体中的LRU链表中。值得注意的是,每个打开的inode对应一个extent status tree。如果没找到extent,则会调用ext4_ext_map_blocks-->ext4_ext_find_extent从磁盘extent tree中使用二分法根据inode和逻辑块号找到对应的extent(关于extent tree的介绍参见我写过的一篇文章Linux删除文件过程解析,接下来调用ext4_find_delalloc_range判断在map->m_lblkmap->m_lblk + map->m_len-1这一个范围内有没有延迟分配的块,紧接着调用ext4_es_insert_extentextent status tree中插入一个节点,节点的结构也较为简单,可以理解为extent在内存中的结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct extent_status {                    
    struct rb_node rb_node;
    ext4_lblk_t es_lblk;    /* 第一个逻辑块*/
    ext4_lblk_t es_len;     /* extent长度(以块为单位) */
    ext4_fsblk_t es_pblk;   /* 第一个物理块 */
};

然后同样将inode对应的ext4_inode_info添加到super block结构中的LRU链表中。至此,块查找的整个过程分析完毕。

2.3 extent cache收缩机制

由于extent tree可能会有碎片,这样会使得extent status tree消耗大量内存,所以当内存压力过大时,系统会触发cache收缩机制从extent status tree中回收处于written/unwritten/hole状态的extent以节省内存(不回收处于delayed状态的extent)。3.10内核中的这一机制较为简单,主要逻辑如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static int ext4_es_shrink(struct shrinker *shrink, struct shrink_control *sc)
{
    ...
    INIT_LIST_HEAD(&scanned);

    spin_lock(&sbi->s_es_lru_lock);
    list_for_each_safe(cur, tmp, &sbi->s_es_lru) {
        list_move_tail(cur, &scanned);

        ei = list_entry(cur, struct ext4_inode_info, i_es_lru);

        read_lock(&ei->i_es_lock);
        if (ei->i_es_lru_nr == 0) {
            read_unlock(&ei->i_es_lock);
            continue;
        }
        read_unlock(&ei->i_es_lock);

        write_lock(&ei->i_es_lock);
        ret = __es_try_to_reclaim_extents(ei, nr_to_scan);
        write_unlock(&ei->i_es_lock);

        nr_shrunk += ret;
        nr_to_scan -= ret;
        if (nr_to_scan == 0)
            break;
    }                      
    list_splice_tail(&scanned, &sbi->s_es_lru);
    spin_unlock(&sbi->s_es_lru_lock);
    ...
}

从2.2小节中我们可以知道,每当访问(查找或插入)一个inode对应的extent status tree时,都会将该inode插入到超级块LRU链表的尾部,所以越是靠近尾部的inode越常被访问。另外,每个ext4_inode_info结构中会有一个i_es_lru_nr字段来记录处于written/unwritten状态的extent数量。基于以上两点,ext4_es_shrink函数就不难理解了,通过遍历整个LRU链表,从每个inode对应的extent status tree中回收extent,直到遍历完整个链表或回收的extent总数达到nr_to_scan为止(nr_to_scan与linux的内存管理相关,这里不展开讨论)。为了保护整个LRU链表,整个遍历过程中需要加一把LRU自旋锁s_es_lru_lock。至此,文章开头出现的soft lockup问题就不难理解了,如果内存压力小,shrinker不会被频繁触发,锁竞争不会很激烈;相反,如果内存压力很大,shrinker就会被频繁触发,这样自旋锁会被shrinker长时间持有,那么其他进程想要加锁就不得不自旋等待更长的时间。如果自旋等待的时间超过20s,那么soft lockup这种软死锁问题就可能会发生了。

3. 社区解决方法

既然遍历整个LRU链表会导致锁占用时间过长,那么就得想办法加快遍历过程。一种思路就是遍历过程中只从某些inode对应的extent status tree中回收extent,社区就是这么解决这个问题的。在ext4_inode_info结构体中加入一个i_touch_when字段来记录inode最近一次被访问的时间。当我们需要从extent status tree中回收extent时,调用list_sort函数按照inode最近被访问时间的大小来排序。然后在ext4_es_stats结构体中,加入一个es_stats_last_sorted字段来表示LRU链表最近一次排序的时间。这样当遍历链表时,如果某个inode的i_touch_when字段大于es_stats_last_sorted的,我们就可以跳过这一inode,然后将其加入到链表尾部;否则,将会从inode对应extent status tree中回收extent。如果链表头的inode其i_touch_when字段大于es_stats_last_sorted时,这时链表将需要重新排序。这里就不再详细列出相关的代码实现,如果感兴趣,可以参考3.18内核源码extents_status.c。

4. 总结

本文通过对Linux ext4文件系统的一个bug进行分析,探讨了ext4的延迟分配机制、块查找过程及extent cache收缩机制,并简要介绍了社区对这一个bug的修复方案。从整个分析过程中可以得到的两个启发是:

  • 当你知道某个地方可能成为性能瓶颈的时候,不妨及时进行优化。像本文讨论的LRU自旋锁可能会导致锁冲突的问题,内核的相关开发者在提出extent status tree时就有过疑虑extent status tree,但是可能是由于测试不充分没有触发这一bug,最终没有处理;
  • 当你知道你需要保护的代码段很快就可以被执行完,那么用自旋锁完全没问题,但当你预料到代码段可能需要较长时间执行时,这时候要不就用其他内核原语(如mutex)让等待的任务可以睡眠,要不就重新组织逻辑,将那些真正critical的部分用自旋锁保护起来,再者就是看有没有办法对代码进行优化,尽量减小CPU的消耗。

5. 参考文献


腾讯数据库技术团队维护MySQL内核分支TXSQL,100%兼容原生MySQL版本,对内支持微信红包,彩票等集团内部业务,对外为腾讯云CDB for MySQL提供内核版本。腾讯数据库技术团队专注于增强MySQL内核功能,提升数据库性能,保证系统稳定性并解决用户在生产过程中遇到的问题。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-05-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯数据库技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Fast commits for ext4
Linux 5.10 版本中包含了一个有望显著提高 ext4 文件系统性能的改动,人们称它为 "fast commits (快速提交)",加入了一种新的、更轻量级的日志方法。让我们来看一下这个功能具体是如何工作的、谁能从中受益、以及什么场景下适用。
用户9732312
2022/12/05
1.3K0
[操作系统] Ext系列文件系统
其实硬盘是典型的“块”设备,操作系统读取硬盘数据的时候,其实是不会⼀个个扇区地读取,这样效率太低,⽽是⼀次性连续读取多个扇区,即⼀次性读取⼀个块(block)。
DevKevin
2025/02/23
780
[操作系统] Ext系列文件系统
性能分析之又见jbd2引起IO高
之前遇到过jbd2引起IO高的问题,直接关掉了日志的功能解决的。写了一个文章,但写的不够细。最近又见类似问题,这里重新整理下对jbd2的内容。
高楼Zee
2019/07/17
23K0
centos7 cgroup oom触发访问ext4文件系统卡死
centos7 3.10.0-1160.62.1.el7.x86_64内核版本已修复该问题,CentOS7受影响内核版本 3.10.0-862.el7 - 3.10.0-1160.59.1.el7
cdh
2022/04/15
2.8K3
一个mutex引发的血案——3.10内核 mount -a hung住问题看到的ext4 lazyinit那些坑
接到一个问题,反馈D3.16XLARGE256(24块3T本地sata数据盘)机型,3.10内核,在初始化mkfs后mount -a,概率触发mount hung住,需要很长时间(半天左右)才能完成所有盘的挂载,需要排查根因。 本文记录复现场景分析的全过程,发现罪魁祸首源于一个mutex锁,而在高版本内核中对该锁的使用进行了优化,因此TencentOS 5.4内核可以根治这个问题。
johnazhang
2022/11/02
1.6K0
Linux内核分析:页回收导致的cpu load瞬间飙高的问题分析与思考
本文一是为了讨论在Linux系统出现问题时我们能够借助哪些工具去协助分析,二是讨论出现问题时大致的可能点以及思路,三是希望能给应用层开发团队介绍一些Linux内核机制从而选择更合适的使用策略。
刘盼
2024/01/18
7300
Linux内核分析:页回收导致的cpu load瞬间飙高的问题分析与思考
ext3,ext4,xfs和btrfs文件系统性能对比
应为原文:http://www.ilsistemista.net/index.php/linux-a-unix/6-linux-filesystems-benchmarked-ext3-vs-ext4
Albert陈凯
2018/04/04
9.2K0
ext3,ext4,xfs和btrfs文件系统性能对比
Linux中文件系统注册及mount过程分析5
这些介绍了从读超级快,获得磁盘的块的属性,然后进行了sops注册,然后进入ext4_iget进行了文件操作,目录操作,链接操作等函数的注册,比如读文件
用户3765803
2019/03/05
2K0
由 OOM 引发的 ext4 文件系统卡死
注:本问题影响 3.10.0-862.el7.centos 及之后的 CentOS 7 版本内核,目前问题还未被修复。
米开朗基杨
2021/01/29
4.9K0
由 OOM 引发的 ext4 文件系统卡死
从应用到内核查接口超时(下)
接上文 从应用到内核查接口超时(中),查到是因为 journal 导致 write 系统调用被阻塞进而导致超时后,总感觉证据还不够充分,没有一个完美的交待。而且 leader 还想着让我把问题排查过程分享给同事们,这让我更加不安,担心搞错了方向。
枕边书
2019/01/03
1.4K0
深入浅出ext4文件系统之数据块组织和日志
ext4中inode数据块存储形式 ext4目前在kernel中的实现有两种分别是基于block和基于extent。基于block的方式存储文件数据块的元数据有direct block(直接数据块)、indirect block(一级间接数据块,pointer to direct blocks)、double indirect block(二级间接数据块,pointer to indirect blocks)、triple indirect(三级间接数据块,pointer to double ind
用户4700054
2023/02/26
1.8K0
深入浅出ext4文件系统之数据块组织和日志
聊聊ext4文件create和truncat实现
下面是ext4创建文件的实现,第一步是经由vfs层的vfs_create函数,最后进入实际文件系统的ext4_create来创建文件,文件的创建核心过程基本分为2步,第一步是新文件的inode申请,第二步是读取新文件的父目录的inode,在这个inode对应的数据块添加新文件的目录项,这个过程是采用事务的方式进行。
用户4700054
2022/08/17
1.3K0
聊聊ext4文件create和truncat实现
Linux删除文件过程解析
1. 概述 ---- 当我们执行rm命令删除一个文件的时候,在操作系统底层究竟会发生些什么事情呢,带着这个疑问,我们在Linux-3.10.104内核下对ext4文件系统下的rm操作进行分析。rm命令本身比较简单,但其在内核底层涉及到VFS操作、ext4块管理以及日志管理等诸多细节。 2. 源码分析 ---- rm命令是GNU coreutils里的一个命令,在对一个文件进行删除时,它实际上调用了Linux的unlink系统调用,unlink系统调用在内核中的定义如下: SYSCALL_DEFINE1
腾讯数据库技术
2018/06/05
15.2K0
太难了,一个接口超时问题,从应用排查到内核。
回望整个过年期间真的是躺的平平的,每天学习的时间和平时比起来差的不是一星半点。今天就复工了,也要收心了。我这个人有一个比较牛逼的能力就是状态调整特别快,只需要往工位上一坐下,我就能进入复工状态了。
why技术
2022/02/17
1.3K0
太难了,一个接口超时问题,从应用排查到内核。
得物SRE K8s 故障诊断:从 CPU 高负载到挂载泄露根源揭示
现代软件部署中,容器技术已成为不可或缺的一环,在云计算和微服务架构中发挥着核心作用。随着容器化应用的普及,确保容器环境的可靠性成为了一个至关重要的任务。这就是容器SRE(Site Reliability Engineering,站点可靠性工程)的职责所在。容器SRE工程师不仅要保证系统的高可用性,还需要优化运行效率,确保系统在各种压力和突发情况下的韧性。
得物技术
2024/06/11
2630
得物SRE K8s 故障诊断:从 CPU 高负载到挂载泄露根源揭示
文件系统专栏 | 之ext4文件系统结构
上次讲了VFS层,这次说说文件系统层,文件系统层将不同的文件系统实现了VFS的这些函数,通过指针注册到VFS里面。所以,用户的操作通过VFS转到各种文件系统,linux用到最多的是ext4文件系统,我们就说这个吧。EXT4是第四代扩展文件系统(英语:Fourth extended filesystem,缩写为 ext4)是Linux系统下的日志文件系统,是ext2和ext3文件系统的后继版本。 ext4文件系统布局 一个Ext4文件系统被分成一系列块组。为减少磁盘碎片产生的性能瓶颈,块分配器尽量保持每个文件
刘盼
2022/08/29
3.6K0
文件系统专栏 | 之ext4文件系统结构
第3阶段——内核启动分析之start_kernel初始化函数(5)
诺谦
2018/01/03
1.8K0
磁盘文件系统三
VFS采用了面向对象的设计思路,将一系列概念抽象出来作为对象而存在,它们包含数据的同时也包含了操作这些数据的方法。当然,这些对象都只能用数据结构来表示,而不可能超出C语言的范畴,不过即使在C++里面数据结构和类的区别也仅仅在于类的成员默认私有,数据结构的成员默认公有。VFS主要有如下4个对象类型。
没有故事的陈师傅
2021/06/24
9050
磁盘文件系统三
云盘文件系统比较:Ext4、XFS和Btrfs
在上一篇云硬盘性能分析的教程中,为大家介绍了如何评测云硬盘的读写性能。但是,我们使用硬盘,从来不是直接读写裸设备,而是通过文件系统来管理和访问硬盘上地文件。不少朋友询问,文件系统该如何对比,又该如何选择呢?
溪歪歪
2021/01/03
13.1K6
云盘文件系统比较:Ext4、XFS和Btrfs
从磁盘存储维度观测ext4文件系统
inode是文件系统中每个文件的唯一标识,映射IO Block到磁盘扇区的对应关系。inode一般存储了文件的acess/modify/create的时间、访问权限、以及最重要的这个文件包含了哪些Blocks.这里需要注意的是ext4系统中当删除文件的时候,这个文件的inode是可以被回收然后被新文件重用。
用户4700054
2023/02/26
1.2K0
从磁盘存储维度观测ext4文件系统
推荐阅读
相关推荐
Fast commits for ext4
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验