Skip to content

Commit 72ebe13

Browse files
authored
perf(virtiofs): batch non-DAX writeback (#2122)
* perf(virtiofs): batch non-DAX writeback Buffer writes in the generic page cache only after FUSE_WRITEBACK_CACHE is negotiated, then claim and submit bounded contiguous batches according to the negotiated write and page limits. Preserve Linux-compatible fsync, close, truncate, mmap, invalidation, stable EOF, redirty, short-write, and errseq behavior. Separate terminal writeback completion from generic page-cache workers so host invalidation cannot strand published Writeback pages behind its own waiters. Harden kernel-thread creation and wakeup ordering required by the new worker pools. Add exact FUSE/page-cache counters, benchmark phase reporting, focused FUSE regressions, and root-only kthread and completion-domain selftests. Validated with make kernel -j2, kernel formatting and diff checks, the completion-domain selftest, targeted close/flush coverage, and FuseExtended 69/69 in a DragonOS guest. Signed-off-by: longjin <longjin@dragonos.org> * refactor(fuse): group negotiated io limits Pass negotiated read, write, page, capability, and effective payload limits through a dedicated stats value object. This keeps the INIT statistics update cohesive and satisfies the project-wide Clippy argument-count lint enforced by make fmt without changing the negotiated values or publication ordering. Signed-off-by: longjin <longjin@dragonos.org> --------- Signed-off-by: longjin <longjin@dragonos.org>
1 parent 6c6d313 commit 72ebe13

29 files changed

Lines changed: 4346 additions & 712 deletions

File tree

kernel/src/arch/riscv64/process/kthread.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,6 @@ impl KernelThreadMechanism {
5858
e
5959
})?;
6060

61-
let pcb = ProcessManager::find(pid).unwrap();
62-
pcb.set_name(info.name().clone());
63-
info.setup_pcb(&pcb);
64-
6561
return Ok(pid);
6662
}
6763
}

kernel/src/arch/x86_64/process/kthread.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,6 @@ impl KernelThreadMechanism {
5959
unsafe { KernelThreadCreateInfo::parse_unsafe_arc_ptr(create_info) };
6060
})?;
6161

62-
let pcb = ProcessManager::find_task_by_vpid(pid).unwrap();
63-
pcb.set_name(info.name().clone());
64-
info.setup_pcb(&pcb);
65-
6662
return Ok(pid);
6763
}
6864
}

kernel/src/debug/kthread.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use alloc::{string::String, string::ToString};
2+
3+
use system_error::SystemError;
4+
5+
use crate::{
6+
debug::sysfs::debugfs_kobj,
7+
driver::base::kobject::KObject,
8+
filesystem::{
9+
kernfs::callback::{KernCallbackData, KernFSCallback, KernFilePrivateData},
10+
vfs::{InodeMode, PollStatus},
11+
},
12+
};
13+
14+
#[derive(Debug)]
15+
struct KthreadSelftestCallback;
16+
17+
impl KernFSCallback for KthreadSelftestCallback {
18+
fn open(&self, mut data: KernCallbackData) -> Result<(), SystemError> {
19+
let report = crate::process::kthread::run_debug_selftests()?;
20+
data.file_private_data_mut()
21+
.replace(KernFilePrivateData::DebugTextSnapshot(report));
22+
Ok(())
23+
}
24+
25+
fn read(
26+
&self,
27+
data: KernCallbackData,
28+
buf: &mut [u8],
29+
offset: usize,
30+
) -> Result<usize, SystemError> {
31+
let report: &String = match data.file_private_data() {
32+
Some(KernFilePrivateData::DebugTextSnapshot(report)) => report,
33+
_ => return Err(SystemError::EINVAL),
34+
};
35+
let bytes = report.as_bytes();
36+
if offset >= bytes.len() {
37+
return Ok(0);
38+
}
39+
let len = buf.len().min(bytes.len() - offset);
40+
buf[..len].copy_from_slice(&bytes[offset..offset + len]);
41+
Ok(len)
42+
}
43+
44+
fn write(
45+
&self,
46+
_data: KernCallbackData,
47+
_buf: &[u8],
48+
_offset: usize,
49+
) -> Result<usize, SystemError> {
50+
Err(SystemError::EPERM)
51+
}
52+
53+
fn poll(&self, _data: KernCallbackData) -> Result<PollStatus, SystemError> {
54+
Ok(PollStatus::READ)
55+
}
56+
}
57+
58+
pub fn init_debugfs_kthread() -> Result<(), SystemError> {
59+
let debugfs = debugfs_kobj();
60+
let root = debugfs.inode().ok_or(SystemError::ENOENT)?;
61+
let kthread = root.add_dir(
62+
"kthread".to_string(),
63+
InodeMode::from_bits_truncate(0o555),
64+
None,
65+
None,
66+
)?;
67+
kthread.add_file(
68+
"selftest".to_string(),
69+
InodeMode::S_IRUSR,
70+
Some(4096),
71+
None,
72+
Some(&KthreadSelftestCallback),
73+
)?;
74+
Ok(())
75+
}

