fd_clone给fuse带来了什么
libfuse从2016年12月08日发布3.0.0版本开始支持clone_fd选项,相应的ko模块在linux内核4.2版本做相应的修改:https://kernelnewbies.org/Linux_4.2。
查看libfuse的changelog是这么描述的
* Added *clone_fd* option. This creates a separate device file
descriptor for each processing thread, which might improve
performance.
意思是可以为一个processing thread创建一个独立的内核文件描述符file descriptor,达到提高性能的目的。这只是用户态所看到的信息,看看内核ko的protocol说明,可以看到如下的表述
Background: When a file is opened, the Linux kernel creates a "file description" for the I/O state, and returns a "file descriptor" to userland. That descriptor can be freely passed to the dup(2) functions to duplicate the descriptor, but the underlying description remains unary.
The FUSE kernel driver implicitly locks access to the
/dev/fuse
file descriptor so that each read() and write() syscall is atomic. This implies that multiple threads can safely share the descriptor, but also that they will face lock contention and reduced performance.To get the best performance out of a multi-threaded filesystem server, open
/dev/fuse
once as a "session FD" and again in each thread as "worker FDs". After initializing the session with a standard FUSE handshake, the workers can be associated with the session by callingioctl(worker_fd, FUSE_DEV_IOC_CLONE, &session_fd)
.This allows multiple threads to serve FUSE requests without contending for the descriptor lock.
所以不是separate file descriptor这么简单,普通的通过dup(2)复制出来的fd,最终指向的是同一个file description,下面这个图应该是熟面孔了,如果fd 20是通过对fd 2执行dup(2)得到的,则两个fd在内核实际指向同一个file description,在这种情况下,不考虑file description中file operation的实现,不同的两个线程分别操作fd 2和fd 20,最终在file description都难以避免一些竞争条件。
而fd_clone则正是为了解决这个问题,通过使用ioctl来对一个fuse dev fd进行clone,在内核创建不同的file description,将资源分割开以达到尽量减少冲突域的目的。
fuse在mount和clone fd后内核数据结构如下图:
- mount一个fuse文件系统对应一个super block;
- 一个super block对应一个fuse_conn;
- mount时会创建第一个fuse_dev;
- 每clone一次fd就会再创建一个fuse_dev;
- 每个fuse_dev都有自己的processing queue;
- 多个fuse_dev都放在fuse_conn的devices链表中;
- 一个fuse文件系统只有一个iput queue在fuse_conn对象中;
fs handler
从fs handler端来看,user application发起的文件操作最终都会走到__fuse_request_send(fuse_conn *, fuse_req *)。可能有多个user application线程同时发起操作,都是通过对fuse_iqueue中waitq的spinlock进行加锁来互斥,保护多个线程同时把request挂入到fuse_iqueue的pending中。所以fd clone对user application端的效率应该是没有任何帮助了。
dev handler
而fuse daemon端的多个worker线程都是通过fuse_dev_do_read(fuse_dev *, file *, size_t)来读取request。如果是支持fd clone的话,这里的fuse_dev *和file *两个参数都是线程独享的。从流程上来看,fuse_dev_do_read是从fuse_conn的fuse_iqueue中取出一个request,放入到fuse_dev的fuse_pqueue中的io list里;然后执行内存拷贝,将内核buffer中的req数据拷贝到用户空间,copy完成后将这个req再从fuse_pqueue的io list移动到processing list。然后read操作返回。整个过程会针对fuse_pqueue加多次锁,因此如果不同的worker使用不同的fuse_dev的话,这里的竞争条件必然会缩小,从而达到提高效率的目的。