Skip to content

Commit d544044

Browse files
authored
perf(virtiofs): reuse create handles for atomic open (#2120)
FUSE_CREATE already returns an opened file handle, but the VFS create path discarded it with FUSE_RELEASE and constructed the file through a second FUSE_OPEN. This added two protocol requests for every created file and amplified metadata-heavy container workloads. Add an optional VFS create-and-open operation backed by an RAII preopened-file guard. Carry the returned FUSE handle through MountFS wrapping and File construction, skip the redundant open request, and close the handle on every intermediate failure path. Cache FUSE_CREATE ENOSYS at the connection level and preserve the Linux-compatible MKNOD plus OPEN fallback. Forward Linux-compatible create flags while keeping close, cache-state, writeback, and file-mode initialization consistent with normal FUSE opens. Extend FuseExtended coverage for handle reuse, CREATE flag filtering, ENOSYS fallback caching, and RELEASE/FORGET cleanup of invalid replies. Signed-off-by: longjin <longjin@dragonos.org>
1 parent 13dacb3 commit d544044

9 files changed

Lines changed: 517 additions & 15 deletions

File tree

kernel/src/filesystem/fuse/conn.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,7 @@ struct FuseConnInner {
732732
init: FuseInitNegotiated,
733733
no_open: bool,
734734
no_opendir: bool,
735+
no_create: bool,
735736
no_readdirplus: bool,
736737
no_fallocate: bool,
737738
no_flush: bool,
@@ -892,6 +893,7 @@ impl FuseConn {
892893
init: FuseInitNegotiated::default(),
893894
no_open: false,
894895
no_opendir: false,
896+
no_create: false,
895897
no_readdirplus: false,
896898
no_fallocate: false,
897899
no_flush: false,
@@ -1347,6 +1349,14 @@ impl FuseConn {
13471349
}
13481350
}
13491351

1352+
pub fn no_create(&self) -> bool {
1353+
self.inner.lock().no_create
1354+
}
1355+
1356+
pub fn mark_no_create(&self) {
1357+
self.inner.lock().no_create = true;
1358+
}
1359+
13501360
pub fn use_readdirplus(&self) -> bool {
13511361
let g = self.inner.lock();
13521362
!g.no_readdirplus && (g.init.flags & FUSE_DO_READDIRPLUS) != 0

kernel/src/filesystem/fuse/inode/file.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,7 @@ impl FuseNode {
931931
Ok(total)
932932
}
933933

934-
fn set_open_private_data(
934+
pub(super) fn set_open_private_data(
935935
&self,
936936
data: &mut FilePrivateData,
937937
opcode: u32,
@@ -1000,7 +1000,7 @@ impl FuseNode {
10001000
Ok(())
10011001
}
10021002

1003-
fn finish_open_cache_state(
1003+
pub(super) fn finish_open_cache_state(
10041004
&self,
10051005
opcode: u32,
10061006
flags: &FileFlags,

kernel/src/filesystem/fuse/inode/vfs.rs

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
filesystem::{
99
page_cache::PageCache,
1010
vfs::{
11-
file::{FileFlags, FileMode},
11+
file::{FileFlags, FileMode, PreopenedFile},
1212
permission::PermissionMask,
1313
syscall::RenameFlags,
1414
utils::DName,
@@ -1093,6 +1093,10 @@ impl IndexNode for FuseNode {
10931093
return self.create_with_data(name, file_type, mode, 0);
10941094
}
10951095

1096+
if self.conn().no_create() {
1097+
return self.create_with_data(name, file_type, mode, 0);
1098+
}
1099+
10961100
let inarg = FuseCreateIn {
10971101
flags: FileFlags::O_RDONLY.bits(),
10981102
mode: (InodeMode::S_IFREG | mode).bits(),
@@ -1103,7 +1107,10 @@ impl IndexNode for FuseNode {
11031107

11041108
let payload = match self.conn().request(FUSE_CREATE, self.nodeid, &payload_in) {
11051109
Ok(v) => v,
1106-
Err(SystemError::ENOSYS) => return self.create_with_data(name, file_type, mode, 0),
1110+
Err(SystemError::ENOSYS) => {
1111+
self.conn().mark_no_create();
1112+
return self.create_with_data(name, file_type, mode, 0);
1113+
}
11071114
Err(e) => return Err(e),
11081115
};
11091116
let (entry, open_out) = Self::parse_create_reply(&payload)?;
@@ -1119,6 +1126,86 @@ impl IndexNode for FuseNode {
11191126
self.create_node_from_entry(&entry, Some(name), FileType::File)
11201127
}
11211128

1129+
fn create_and_open(
1130+
&self,
1131+
name: &str,
1132+
mode: InodeMode,
1133+
flags: &FileFlags,
1134+
) -> Result<PreopenedFile, SystemError> {
1135+
self.check_not_stale()?;
1136+
self.ensure_dir()?;
1137+
if self.conn().no_create() {
1138+
return Err(SystemError::ENOSYS);
1139+
}
1140+
1141+
// Linux fuse_create_open() forwards creation flags, excluding the
1142+
// tty-only flag. O_CLOEXEC is per-fd state and must not reach FUSE.
1143+
let file_flags = flags.bits() & !FileFlags::O_CLOEXEC.bits();
1144+
let create_flags = file_flags & !FileFlags::O_NOCTTY.bits();
1145+
let inarg = FuseCreateIn {
1146+
flags: create_flags,
1147+
mode: (InodeMode::S_IFREG | mode).bits(),
1148+
umask: 0,
1149+
open_flags: 0,
1150+
};
1151+
let payload_in = Self::pack_struct_and_name_payload(&inarg, name);
1152+
let payload = match self.conn().request(FUSE_CREATE, self.nodeid, &payload_in) {
1153+
Ok(payload) => payload,
1154+
Err(SystemError::ENOSYS) => {
1155+
self.conn().mark_no_create();
1156+
return Err(SystemError::ENOSYS);
1157+
}
1158+
Err(err) => return Err(err),
1159+
};
1160+
let (entry, open_out) = Self::parse_create_reply(&payload)?;
1161+
1162+
let inode = match self.create_node_from_entry(&entry, Some(name), FileType::File) {
1163+
Ok(inode) => inode,
1164+
Err(err) => {
1165+
if entry.nodeid != 0 {
1166+
self.release_common_for_node(
1167+
FUSE_RELEASE,
1168+
entry.nodeid,
1169+
open_out.fh,
1170+
file_flags,
1171+
0,
1172+
);
1173+
}
1174+
return Err(err);
1175+
}
1176+
};
1177+
let fuse_inode = match inode.clone().downcast_arc::<FuseNode>() {
1178+
Some(inode) => inode,
1179+
None => {
1180+
self.release_common_for_node(
1181+
FUSE_RELEASE,
1182+
entry.nodeid,
1183+
open_out.fh,
1184+
file_flags,
1185+
0,
1186+
);
1187+
return Err(SystemError::EIO);
1188+
}
1189+
};
1190+
1191+
let mut private_data = FilePrivateData::default();
1192+
if let Err(err) = fuse_inode.set_open_private_data(
1193+
&mut private_data,
1194+
FUSE_OPEN,
1195+
open_out.fh,
1196+
file_flags,
1197+
open_out.open_flags,
1198+
false,
1199+
) {
1200+
self.release_common_for_node(FUSE_RELEASE, entry.nodeid, open_out.fh, file_flags, 0);
1201+
return Err(err);
1202+
}
1203+
1204+
let preopened = PreopenedFile::new(inode, private_data);
1205+
fuse_inode.finish_open_cache_state(FUSE_OPEN, flags, open_out.open_flags)?;
1206+
Ok(preopened)
1207+
}
1208+
11221209
fn create_with_data(
11231210
&self,
11241211
name: &str,

kernel/src/filesystem/vfs/file.rs

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,47 @@ impl Default for FilePrivateData {
284284
}
285285
}
286286

287+
/// Owns filesystem private data for an inode that has already been opened.
288+
///
289+
/// The guard stays armed until a [`File`] takes ownership. This closes the
290+
/// filesystem handle on every error path between an atomic create/open and
291+
/// construction of the open file description.
292+
pub struct PreopenedFile {
293+
inode: Arc<dyn IndexNode>,
294+
private_data: Option<FilePrivateData>,
295+
}
296+
297+
impl PreopenedFile {
298+
pub fn new(inode: Arc<dyn IndexNode>, private_data: FilePrivateData) -> Self {
299+
Self {
300+
inode,
301+
private_data: Some(private_data),
302+
}
303+
}
304+
305+
pub fn inode(&self) -> Arc<dyn IndexNode> {
306+
self.inode.clone()
307+
}
308+
309+
pub fn replace_inode(&mut self, inode: Arc<dyn IndexNode>) {
310+
self.inode = inode;
311+
}
312+
313+
fn take_private_data(&mut self) -> FilePrivateData {
314+
self.private_data
315+
.take()
316+
.expect("preopened file private data already consumed")
317+
}
318+
}
319+
320+
impl Drop for PreopenedFile {
321+
fn drop(&mut self) {
322+
if let Some(data) = self.private_data.take() {
323+
let _ = self.inode.close(Mutex::new(data).lock());
324+
}
325+
}
326+
}
327+
287328
impl FilePrivateData {
288329
pub fn update_flags(&mut self, flags: FileFlags) -> Result<(), SystemError> {
289330
match self {
@@ -831,7 +872,13 @@ impl File {
831872
.downcast_arc::<MountFSInode>()
832873
.map(|inode| inode.mount_fs().try_pin_external())
833874
.transpose()?;
834-
Self::new_with_private_data_and_mount_guard(inode, flags, private_data_init, mount_guard)
875+
Self::new_with_private_data_and_mount_guard(
876+
inode,
877+
flags,
878+
private_data_init,
879+
mount_guard,
880+
None,
881+
)
835882
}
836883

837884
/// Construct a pathname-backed file by consuming the mount pin acquired
@@ -847,6 +894,26 @@ impl File {
847894
flags,
848895
FilePrivateData::default(),
849896
mount_guard,
897+
None,
898+
);
899+
drop(operation_guard);
900+
file
901+
}
902+
903+
/// Construct a pathname-backed file from an already-opened inode.
904+
pub fn new_preopened_with_mount_guard(
905+
preopened: PreopenedFile,
906+
flags: FileFlags,
907+
mount_guard: Option<MountExternalGuard>,
908+
operation_guard: InodeRetentionGuard,
909+
) -> Result<Self, SystemError> {
910+
let inode = preopened.inode();
911+
let file = Self::new_with_private_data_and_mount_guard(
912+
inode,
913+
flags,
914+
FilePrivateData::default(),
915+
mount_guard,
916+
Some(preopened),
850917
);
851918
drop(operation_guard);
852919
file
@@ -857,6 +924,7 @@ impl File {
857924
mut flags: FileFlags,
858925
private_data_init: FilePrivateData,
859926
mount_guard: Option<MountExternalGuard>,
927+
mut preopened: Option<PreopenedFile>,
860928
) -> Result<Self, SystemError> {
861929
let mut inode = inode;
862930
let mut file_type = inode.metadata()?.file_type;
@@ -912,11 +980,20 @@ impl File {
912980
let inode_retention =
913981
InodeRetentionGuard::new(inode.clone(), InodeRetentionKind::OpenFileDescription)?;
914982

915-
let private_data = Mutex::new(private_data_init);
983+
if is_path && preopened.is_some() {
984+
return Err(SystemError::EINVAL);
985+
}
986+
let already_open = preopened.is_some();
987+
let private_data = Mutex::new(match preopened.as_mut() {
988+
Some(preopened) => preopened.take_private_data(),
989+
None => private_data_init,
990+
});
916991
if is_path {
917992
mode = FileMode::FMODE_PATH | FileMode::FMODE_OPENED;
918993
} else {
919-
inode.open(private_data.lock(), &flags)?;
994+
if !already_open {
995+
inode.open(private_data.lock(), &flags)?;
996+
}
920997

921998
// 设置默认能力(由 inode 能力接口统一决定;避免 syscall 层/字符串特判)
922999
if inode.is_stream() {

kernel/src/filesystem/vfs/mod.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ use crate::{
4949
pub use self::inode_lifecycle::{EvictionEpoch, InodeRetentionKind, InodeRetentionState};
5050
pub use self::{file::FilePrivateData, mount::MountFS};
5151
use self::{
52-
file::{FileFlags, FileMode},
52+
file::{FileFlags, FileMode, PreopenedFile},
5353
utils::DName,
5454
vcore::generate_inode_id,
5555
};
@@ -794,6 +794,18 @@ pub trait IndexNode: Any + Sync + Send + Debug + CastFromSync {
794794
return self.create_with_data(name, file_type, mode, 0);
795795
}
796796

797+
/// Atomically create and open a regular file when supported by the
798+
/// filesystem. The returned guard owns the open handle until VFS builds
799+
/// the corresponding open file description.
800+
fn create_and_open(
801+
&self,
802+
_name: &str,
803+
_mode: InodeMode,
804+
_flags: &FileFlags,
805+
) -> Result<PreopenedFile, SystemError> {
806+
Err(SystemError::ENOSYS)
807+
}
808+
797809
/// @brief 在当前目录下创建一个新的inode,并传入一个简单的data字段,方便进行初始化。
798810
///
799811
/// @param name 目录项的名字

kernel/src/filesystem/vfs/mount/mod.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::{
2-
file::{File, FileFlags, FileMode},
2+
file::{File, FileFlags, FileMode, PreopenedFile},
33
utils::DName,
44
FilePrivateData, FileSystem, FileType, IndexNode, InodeId, InodeMode, InodeRetentionKind,
55
PollableInode, SetMetadataMask, SuperBlock, XattrFlags,
@@ -3301,6 +3301,38 @@ impl IndexNode for MountFSInode {
33013301
)?);
33023302
}
33033303

3304+
fn create_and_open(
3305+
&self,
3306+
name: &str,
3307+
mode: InodeMode,
3308+
flags: &FileFlags,
3309+
) -> Result<PreopenedFile, SystemError> {
3310+
self.ensure_mount_writable()?;
3311+
let children_guard = self.dentry.children_gate.lock();
3312+
let mut preopened = self.dentry.inode.create_and_open(name, mode, flags)?;
3313+
let wrapped = {
3314+
let _namespace_guard = self
3315+
.mount_fs
3316+
.super_block_state
3317+
.dentry_namespace_lock
3318+
.write();
3319+
self.self_ref
3320+
.upgrade()
3321+
.ok_or(SystemError::ENOENT)
3322+
.and_then(|parent| {
3323+
MountFSInode::new_child(
3324+
preopened.inode(),
3325+
self.mount_fs.clone(),
3326+
&parent,
3327+
DName::from(name),
3328+
)
3329+
})
3330+
};
3331+
drop(children_guard);
3332+
preopened.replace_inode(wrapped?);
3333+
Ok(preopened)
3334+
}
3335+
33043336
fn link(&self, name: &str, other: &Arc<dyn IndexNode>) -> Result<(), SystemError> {
33053337
self.ensure_mount_writable()?;
33063338
let _children_guard = self.dentry.children_gate.lock();

0 commit comments

Comments
 (0)