kernel/src/debug/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ pub mod fuse;
44
pub mod jump_label;
55
pub mod klog;
66
pub mod kprobe;
7+
pub mod kthread;
8+
pub mod page_cache;
79
pub mod panic;
810
pub mod rcu;
911
pub mod sysfs;

kernel/src/debug/page_cache.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use alloc::{string::String, string::ToString};
2+
3+
use system_error::SystemError;
4+
5+
use crate::{
6+
debug::sysfs::debugfs_kobj,
7+
driver::base::kobject::KObject,
8+
filesystem::{
9+
kernfs::callback::{KernCallbackData, KernFSCallback, KernFilePrivateData},
10+
vfs::{InodeMode, PollStatus},
11+
},
12+
};
13+
14+
#[derive(Debug)]
15+
struct PageCacheCompletionSelftestCallback;
16+
17+
impl KernFSCallback for PageCacheCompletionSelftestCallback {
18+
fn open(&self, mut data: KernCallbackData) -> Result<(), SystemError> {
19+
let report = crate::filesystem::page_cache::run_completion_domain_debug_selftest()?;
20+
data.file_private_data_mut()
21+
.replace(KernFilePrivateData::DebugTextSnapshot(report));
22+
Ok(())
23+
}
24+
25+
fn read(
26+
&self,
27+
data: KernCallbackData,
28+
buf: &mut [u8],
29+
offset: usize,
30+
) -> Result<usize, SystemError> {
31+
let report: &String = match data.file_private_data() {
32+
Some(KernFilePrivateData::DebugTextSnapshot(report)) => report,
33+
_ => return Err(SystemError::EINVAL),
34+
};
35+
let bytes = report.as_bytes();
36+
if offset >= bytes.len() {
37+
return Ok(0);
38+
}
39+
let len = buf.len().min(bytes.len() - offset);
40+
buf[..len].copy_from_slice(&bytes[offset..offset + len]);
41+
Ok(len)
42+
}
43+
44+
fn write(
45+
&self,
46+
_data: KernCallbackData,
47+
_buf: &[u8],
48+
_offset: usize,
49+
) -> Result<usize, SystemError> {
50+
Err(SystemError::EPERM)
51+
}
52+
53+
fn poll(&self, _data: KernCallbackData) -> Result<PollStatus, SystemError> {
54+
Ok(PollStatus::READ)
55+
}
56+
}
57+
58+
pub fn init_debugfs_page_cache() -> Result<(), SystemError> {
59+
let debugfs = debugfs_kobj();
60+
let root = debugfs.inode().ok_or(SystemError::ENOENT)?;
61+
let page_cache = root.add_dir(
62+
"page_cache".to_string(),
63+
InodeMode::from_bits_truncate(0o555),
64+
None,
65+
None,
66+
)?;
67+
page_cache.add_file(
68+
"completion_domain_selftest".to_string(),
69+
InodeMode::S_IRUSR,
70+
Some(4096),
71+
None,
72+
Some(&PageCacheCompletionSelftestCallback),
73+
)?;
74+
Ok(())
75+
}

kernel/src/debug/sysfs/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ fn debugfs_init() -> Result<(), SystemError> {
2525
super::errseq::init_debugfs_errseq()?;
2626
super::ext4::init_debugfs_ext4()?;
2727
super::fuse::init_debugfs_fuse()?;
28+
super::kthread::init_debugfs_kthread()?;
29+
super::page_cache::init_debugfs_page_cache()?;
2830
return Ok(());
2931
}
3032

kernel/src/filesystem/fuse/conn/daemon.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ use core::{mem::size_of, sync::atomic::Ordering};
44
use num_traits::FromPrimitive;
55
use system_error::SystemError;
66

7-
use crate::filesystem::epoll::{EPollEventType, EPollItem};
7+
use crate::{
8+
arch::MMArch,
9+
filesystem::epoll::{EPollEventType, EPollItem},
10+
mm::MemoryManagementArch,
11+
};
812

