Skip to content

Feat: improve async filesystem - #59

Merged
muou000 merged 7 commits into
mainfrom
feat-async-fs
Jul 31, 2026
Merged

muou000 merged 7 commits into
mainfrom
feat-async-fs

Conversation

@muou000

@muou000 muou000 commented Jul 31, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • 新功能

    • 块设备支持并发异步读写、刷新及可取消操作,提升 I/O 灵活性。
    • 文件系统新增异步刷新、批量写回和直接 I/O,支持更高效的缓存管理。
    • Ext4 增强目录查找缓存、并发写入协调及大范围 I/O 处理。
    • 新增文件预读优化,改善连续访问性能。
  • 问题修复

    • 改进 VirtIO 刷新、队列满、丢失中断及设备错误的处理。
    • 优化目录并发操作、缓存失效和文件元数据更新的一致性。
    • 系统调用及页面故障处理期间的中断状态管理更加可靠。

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@muou000, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 44 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d08f5348-cecf-4c8c-ae5b-5c6a7b590095

📥 Commits

Reviewing files that changed from the base of the PR and between 79946bb and 83d3c15.

📒 Files selected for processing (1)
  • arceos/modules/axfs/src/fs/ext4/inode.rs
📝 Walkthrough

Walkthrough

本次改动将块驱动、VirtIO、共享磁盘、Ext4、文件缓存和目录缓存改为异步并发流程。系统调用和用户态 trap 慢路径新增中断状态守卫。文件系统刷新统一使用异步总刷新入口。

Changes

异步存储栈

Layer / File(s) Summary
异步块驱动契约与 VirtIO 请求
crates/axdriver_block/..., crates/virtio-drivers/..., arceos/modules/axdriver/src/prelude.rs, crates/axdriver_virtio/...
异步读写改用共享引用。接口新增异步刷新、直接 DMA 读缓冲区和非阻塞 VirtIO 刷新。
VirtIO 异步状态机
arceos/modules/axdriver/src/virtio.rs
块设备改用挂起请求、完成队列、DMA 缓冲区、请求唤醒和取消回收状态。同步接口通过 Future 等待。
共享磁盘与异步刷新
arceos/modules/axfs/src/disk.rs, arceos/modules/axfs/src/fs/loop_dev.rs, arceos/modules/axfs/src/lib.rs, arceos/modules/axfs/Cargo.toml
共享磁盘和 loop 设备直接转发异步 Future。磁盘和文件系统刷新使用写作用域、异步互斥和并发批量流程。
Ext4 条带缓存与批量写回
arceos/modules/axfs/src/fs/ext4/fs.rs, arceos/modules/axfs/src/fs/ext4/mod.rs
Ext4 使用条带锁、写入代次、脏块 owner 和异步批量写回。大范围对齐 I/O 可绕过缓存。
异步文件缓存与直接读缓冲
arceos/modules/axfs/src/highlevel/file.rs
文件缓存使用异步锁、共享页面帧、拥有式直接读缓冲和并发脏页写回。
目录缓存与 inode 写作用域
arceos/modules/axfs/src/fs/ext4/inode.rs, crates/axfs-ng-vfs/src/node/dir.rs
目录缓存增加代际校验和 mutation lock。inode 操作使用写作用域。目录快照支持索引、晋升和失效测试。
文件故障预读窗口
arceos/modules/axmm/src/backend/file.rs
非连续故障首次预读最多四页。页映射位掩码扩展为 u16。

中断与系统调用

Layer / File(s) Summary
Trap 中断状态守卫
pulse_core/src/trap.rs, pulse_syscalls/src/handler.rs
用户态 page fault 和系统调用分发期间临时启用中断,并在作用域结束时恢复状态。
系统调用文件系统刷新
pulse_syscalls/src/impls/fs/io.rs
sys_sync 改为调用全局文件系统刷新,不再逐个刷新文件对象。

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Poem

小兔轻敲 Future,
DMA 缓冲稳稳住。
VirtIO 完成队列响,
Ext4 异步写回忙。
中断守卫恢复快,
全盘刷新不等待。

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了将文件系统改进为异步流程这一主要变更,内容清晰且简洁。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (9)
pulse_core/src/trap.rs (1)

14-25: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

禁止将 TrapIrqEnableGuard 跨 CPU 移动。

Drop::drop 会修改当前 CPU 的中断状态,但当前只保存 bool,会在没有 CPU 绑定约束时自动实现 Send/Sync。加入 PhantomData<*mut ()>,阻止守护值在其他 CPU 析构导致中断状态与创建 CPU 不一致。

建议修改
 pub struct TrapIrqEnableGuard {
     restore_disabled: bool,
+    _not_send_or_sync: core::marker::PhantomData<*mut ()>,
 }
 
