427 Star 1.5K Fork 1.6K

GVPopenEuler/kernel

 / 详情

多任务并发做da_write可能导致ext4_writepages陷入死循环

已完成
任务
创建于  
2023-06-09 17:10

问题根因:
ext4_da_write_begin 前台使用计数的方式预留block(仅预留数据块),writepages的时候申请块,那么如果用户写入5个数据块,预留计数为5,但实际writepages的时候可能由于需要额外的extent块,实际申请的物理块数量6会超出前台计数。

ext4通过两种方式保证da_write通过引用计数方式不会出现上述情况:

  1. ext4_da_write_begin -> ext4_nonda_switch:
    if (dirty_clusters && (free_clusters < 2 * dirty_clusters)) // 如果free块数小于2倍的dirty数量,dirty会基于前台写入的数据块数累加,在writepage申请到物理块后消减,同时free也再次之后被消减
    try_to_writeback_inodes_sb(sb, WB_REASON_FS_FREE_SPACE); // 等待回写
    if (2 * free_clusters < 3 * dirty_clusters ||
    free_clusters < (dirty_clusters + EXT4_FREECLUSTERS_WATERMARK)) { // 如果free < dirty + EXT4_FREECLUSTERS_WATERMARK[4 * (percpu_counter_batch * nr_cpu_ids)],改变da_write方式为普通write方式(前台就分配unwritten extent block)
    return 1;
    }
    return 0;

方式1主要是通过判断当前free减去dirty达不到阈值(阈值不小)时,触发回写更新准确的free,如果free确实不足改变da_write为write,前台分配块。

  1. ext4_da_write_begin -> ext4_da_get_block_prep -> ext4_da_map_blocks -> ext4_insert_delayed_block -> ext4_da_reserve_space -> ext4_claim_free_clusters -> ext4_has_free_clusters:
    resv_clusters = atomic64_read(&sbi->s_resv_clusters);
    if (free_clusters >= (nclusters + dirty_clusters + resv_clusters)) // ext4有预留块,不会让前台把所有的free计数消耗掉
    return 1;

方式2通过预留一定数量块,保证文件系统空闲块不会被理论耗尽。

但是,在并发写多个不同文件的场景时上述条件无法保证计数预留的块 > 实际分配的块,流程如下:

  1. 消耗文件系统free_clusters,直到free_clusters > 2 * sbi->s_resv_clusters,并且free_clusters > EXT4_FREECLUSTERS_WATERMARK
  2. 重新挂载,dirty_clusters = 0
  3. 并发起free block - sbi->s_resv_clusters个任务,每个任务写不同的文件
    由于任务对不同文件操作,可以并发进入ext4_nonda_switch,因此所有任务都是da_write
  4. 每个任务执行ext4_da_get_block_prep更新percpu_counter_add(&sbi->s_dirtyclusters_counter, 1),此时dirty_clusters > free_clusters
  5. 删除文件,释放新的块,free_clusters > 0
  6. ext4_writepages
    mpage_map_and_submit_extent
    mpage_map_one_extent // ret = ENOSPC
    ext4_map_blocks -> ext4_ext_map_blocks -> ext4_mb_new_blocks -> ext4_claim_free_clusters :
    if (free_clusters >= (nclusters + dirty_clusters)) // false,因为每个文件写触发了新的extent申请,开始预留的计数1 < 实际申请的块(数据块+extent块)
    if (err == -ENOSPC && ext4_count_free_clusters(sb)) // true
    // 陷入while (!mpd.scanned_until_end && wbc->nr_to_write > 0)循环

评论 (1)

chengzhihao 创建了任务 2年前

登录 后才可以发表评论

状态
负责人
项目
预计工期 (小时)
开始日期   -   截止日期
-
置顶选项
优先级
分支
参与者(2)
5329419 openeuler ci bot 1632792936 chengzhihao-czh549642238
C
1
https://gitee.com/openeuler/kernel.git
git@gitee.com:openeuler/kernel.git
openeuler
kernel
kernel

搜索帮助

371d5123 14472233 46e8bd33 14472233