913
use super::super::protocol::{
1014
fuse_read_struct, FuseAttrOut, FuseEntryOut, FuseInHeader, FuseInitOut, FuseNotifyDeleteOut,
@@ -15,7 +19,7 @@ use super::super::protocol::{
1519
FUSE_MAP_ALIGNMENT, FUSE_MAX_PAGES, FUSE_MIN_READ_BUFFER, FUSE_MKDIR, FUSE_MKNOD,
1620
FUSE_NOTIFY_DELETE, FUSE_NOTIFY_INVAL_ENTRY, FUSE_NOTIFY_INVAL_INODE, FUSE_NOTIFY_POLL,
1721
FUSE_NOTIFY_RETRIEVE, FUSE_NOTIFY_STORE, FUSE_READ, FUSE_REMOVEXATTR, FUSE_SETATTR,
18-
FUSE_SETXATTR, FUSE_STATFS, FUSE_SYMLINK,
22+
FUSE_SETXATTR, FUSE_STATFS, FUSE_SYMLINK, FUSE_WRITEBACK_CACHE,
1923
};
2024
use super::{
2125
stats, trace, wait_with_recheck, FuseConn, FuseConnInner, FuseInitNegotiated, FuseRequest,
@@ -615,13 +619,23 @@ impl FuseConn {
615619
})?;
616620
self.background
617621
.configure(max_background, congestion_threshold);
618-
stats::on_fuse_read_limits_negotiated(
619-
self.max_read(),
620-
negotiated_max_pages as usize,
621-
init_out.max_readahead as usize,
622-
(enabled_flags & FUSE_ASYNC_READ) != 0,
623-
self.effective_read_payload_limit(),
624-
);
622+
stats::on_fuse_io_limits_negotiated(stats::NegotiatedFuseIoLimits {
623+
max_read: self.max_read(),
624+
max_write: self.max_write(),
625+
max_pages: negotiated_max_pages as usize,
626+
max_readahead: init_out.max_readahead as usize,
627+
async_read: (enabled_flags & FUSE_ASYNC_READ) != 0,
628+
writeback_cache: (enabled_flags & FUSE_WRITEBACK_CACHE) != 0,
629+
effective_read_payload_limit: self.effective_read_payload_limit(),
630+
effective_write_payload_limit: core::cmp::min(
631+
core::cmp::min(
632+
negotiated_max_pages as usize,
633+
self.max_write() / MMArch::PAGE_SIZE,
634+
),
635+
64,
636+
)
637+
.saturating_mul(MMArch::PAGE_SIZE),
638+
});
625639
self.init_wait.wakeup(None);
626640
} else {
627641
self.claim_pending_reply(out_hdr.unique, &pending, |_| {})?;

kernel/src/filesystem/fuse/fs.rs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use super::{
3131
fuse_read_struct, FuseStatfsOut, FOPEN_DIRECT_IO, FUSE_ATTR_SUBMOUNT, FUSE_ROOT_ID,
3232
FUSE_STATFS,
3333
},
34+
stats,
3435
};
3536

3637
#[derive(Debug)]
@@ -843,20 +844,40 @@ impl FileSystem for FuseFS {
843844
p.node.clone()
844845
};
845846

846-
node.with_writeback_admission(|| {
847+
let mut result = VmFaultReason::VM_FAULT_SIGBUS;
848+
let admitted = node.try_with_writeback_admission(&mut || {
847849
let Ok(_pin) = node.pin_writeback_handle() else {
848-
return VmFaultReason::VM_FAULT_SIGBUS;
850+
return Ok(());
851+
};
852+
let Some(metadata) = node.cached_metadata_snapshot() else {
853+
return Ok(());
849854
};
850-
let result = PageFaultHandler::filemap_page_mkwrite(pfm);
855+
result = PageFaultHandler::filemap_page_mkwrite_with_stable_size(
856+
pfm,
857+
metadata.size.max(0) as usize,
858+
);
851859
if !result.intersects(
852860
VmFaultReason::VM_FAULT_SIGBUS
853861
| VmFaultReason::VM_FAULT_OOM
854862
| VmFaultReason::VM_FAULT_RETRY,
855863
) {
856864
node.note_mmap_write();
857865
}
858-
result
859-
})
866+
Ok(())
867+
});
868+
match admitted {
869+
Ok(true) => result,
870+
Ok(false) => {
871+
// Never block on a writer-preferred admission semaphore while
872+
// the architecture fault path owns AddressSpace::write(). The
873+
// retry waiter acquires/releases it only after the MM guard is
874+
// dropped, closing the barrier<->mkclean cycle.
875+
stats::on_mmap_writeback_admission_retry();
876+
pfm.set_retry_wait(node.writeback_admission_retry_wait());
877+
VmFaultReason::VM_FAULT_RETRY
878+
}
879+
Err(_) => VmFaultReason::VM_FAULT_SIGBUS,
880+
}
860881
}
861882

862883
fn mprotect(&self, _old_vm_flags: VmFlags, new_vm_flags: VmFlags) -> Result<(), SystemError> {

0 commit comments

Comments
 (0)