-        Self { restore_disabled }
+        Self {
+            restore_disabled,
+            _not_send_or_sync: core::marker::PhantomData,
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pulse_core/src/trap.rs` around lines 14 - 25, Update TrapIrqEnableGuard to
include PhantomData<*mut ()> state, preventing automatic Send/Sync
implementations and ensuring the guard cannot move across CPUs before Drop::drop
restores interrupt state. Initialize the marker in TrapIrqEnableGuard::new while
preserving the existing restore_disabled behavior.
crates/axdriver_block/src/lib.rs (1)

142-148: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

注册表在读路径上做全局锁 + 线性扫描,请改用有序索引。

claim_owned_read_buffer 在每次块读取时获取全局 OWNED_READ_BUFFERS 自旋锁,并线性遍历 Vec 查找包含目标范围的条目。register_owned_read_buffer 的重叠检查同样是 O(n)。页缓存可能注册大量页帧,此时每次直接读都变成 O(n),并且所有 CPU 在同一把自旋锁上串行化,这与本 PR 的并发目标冲突。

建议用按起始地址排序的结构(例如 BTreeMap<usize, Arc<OwnedReadBufferRange>>)替换 Vec。查找时用 range(..=start).next_back() 定位候选条目,重叠检查同样只需比较前后相邻条目。

♻️ 建议的索引结构调整方向
-static OWNED_READ_BUFFERS: Mutex<Vec<OwnedReadBufferEntry>> = Mutex::new(Vec::new());
+// key = range.start
+static OWNED_READ_BUFFERS: Mutex<BTreeMap<usize, OwnedReadBufferEntry>> =
+    Mutex::new(BTreeMap::new());

claim_owned_read_buffer 中的查找改为:

let (_, entry) = buffers.range(..=start).next_back()?;
if entry.range.start > start || end > entry.range.end {
    return None;
}

Also applies to: 172-199

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/axdriver_block/src/lib.rs` around lines 142 - 148, 将
OWNED_READ_BUFFERS 从 Vec 改为按起始地址索引的有序结构(如 BTreeMap<usize,
Arc<OwnedReadBufferRange>>),并更新 register_owned_read_buffer 与
claim_owned_read_buffer 的访问逻辑。使用 range(..=start).next_back()
定位候选范围,注册时仅检查相邻前后条目的重叠,避免全量线性扫描,同时保持现有资源冲突与范围匹配行为。
arceos/modules/axdriver/src/virtio.rs (1)

812-824: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

未知或越界 token 会让已用环永久停滞。

complete_one 在 peek_used 返回的 token 没有对应挂起请求时只记录日志并返回 None。该已用项没有被弹出,因此它永远留在环头。之后每次 drain_completions(IRQ、定时器、poll)都会重新看到同一个 token,重复打印错误日志,并且所有真正的完成项都排在它后面,无法被认领。结果是全部块 I/O 静默挂死,同时日志被刷满。

这两个分支表示驱动状态已损坏。请把它们变成显式的致命处理:记录一次日志后进入永久错误状态,让后续请求快速失败,或者直接 panic!,与本文件其他不变量(例如第 1234 行的 virtio-blk reused an in-flight token)保持一致。

🔒️ 建议的处理方向
                 let Some(slot) = self.pending.get_mut(token as usize) else {
-                    axlog::error!("virtio-blk completed out-of-range token {}", token);
-                    return None;
+                    panic!("virtio-blk completed out-of-range token {}", token);
                 };
                 let Some((request_id, mut request)) = slot.take() else {
-                    axlog::error!("virtio-blk completed unknown token {}", token);
-                    return None;
+                    panic!("virtio-blk completed unknown token {}", token);
                 };

如果不能接受 panic,请改为置位一个 fatal: AtomicBool,并让 poll 在该位被置位时立即返回 Err(DevError::BadState),避免无限重扫环头。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arceos/modules/axdriver/src/virtio.rs` around lines 812 - 824, Update
complete_one so the out-of-range and unknown-token branches do not return
normally while leaving the used-ring entry at its head. After logging once,
transition the device into an unrecoverable error state that makes subsequent
polling/request handling fail fast, or use panic! consistently with the existing
“virtio-blk reused an in-flight token” invariant handling; ensure later
drain_completions calls cannot repeatedly rescan the corrupted token.
arceos/modules/axfs/src/highlevel/file.rs (1)

2192-2203: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

大范围写入会按页数构建条带索引,开销与写入长度成正比。

page_fill_lock_indices 对每个页号都执行一次哈希并推入 Vec::with_capacity(page_count),随后排序去重。条带总数固定为 PAGE_ACCESS_LOCK_STRIPES(256)。一次数百 MB 的写入会产生数十万次迭代和一次大数组分配,而结果最多只有 256 个索引。请在 page_count >= PAGE_ACCESS_LOCK_STRIPES 时直接使用全部条带索引。

⚡ 建议的优化
// CachedFileShared::page_fill_lock_indices
fn page_fill_lock_indices(&self, pn: u32, page_count: usize) -> VfsResult<Vec<usize>> {
    if page_count >= PAGE_ACCESS_LOCK_STRIPES {
        return Ok((0..PAGE_ACCESS_LOCK_STRIPES).collect());
    }
    // ...现有逐页路径
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arceos/modules/axfs/src/highlevel/file.rs` around lines 2192 - 2203, 在
CachedFileShared::page_fill_lock_indices 中增加 page_count >=
PAGE_ACCESS_LOCK_STRIPES 的快速路径,直接返回 0 到 PAGE_ACCESS_LOCK_STRIPES
的全部条带索引,避免逐页哈希、排序去重和大 Vec 分配;保留较小 page_count 的现有逐页处理逻辑不变。
arceos/modules/axmm/src/backend/file.rs (1)

846-852: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

当前测试无法区分冷启动窗口与连续窗口。

readahead_resets_after_nonsequential_fault 全部使用 max_pages = 4。此时 max_pages.min(COLD_FILE_FAULT_AROUND_PAGES) 与 max_pages 结果相同,所以第 107 行的冷启动上限不会被验证。请增加一个 max_pages > COLD_FILE_FAULT_AROUND_PAGES 的用例。

💚 建议的测试补充
     #[test]
     fn readahead_resets_after_nonsequential_fault() {
         let mut state = FileReadAheadState::default();
         assert_eq!(state.plan(3, 4), 4);
         assert_eq!(state.plan(20, 4), 4);
         assert_eq!(state.plan(24, 4), 4);
     }
+
+    #[test]
+    fn cold_fault_is_capped_below_the_sequential_window() {
+        let mut state = FileReadAheadState::default();
+        assert_eq!(state.plan(3, 8), COLD_FILE_FAULT_AROUND_PAGES);
+        assert_eq!(state.plan(3 + COLD_FILE_FAULT_AROUND_PAGES as u32, 8), 8);
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arceos/modules/axmm/src/backend/file.rs` around lines 846 - 852, Update the
test readahead_resets_after_nonsequential_fault to include a case where
max_pages exceeds COLD_FILE_FAULT_AROUND_PAGES, and assert the initial
cold-start plan is capped at the cold-start limit while subsequent sequential
plans can use the larger max_pages window.
arceos/modules/axfs/src/disk.rs (1)

180-192: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

DiskWriteScope::run 对同一 scope 的重入没有防护。

run 接收 &self,因此同一个 DiskWriteScope 可以被并发或嵌套地 run。enter_write_scope 在 ext4 实现中把 scope_id 压入按 task 的栈,leave_write_scope 的 debug_assert_eq!(position, stack.len().checked_sub(1)) 假设严格的后进先出顺序(见 arceos/modules/axfs/src/fs/ext4/mod.rs 第 267-282 行)。若两个 future 交错轮询,该断言会在 debug 构建中触发。

建议把 run 改为 &mut self,或在文档注释中写明“同一 scope 一次只能运行一个 future”。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arceos/modules/axfs/src/disk.rs` around lines 180 - 192, Update
DiskWriteScope::run to take &mut self, preventing concurrent or nested execution
of the same scope and preserving the required LIFO ordering of enter_write_scope
and leave_write_scope. Propagate any necessary mutability changes to callers
while leaving the existing polling and guard behavior unchanged.
arceos/modules/axfs/src/fs/ext4/mod.rs (2)

126-136: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

ReadRangeGuards::len 中的 let _ = &**guard; 无作用,请删除。

该语句只做一次解引用,不影响返回值 1。它增加读者的理解负担。如果目的是抑制未使用字段的警告,请改用 Self::One(_) => 1。

♻️ 建议的简化
     fn len(&self) -> usize {
         match self {
-            Self::One(guard) => {
-                let _ = &**guard;
-                1
-            }
+            Self::One(_) => 1,
             Self::Many(guards) => guards.len(),
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arceos/modules/axfs/src/fs/ext4/mod.rs` around lines 126 - 136, 简化
ReadRangeGuards::len 中的 Self::One 分支,删除无作用的 let _ = &**guard;,并将未使用的 guard
绑定改为通配符模式,同时保持返回值为 1 不变。

1102-1107: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

set_block_size 直接清空缓存,会丢弃脏块。

self.block_cache.lock().clear() 丢弃全部缓存项,包含 dirty == true 的块。它也不清理 flushing_evicted 与 dirty_owners,这两个结构随后会保留指向旧块大小的偏移。当前唯一调用点是 arceos/modules/axfs/src/fs/ext4/fs.rs 第 63 行的挂载流程,那时缓存内只有干净的超级块数据,因此不会立刻丢数据。

请增加防护,避免后续调用引入静默数据丢失。

🛡️ 建议增加断言与状态清理
     pub async fn set_block_size(&self, size: usize) {
         let _io_guards = self.lock_all_write_stripes().await;
         self.block_size
             .store(size, core::sync::atomic::Ordering::Relaxed);
-        self.block_cache.lock().clear();
+        let mut cache = self.block_cache.lock();
+        debug_assert!(
+            cache.iter().all(|(_, block)| !block.dirty),
+            "set_block_size must not discard dirty blocks"
+        );
+        cache.clear();
+        drop(cache);
+        debug_assert!(self.flushing_evicted.lock().is_empty());
+        self.dirty_owners.lock().clear();
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arceos/modules/axfs/src/fs/ext4/mod.rs` around lines 1102 - 1107, 更新 Ext4 缓存的
set_block_size:在修改 block_size 并清空 block_cache 前,断言不存在脏块,防止静默丢失未写回数据;同时清理
flushing_evicted 和 dirty_owners 中与旧块大小相关的状态,确保缓存及其辅助索引不会残留旧偏移信息。
arceos/modules/axfs/src/fs/ext4/inode.rs (1)

427-447: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

lock_directory_pair 用 ino 判等,其他路径用指针判等,标准不一致。

第 434 行用 self.ino == other.ino 判定同一目录。link(第 1054 行)与 unlink(第 1135 行)用 core::ptr::eq。Inode::new 通过 active_inodes 复用实例,但在 weak 引用刚失效的竞态窗口内,同一 ino 可能对应两个不同的 Inode 对象。此时 lock_directory_pair 只取一把锁,另一个对象的 mutation_lock 未被持有。

请统一判等标准,或在注释中写明 ino 到实例的唯一性保证。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arceos/modules/axfs/src/fs/ext4/inode.rs` around lines 427 - 447, 统一
lock_directory_pair 与 link、unlink 的实例判等标准,使用 core::ptr::eq 判断 self 和 other 是否为同一
Inode;仅在指针相同的情况下只获取一把锁,否则按稳定顺序获取两个 mutation_lock,避免不同对象但相同 ino 时漏锁。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@arceos/modules/axfs/src/disk.rs`:
- Around line 345-350: 更新 read_partial 及同类的读写/刷新路径(包括 read_block、write_block
相关代码),避免在持有 inner 与 dev 自旋锁时执行可能阻塞的块 I/O:先克隆设备句柄并提取所需 block_id
与缓冲区到局部变量,释放两把锁后调用 read_block/write_block,再按现有逻辑写回必要状态。确保第
376-379、391-395、426-429 行对应路径同样遵循先释放锁、后执行 I/O 的顺序。

In `@arceos/modules/axfs/src/fs/ext4/fs.rs`:
- Around line 120-125: 更新 `flush_inode`,不要仅依赖按 owner 选择脏块的
`disk_flusher.flush_owner`;应改用或补充按 inode 完整匹配并写回所有脏块的刷新路径,覆盖无 owner 或在
`write_scope` 外通过 `write_offset` 产生的脏块。保持异步刷新流程及现有 `VfsError::Io` 错误映射不变。

In `@arceos/modules/axfs/src/fs/ext4/inode.rs`:
- Around line 1208-1209: 统一 Inode::mutation_lock 的加锁顺序以消除 rename、link、unlink
间的死锁:在 arceos/modules/axfs/src/fs/ext4/inode.rs:1208-1209,将目录锁与子节点锁合并、去重后按 ino
升序通过统一辅助函数一次性获取,替换 lock_directory_pair;在
arceos/modules/axfs/src/fs/ext4/inode.rs:1046-1058 和 :1134-1140,让对应操作同样使用该辅助函数按
ino 升序获取 self 与 child,移除“目录先、子节点后”的固定顺序。
- Around line 1134-1140: Update the unlink flow around Inode::new and its
active-reference check so the temporary child instance is not counted as an
external active inode, allowing immediate deletion when no other references
exist. Apply the same correction to the dst_node instance created by rename,
either by avoiding active_inodes registration for these temporary instances or
excluding the exact newly created instance during still_active evaluation.
- Around line 884-902: 在包含 snapshot 的两条 lookup 缓存发布路径中,先于
self.dir_snapshot(fs).await? 或使用已有 snapshot 的处理记录 lookup_generation,并将该预先记录的代际传给
publish_lookup;不要在读取 snapshot 后再调用 self.dir_cache.lookup_generation()。更新涉及
dir_cache 分支及 note_uncached_lookup 分支,保留现有 entry_from_cached_lookup 行为。

In `@arceos/modules/axfs/src/highlevel/file.rs`:
- Around line 674-692: 更新 PageCacheFrame::drop
的引用计数处理:不要将用户映射持有的引用计入缓存页释放逻辑,也不要对仅由映射持有的 ref_count == 1 调用 dec_ref。仅在
PageCacheFrame 明确持有独立引用时递减并在计数归零后释放;只要仍有用户映射或其他引用,就保留页面,避免触发 FrameTable::dec_ref
的非法减计数。
- Around line 1080-1105: Update the batch assembly loop around
submit_writeback_batch so page-cache lookup or data extraction errors are stored
in first_error instead of returned immediately. Stop submitting additional
batches after the first error, then continue polling and draining all existing
pending futures so in-flight writebacks complete and their page state is handled
normally.

---

Nitpick comments:
In `@arceos/modules/axdriver/src/virtio.rs`:
- Around line 812-824: Update complete_one so the out-of-range and unknown-token
branches do not return normally while leaving the used-ring entry at its head.
After logging once, transition the device into an unrecoverable error state that
makes subsequent polling/request handling fail fast, or use panic! consistently
with the existing “virtio-blk reused an in-flight token” invariant handling;
ensure later drain_completions calls cannot repeatedly rescan the corrupted
token.

In `@arceos/modules/axfs/src/disk.rs`:
- Around line 180-192: Update DiskWriteScope::run to take &mut self, preventing
concurrent or nested execution of the same scope and preserving the required
LIFO ordering of enter_write_scope and leave_write_scope. Propagate any
necessary mutability changes to callers while leaving the existing polling and
guard behavior unchanged.

In `@arceos/modules/axfs/src/fs/ext4/inode.rs`:
- Around line 427-447: 统一 lock_directory_pair 与 link、unlink 的实例判等标准,使用
core::ptr::eq 判断 self 和 other 是否为同一 Inode;仅在指针相同的情况下只获取一把锁,否则按稳定顺序获取两个
mutation_lock,避免不同对象但相同 ino 时漏锁。

In `@arceos/modules/axfs/src/fs/ext4/mod.rs`:
- Around line 126-136: 简化 ReadRangeGuards::len 中的 Self::One 分支,删除无作用的 let _ =
&**guard;,并将未使用的 guard 绑定改为通配符模式,同时保持返回值为 1 不变。
- Around line 1102-1107: 更新 Ext4 缓存的 set_block_size:在修改 block_size 并清空
block_cache 前,断言不存在脏块,防止静默丢失未写回数据;同时清理 flushing_evicted 和 dirty_owners
中与旧块大小相关的状态,确保缓存及其辅助索引不会残留旧偏移信息。

In `@arceos/modules/axfs/src/highlevel/file.rs`:
- Around line 2192-2203: 在 CachedFileShared::page_fill_lock_indices 中增加
page_count >= PAGE_ACCESS_LOCK_STRIPES 的快速路径,直接返回 0 到 PAGE_ACCESS_LOCK_STRIPES
的全部条带索引,避免逐页哈希、排序去重和大 Vec 分配;保留较小 page_count 的现有逐页处理逻辑不变。

In `@arceos/modules/axmm/src/backend/file.rs`:
- Around line 846-852: Update the test
readahead_resets_after_nonsequential_fault to include a case where max_pages
exceeds COLD_FILE_FAULT_AROUND_PAGES, and assert the initial cold-start plan is
capped at the cold-start limit while subsequent sequential plans can use the
larger max_pages window.

In `@crates/axdriver_block/src/lib.rs`:
- Around line 142-148: 将 OWNED_READ_BUFFERS 从 Vec 改为按起始地址索引的有序结构(如
BTreeMap<usize, Arc<OwnedReadBufferRange>>),并更新 register_owned_read_buffer 与
claim_owned_read_buffer 的访问逻辑。使用 range(..=start).next_back()
定位候选范围,注册时仅检查相邻前后条目的重叠,避免全量线性扫描,同时保持现有资源冲突与范围匹配行为。

In `@pulse_core/src/trap.rs`:
- Around line 14-25: Update TrapIrqEnableGuard to include PhantomData<*mut ()>
state, preventing automatic Send/Sync implementations and ensuring the guard
cannot move across CPUs before Drop::drop restores interrupt state. Initialize
the marker in TrapIrqEnableGuard::new while preserving the existing
restore_disabled behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0a9562bd-024d-449d-b306-498130fcafda

📥 Commits

Reviewing files that changed from the base of the PR and between 31900fe and 1368474.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • arceos/modules/axdriver/src/prelude.rs
  • arceos/modules/axdriver/src/virtio.rs
  • arceos/modules/axfs/Cargo.toml
  • arceos/modules/axfs/src/disk.rs
  • arceos/modules/axfs/src/fs/ext4/fs.rs
  • arceos/modules/axfs/src/fs/ext4/inode.rs
  • arceos/modules/axfs/src/fs/ext4/mod.rs
  • arceos/modules/axfs/src/fs/loop_dev.rs
  • arceos/modules/axfs/src/highlevel/file.rs
  • arceos/modules/axfs/src/lib.rs
  • arceos/modules/axmm/src/backend/file.rs
  • crates/axdriver_block/Cargo.toml
  • crates/axdriver_block/src/lib.rs
  • crates/axdriver_virtio/src/blk.rs
  • crates/axfs-ng-vfs/src/node/dir.rs
  • crates/virtio-drivers/src/device/blk.rs
  • crates/virtio-drivers/src/lib.rs
  • pulse_core/src/trap.rs
  • pulse_syscalls/src/handler.rs
  • pulse_syscalls/src/impls/fs/io.rs

Comment thread arceos/modules/axfs/src/disk.rs Outdated
Comment thread arceos/modules/axfs/src/fs/ext4/fs.rs
Comment thread arceos/modules/axfs/src/fs/ext4/inode.rs
Comment thread arceos/modules/axfs/src/fs/ext4/inode.rs Outdated
Comment thread arceos/modules/axfs/src/fs/ext4/inode.rs Outdated
Comment on lines 674 to +692
#[derive(Debug)]
pub struct PageCache {
struct PageCacheFrame {
addr: VirtAddr,
}

impl Drop for PageCacheFrame {
fn drop(&mut self) {
let paddr = virt_to_phys(self.addr);
if let Some(ref_count) = axalloc::frame_table().try_get_ref(paddr) {
if ref_count == 0 {
global_allocator().dealloc_pages(self.addr.as_usize(), 1);
} else if axalloc::frame_table().dec_ref(paddr) == 0 {
global_allocator().dealloc_pages(self.addr.as_usize(), 1);
}
} else {
global_allocator().dealloc_pages(self.addr.as_usize(), 1);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 查明 frame_table 引用计数与页释放路径的所有者约定
fd -t f 'frameinfo.rs' arceos/modules/axalloc | xargs -r cat -n

ast-grep run --pattern 'fn dealloc_frame($$$) { $$$ }' --lang rust arceos/modules/axmm
rg -nP -C4 '\b(dealloc_frame|inc_ref|dec_ref|mark_used)\s*\(' --type=rust arceos/modules/axmm arceos/modules/axfs

Repository: muou000/PulseOS

Length of output: 18923


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== axfs file.rs relevant sections =="
sed -n '620,705p' arceos/modules/axfs/src/highlevel/file.rs
echo
sed -n '720,760p' arceos/modules/axfs/src/highlevel/file.rs
echo
sed -n '1630,1690p' arceos/modules/axfs/src/highlevel/file.rs

echo
echo "== axalloc frameinfo references and exports =="
rg -n -C3 'pub (struct|fn)|FrameTable|frame_table|PinForMapping|pin_for_mapping|PageCacheFrame' arceos/modules/axalloc arceos/modules/axmm arceos/modules/axfs/src/highlevel/file.rs

Repository: muou000/PulseOS

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read-only verifier equivalent of PageCacheFrame::drop behavior from the source.
python3 - <<'PY'
def current_drop(ref_count):
    if ref_count is None:
        return "dealloc"
    if ref_count == 0:
        return "dealloc"
    remaining = ref_count - 1
    if remaining == 0:
        return "dec_ref_zero_dealloc"
    return "ref_count_reduced_to_" + str(remaining)

def requested_drop(ref_count):
    if ref_count is None:
        return "dealloc"
    if ref_count == 0:
        return "dealloc"
    return "no_dealloc"

for n in [None, 0, 1, 2]:
    print(f"entry ref_count = {n}: current={current_drop(n)}, requested={requested_drop(n)}")
PY

Repository: muou000/PulseOS

Length of output: 414


避免在 PageCacheFrame::drop 中把用户映射引用计入页引用计数。

FrameTable 的 dec_ref 不允许将 0 减到负值;pin_for_mapping 当前在原来为 0 时调用 mark_used,原为 1 时只调用 inc_ref,因此 PageCacheFrame 可能并不持有引用。若用户映射存在,ref_count == 1 的 dec_ref 会触发 panic,并且缓存解除/回写路径不应替代映射解除逻辑。保持 ref_count > 0 时不释放,除非 PageCacheFrame 能确保自己显式持有计数中的一份。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arceos/modules/axfs/src/highlevel/file.rs` around lines 674 - 692, 更新
PageCacheFrame::drop 的引用计数处理:不要将用户映射持有的引用计入缓存页释放逻辑,也不要对仅由映射持有的 ref_count == 1 调用
dec_ref。仅在 PageCacheFrame 明确持有独立引用时递减并在计数归零后释放;只要仍有用户映射或其他引用,就保留页面,避免触发
FrameTable::dec_ref 的非法减计数。

Comment on lines 1080 to +1105
loop {
let (dirty_pns, page_start, data) = {
let mut guard = self.page_cache.lock();
let mut dirty_pns = guard
.iter()
.filter(|(_, page)| page.dirty)
.map(|(pn, _)| *pn)
.collect::<Vec<_>>();
dirty_pns.sort_unstable();
let Some(&pn_start) = dirty_pns.first() else {
return Ok(());
};
let mut count = 1usize;
while count < dirty_pns.len()
&& count < MAX_WRITEBACK_PAGES
&& dirty_pns[count] == pn_start + count as u32
{
count += 1;
}
dirty_pns.truncate(count);

let pn_end = *dirty_pns.last().unwrap();
let page_start = pn_start as u64 * PAGE_SIZE as u64;
let last_page_start = pn_end as u64 * PAGE_SIZE as u64;
let last_len = file_len
.saturating_sub(last_page_start)
.min(PAGE_SIZE as u64) as usize;
let mut data = Vec::new();
if last_len != 0 {
let total_len = (pn_end - pn_start) as usize * PAGE_SIZE + last_len;
data.reserve(total_len);
for &pn in &dirty_pns {
let page = guard.get_mut(&pn).ok_or(VfsError::Io)?;
let curr_page_start = pn as u64 * PAGE_SIZE as u64;
let curr_len = file_len
.saturating_sub(curr_page_start)
.min(PAGE_SIZE as u64) as usize;
data.extend_from_slice(&page.data()[..curr_len]);
page.dirty = false;
}
} else {
for &pn in &dirty_pns {
if let Some(page) = guard.get_mut(&pn) {
page.dirty = false;
}
while next_batch < page_batches.len() && pending.len() < WRITEBACK_CONCURRENCY {
let pages = &page_batches[next_batch];
let page_start = pages[0] as u64 * PAGE_SIZE as u64;
let data = {
let mut cache = self.page_cache.lock();
let mut data = Vec::new();
for &pn in pages {
let page = cache.get_mut(&pn).ok_or(VfsError::Io)?;
let current_start = pn as u64 * PAGE_SIZE as u64;
let len =
file_len.saturating_sub(current_start).min(PAGE_SIZE as u64) as usize;
data.extend_from_slice(&page.data()[..len]);
}
}
(dirty_pns, page_start, data)
};
data
};
pending.push(submit_writeback_batch(
file,
WritebackBatch {
pages: pages.clone(),
offset: page_start,
data,
},
));
next_batch += 1;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

批次收集失败会取消已在飞行中的写回。

第 1088 行在批次组装阶段使用 ? 直接返回。此时 pending 中仍可能存在未完成的 submit_writeback_batch future。返回会丢弃 FuturesUnordered,从而取消这些正在进行的写回,并且这些页的脏标记既不会被清除也不会被确认落盘。请把该错误记录到 first_error,然后停止提交新批次并继续排空 pending。

♻️ 建议的处理方式
-                let data = {
+                let data = {
                     let mut cache = self.page_cache.lock();
                     let mut data = Vec::new();
+                    let mut missing = false;
                     for &pn in pages {
-                        let page = cache.get_mut(&pn).ok_or(VfsError::Io)?;
+                        let Some(page) = cache.get_mut(&pn) else {
+                            missing = true;
+                            break;
+                        };
                         let current_start = pn as u64 * PAGE_SIZE as u64;
                         let len =
                             file_len.saturating_sub(current_start).min(PAGE_SIZE as u64) as usize;
                         data.extend_from_slice(&page.data()[..len]);
                     }
-                    data
+                    if missing {
+                        if first_error.is_none() {
+                            first_error = Some(VfsError::Io);
+                        }
+                        next_batch = page_batches.len();
+                        break;
+                    }
+                    data
                 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
loop {
let (dirty_pns, page_start, data) = {
let mut guard = self.page_cache.lock();
let mut dirty_pns = guard
.iter()
.filter(|(_, page)| page.dirty)
.map(|(pn, _)| *pn)
.collect::<Vec<_>>();
dirty_pns.sort_unstable();
let Some(&pn_start) = dirty_pns.first() else {
return Ok(());
};
let mut count = 1usize;
while count < dirty_pns.len()
&& count < MAX_WRITEBACK_PAGES
&& dirty_pns[count] == pn_start + count as u32
{
count += 1;
}
dirty_pns.truncate(count);
let pn_end = *dirty_pns.last().unwrap();
let page_start = pn_start as u64 * PAGE_SIZE as u64;
let last_page_start = pn_end as u64 * PAGE_SIZE as u64;
let last_len = file_len
.saturating_sub(last_page_start)
.min(PAGE_SIZE as u64) as usize;
let mut data = Vec::new();
if last_len != 0 {
let total_len = (pn_end - pn_start) as usize * PAGE_SIZE + last_len;
data.reserve(total_len);
for &pn in &dirty_pns {
let page = guard.get_mut(&pn).ok_or(VfsError::Io)?;
let curr_page_start = pn as u64 * PAGE_SIZE as u64;
let curr_len = file_len
.saturating_sub(curr_page_start)
.min(PAGE_SIZE as u64) as usize;
data.extend_from_slice(&page.data()[..curr_len]);
page.dirty = false;
}
} else {
for &pn in &dirty_pns {
if let Some(page) = guard.get_mut(&pn) {
page.dirty = false;
}
while next_batch < page_batches.len() && pending.len() < WRITEBACK_CONCURRENCY {
let pages = &page_batches[next_batch];
let page_start = pages[0] as u64 * PAGE_SIZE as u64;
let data = {
let mut cache = self.page_cache.lock();
let mut data = Vec::new();
for &pn in pages {
let page = cache.get_mut(&pn).ok_or(VfsError::Io)?;
let current_start = pn as u64 * PAGE_SIZE as u64;
let len =
file_len.saturating_sub(current_start).min(PAGE_SIZE as u64) as usize;
data.extend_from_slice(&page.data()[..len]);
}
}
(dirty_pns, page_start, data)
};
data
};
pending.push(submit_writeback_batch(
file,
WritebackBatch {
pages: pages.clone(),
offset: page_start,
data,
},
));
next_batch += 1;
}
loop {
while next_batch < page_batches.len() && pending.len() < WRITEBACK_CONCURRENCY {
let pages = &page_batches[next_batch];
let page_start = pages[0] as u64 * PAGE_SIZE as u64;
let data = {
let mut cache = self.page_cache.lock();
let mut data = Vec::new();
let mut missing = false;
for &pn in pages {
let Some(page) = cache.get_mut(&pn) else {
missing = true;
break;
};
let current_start = pn as u64 * PAGE_SIZE as u64;
let len =
file_len.saturating_sub(current_start).min(PAGE_SIZE as u64) as usize;
data.extend_from_slice(&page.data()[..len]);
}
if missing {
if first_error.is_none() {
first_error = Some(VfsError::Io);
}
next_batch = page_batches.len();
break;
}
data
};
pending.push(submit_writeback_batch(
file,
WritebackBatch {
pages: pages.clone(),
offset: page_start,
data,
},
));
next_batch += 1;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arceos/modules/axfs/src/highlevel/file.rs` around lines 1080 - 1105, Update
the batch assembly loop around submit_writeback_batch so page-cache lookup or
data extraction errors are stored in first_error instead of returned
immediately. Stop submitting additional batches after the first error, then
continue polling and draining all existing pending futures so in-flight
writebacks complete and their page state is handled normally.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
arceos/modules/axfs/src/fs/ext4/inode.rs (3)

1119-1150: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

观察阶段可复用 dir_cache 的 lookup 缓存,减少每次 unlink 的双倍目录 I/O。

当前实现在每次 unlink 中执行两轮完整的目录查找:1122-1132 行的观察阶段和 1140-1147 行的重确认阶段。每轮包含一次 Inode::read 和一次 dir.get_entry。在无并发的常见路径上,这使 unlink 的元数据 I/O 翻倍。

self.dir_cache.get_lookup(name) 已能在缓存命中时直接给出 inode_num。观察阶段只需要一个候选 ino 来决定加锁顺序,缓存值即使陈旧也不影响正确性,因为 1148 行的重确认会检测不匹配并重试。

建议在观察阶段先尝试缓存,仅在未命中时回退到目录读取。

♻️ 建议的快路径
         loop {
-            let observation_nodes = [self];
-            let observation_guards = Self::lock_mutation_set(&observation_nodes).await;
-            let observed_dir_inode = ext4plus::inode::Inode::read(&fs, dir_idx)
-                .await
-                .map_err(into_vfs_err)?;
-            let observed_dir =
-                ext4plus::dir::Dir::open_inode(&fs, observed_dir_inode).map_err(into_vfs_err)?;
-            let observed_name =
-                ext4plus::DirEntryName::try_from(name).map_err(|_| VfsError::InvalidInput)?;
-            let observed_child = observed_dir
-                .get_entry(observed_name)
-                .await
-                .map_err(into_vfs_err)?;
-            let observed_child_ino = observed_child.index.get();
-            drop(observation_guards);
+            let observed_child_ino = match self.dir_cache.get_lookup(name) {
+                Some(Some(cached)) => cached.inode_num,
+                Some(None) => return Err(VfsError::NotFound),
+                None => {
+                    let observation_nodes = [self];
+                    let observation_guards = Self::lock_mutation_set(&observation_nodes).await;
+                    let observed_dir_inode = ext4plus::inode::Inode::read(&fs, dir_idx)
+                        .await
+                        .map_err(into_vfs_err)?;
+                    let observed_dir = ext4plus::dir::Dir::open_inode(&fs, observed_dir_inode)
+                        .map_err(into_vfs_err)?;
+                    let observed_name = ext4plus::DirEntryName::try_from(name)
+                        .map_err(|_| VfsError::InvalidInput)?;
+                    let observed_child = observed_dir
+                        .get_entry(observed_name)
+                        .await
+                        .map_err(into_vfs_err)?;
+                    let ino = observed_child.index.get();
+                    drop(observation_guards);
+                    ino
+                }
+            };

注意:采用该快路径后,1148 行的 child_ino != observed_child_ino 判断从“并发检测”变为同时承担“缓存校验”。请确认重试次数在缓存长期陈旧时仍能收敛。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arceos/modules/axfs/src/fs/ext4/inode.rs` around lines 1119 - 1150, 在 unlink
循环的观察阶段复用 self.dir_cache.get_lookup(name) 获取候选 inode;缓存命中时跳过
Inode::read、Dir::open_inode 和 observed_dir.get_entry,仅在未命中时执行现有目录读取回退。保留重确认阶段及
child_ino != observed_child_ino 的校验,使陈旧缓存通过 continue 重试并最终收敛。

1338-1342: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

用解构消除 expect,避免在写路径上留下 panic 点。

1338-1340 行的 expect("rename destination lock target disappeared") 依赖一条跨约 100 行的隐式推理:1299 行 dst_inode 为 Some,1274 行校验 dst_inode.index.get() == observed_dst_ino,因此 1242 行由 observed_dst_ino.map(...) 构造的 dst_node 也必为 Some。

该推理目前成立,expect 不会触发。但它把一个 panic 点放在文件系统写路径中间,并且依赖三处远距离代码保持同步。

建议在 1299 行同时绑定 dst_inode 与 dst_node,让类型系统保证两者同时存在。

♻️ 建议的解构
-                    if let Some(dst_inode) = dst_inode {
+                    if let (Some(dst_inode), Some(local_dst)) = (dst_inode, dst_node.as_ref()) {
                         if dst_inode.links_count() == 0 {
-                            let local_dst = dst_node
-                                .as_ref()
-                                .expect("rename destination lock target disappeared");
                             let has_other_active =
                                 Self::mark_unlinked_and_has_external_refs(local_dst);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arceos/modules/axfs/src/fs/ext4/inode.rs` around lines 1338 - 1342,
在重命名写路径中更新 `dst_inode` 的匹配逻辑,同时解构绑定对应的 `dst_node`,让两者在同一分支内被类型系统证明同时存在。随后移除
`dst_node.as_ref().expect(...)`,直接使用已绑定的节点引用,并保持
`mark_unlinked_and_has_external_refs` 的现有处理不变。

448-455: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Arc::strong_count(&inode) > 2 依赖调用点的隐式引用计数约定,建议补充注释。

阈值 2 的来源是:调用点持有 1 个强引用(unlink 中的 child,rename 中的 dst_node),weak.upgrade() 在循环内产生第 2 个强引用。只有超过 2 才表示存在第三方引用。

该约定没有在代码中说明。如果将来某个调用点额外克隆了 Arc<Inode>,该函数会把本地实例误判为外部引用,立即删除分支将不再执行,删除退回后台 process_pending_deletions。这不损坏数据,但会静默改变删除时机,且很难定位。

建议在函数上方注释说明该不变量,或改为显式传入本地持有的强引用数量。

♻️ 建议的注释
+    /// 判断 `local` 对应的 inode 是否存在本函数调用方之外的活跃引用,并为这些
+    /// 外部实例设置 `is_unlinked`。
+    ///
+    /// 不变量:调用方必须恰好持有 `local` 的 1 个强引用。循环内 `weak.upgrade()`
+    /// 产生第 2 个强引用,因此 `strong_count > 2` 才代表存在第三方引用。
     fn mark_unlinked_and_has_external_refs(local: &Arc<Self>) -> bool {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@arceos/modules/axfs/src/fs/ext4/inode.rs` around lines 448 - 455, 在包含
is_local 和 Arc::strong_count(&inode) > 2 判断的函数上方补充注释,明确说明阈值 2 依赖调用点持有一个强引用、循环内
weak.upgrade() 持有第二个强引用,超过 2 才表示存在第三方引用;同时记录 unlink 的 child 和 rename 的 dst_node
是该约定的一部分,避免未来额外克隆 Arc<Inode> 后误判本地实例。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@arceos/modules/axfs/src/fs/ext4/inode.rs`:
- Around line 1366-1394: 更新 rename 流程中由 link_result 和 unlink_result
驱动的回滚逻辑:当源目录的 src_dir.unlink 失败时,立即在目标目录 dst_dir_obj 中撤销刚创建的 dst_name
条目,使链接计数恢复到 link 前状态。同步执行目标目录及 inode 元数据、目录缓存失效处理,并继续返回源条目删除错误;成功路径保持现有缓存更新行为不变。

---

Nitpick comments:
In `@arceos/modules/axfs/src/fs/ext4/inode.rs`:
- Around line 1119-1150: 在 unlink 循环的观察阶段复用 self.dir_cache.get_lookup(name) 获取候选
inode;缓存命中时跳过 Inode::read、Dir::open_inode 和
observed_dir.get_entry,仅在未命中时执行现有目录读取回退。保留重确认阶段及 child_ino != observed_child_ino
的校验,使陈旧缓存通过 continue 重试并最终收敛。
- Around line 1338-1342: 在重命名写路径中更新 `dst_inode` 的匹配逻辑,同时解构绑定对应的
`dst_node`,让两者在同一分支内被类型系统证明同时存在。随后移除
`dst_node.as_ref().expect(...)`,直接使用已绑定的节点引用,并保持
`mark_unlinked_and_has_external_refs` 的现有处理不变。
- Around line 448-455: 在包含 is_local 和 Arc::strong_count(&inode) > 2
判断的函数上方补充注释,明确说明阈值 2 依赖调用点持有一个强引用、循环内 weak.upgrade() 持有第二个强引用,超过 2
才表示存在第三方引用;同时记录 unlink 的 child 和 rename 的 dst_node 是该约定的一部分,避免未来额外克隆 Arc<Inode>
后误判本地实例。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fdcf9bca-ec63-418b-9cf1-55431dac768f

📥 Commits

Reviewing files that changed from the base of the PR and between 1368474 and 79946bb.

📒 Files selected for processing (9)
  • arceos/modules/axdriver/src/virtio.rs
  • arceos/modules/axfs/src/disk.rs
  • arceos/modules/axfs/src/fs/ext4/fs.rs
  • arceos/modules/axfs/src/fs/ext4/inode.rs
  • arceos/modules/axfs/src/fs/ext4/mod.rs
  • arceos/modules/axfs/src/highlevel/file.rs
  • arceos/modules/axmm/src/backend/file.rs
  • crates/axdriver_block/src/lib.rs
  • pulse_core/src/trap.rs
🚧 Files skipped from review as they are similar to previous changes (7)
  • pulse_core/src/trap.rs
  • arceos/modules/axmm/src/backend/file.rs
  • arceos/modules/axdriver/src/virtio.rs
  • arceos/modules/axfs/src/fs/ext4/fs.rs
  • crates/axdriver_block/src/lib.rs
  • arceos/modules/axfs/src/fs/ext4/mod.rs
  • arceos/modules/axfs/src/highlevel/file.rs

Comment thread arceos/modules/axfs/src/fs/ext4/inode.rs Outdated
@muou000
muou000 merged commit c51e69b into main Jul 31, 2026
1 check passed
@muou000
muou000 deleted the feat-async-fs branch July 31, 2026 05:23
muou000 added a commit that referenced this pull request Sep 29, 2026
* feat(block): add request-owned asynchronous I/O

* fix(runtime): enable IRQs across sleepable user traps

* feat(fs): propagate async I/O through ext4

* perf(fs): parallelize page cache and directory operations

* perf(fs): coalesce global filesystem flushes

* fix(fs): harden async I/O concurrency invariants

* fix(fs): harden ext4 mutation recovery
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant