Fuse内核分析
Fuse内核入口
fuse_init是FUSE文件系统内核部分的入口
module_init(fuse_init);
scss
static int __init fuse_init(void)
{
。。。
// 初始化fuse连接列表
INIT_LIST_HEAD(&fuse_conn_list);
// fuse文件系统初始化
res = fuse_fs_init();
。。。
// fuse misc设备初始化
res = fuse_dev_init();
。。。
// sys节点创建,/sys/fs/fuse/connections
res = fuse_sysfs_init();
。。。
// 注册fusectl文件系统
res = fuse_ctl_init();
。。。
// 初始化最大后台请求数和最大阻塞请求数,可以通过cmdline配置,缺省1987个
sanitize_global_limit(&max_user_bgreq);
sanitize_global_limit(&max_user_congthresh);
。。。
}
csharp
static int __init fuse_fs_init(void)
{
。。。
//创建fuse_inode的cache链表,大小固定采用kmem_cache方式分配
fuse_inode_cachep = kmem_cache_create("fuse_inode",
sizeof(struct fuse_inode), 0,
SLAB_HWCACHE_ALIGN|SLAB_ACCOUNT|SLAB_RECLAIM_ACCOUNT,
fuse_inode_init_once);
。。。
//注册Fuse文件系统
err = register_filesystem(&fuse_fs_type);
。。。
}
int __init fuse_dev_init(void)
{
。。。
//创建fuse_req的cache链表,大小固定采用kmem_cache方式分配
fuse_req_cachep = kmem_cache_create("fuse_request",
sizeof(struct fuse_req),
0, 0, NULL);
。。。
//注册Fuse的Misc device,/dev/fuse
err = misc_register(&fuse_miscdevice);
。。。
}
上面初始化代码完成了如下功能:
- 内核注册了2种文件系统:fuse,fuse_ctl
- 与用户空间交互的设备接口 /dev/fuse
- 创建控制用的sysfs接口 /sys/fs/fuse/connections,在init.rc里面会把fuse_ctl挂载到这个目录
mount fusectl none /sys/fs/fuse/connections
- 初始化了fuse相关的全局链表,fuse_conn_list,fuse_inode_cachep,fuse_req_cachep等
Fuse内核挂载流程
在fuse_init里面注册文件类型的时候会注册mount的处理函数,这样当fuse_init执行完毕后,内核里面的下一步处理,就是等待用户态执行系统调用mount的时候,通过指定文件系统类型为fuse来触发fuse_mount的执行。
ini
static struct file_system_type fuse_fs_type = {
.owner = THIS_MODULE,
.name = "fuse",
.fs_flags = FS_HAS_SUBTYPE | FS_USERNS_MOUNT,
.mount = fuse_mount,
.kill_sb = fuse_kill_sb_anon,
};
ini
static struct dentry *fuse_mount(struct file_system_type *fs_type,
int flags, const char *dev_name,
void *raw_data)
{
// 直接就调用了内核的mount_nodev标准处理,完成挂载操作,这里主要关注
// 传入的回调函数fuse_fill_super这里面包含了Fuse文件系统主要的处理
return mount_nodev(fs_type, flags, raw_data, fuse_fill_super);
}
static int fuse_fill_super(struct super_block *sb, void *data, int silent)
{
。。。
// 挂载选项处理
if (!parse_fuse_opt(data, &d, is_bdev, sb->s_user_ns))
goto err;
。。。
// superblock的一些常规设置,包括Magic和一些操作函数设置
sb->s_magic = FUSE_SUPER_MAGIC;
sb->s_op = &fuse_super_operations;
sb->s_xattr = fuse_xattr_handlers;
sb->s_maxbytes = MAX_LFS_FILESIZE;
sb->s_time_gran = 1;
sb->s_export_op = &fuse_export_operations;
。。。
// 初始化fuse_conn结构,每个用户对应一个connection,
// 如u0,u10各自对应不同的connection
fc = kmalloc(sizeof(*fc), GFP_KERNEL);
fuse_conn_init(fc, sb->s_user_ns);
// 初始化fuse_dev结构
fud = fuse_dev_alloc(fc);
// 块设备信息初始化
err = fuse_bdi_init(fc, sb);
。。。
// 根节点初始化
root = fuse_get_root_inode(sb, d.rootmode);
sb->s_d_op = &fuse_root_dentry_operations;
root_dentry = d_make_root(root);
。。。
// 添加fuse_control节点,创建在/sys/fs/fuse/connections下创建一个子目录
// 子目录包含,waiting,abort,max_background,congestion_threshold控制节点
err = fuse_ctl_add_conn(fc);
if (err)
goto err_unlock;
// 把fuse_conn的实例,添加到fuse_conn_list中
list_add_tail(&fc->entry, &fuse_conn_list);
// 保存根目录的dentry到super_block里面
sb->s_root = root_dentry;
// 把fuse device保存到file的private_data里面
file->private_data = fud;
。。。
// 发送init_req request,初始化操作基本完成,后面就是响应用户空间发送的文件操作函数,
// 如read,write等
fuse_send_init(fc, init_req);
。。。
}
fuse_req结构体
fuse_req是用到最多的结构体,所有的操作都是通过这个结构体来完成的
arduino
/**
* A request to the client
*
* .waitq.lock protects the following fields:
* - FR_ABORTED
* - FR_LOCKED (may also be modified under fc->lock, tested under both)
*/
struct fuse_req {
/** This can be on either pending processing or io lists in
fuse_conn */
struct list_head list;
/** Entry on the interrupts list */
struct list_head intr_entry;
/** refcount */
refcount_t count;
/** Unique ID for the interrupt request */
u64 intr_unique;
/* Request flags, updated with test/set/clear_bit() */
unsigned long flags;
/** The request input */
struct fuse_in in;
/** The request output */
struct fuse_out out;
/** Used to wake up the task waiting for completion of request*/
wait_queue_head_t waitq;
/** Data for asynchronous requests */
union {
struct {
struct fuse_release_in in;
struct inode *inode;
} release;
struct fuse_init_in init_in;
struct fuse_init_out init_out;
struct cuse_init_in cuse_init_in;
struct {
struct fuse_read_in in;
u64 attr_ver;
} read;
struct {
struct fuse_write_in in;
struct fuse_write_out out;
struct fuse_req *next;
} write;
struct fuse_notify_retrieve_in retrieve_in;
} misc;
/** page vector */
struct page **pages;
/** page-descriptor vector */
struct fuse_page_desc *page_descs;
/** size of the 'pages' array */
unsigned max_pages;
/** inline page vector */
struct page *inline_pages[FUSE_REQ_INLINE_PAGES];
/** inline page-descriptor vector */
struct fuse_page_desc inline_page_descs[FUSE_REQ_INLINE_PAGES];
/** number of pages in vector */
unsigned num_pages;
/** File used in the request (or NULL) */
struct fuse_file *ff;
/** Inode used in the request or NULL */
struct inode *inode;
/** AIO control block */
struct fuse_io_priv *io;
/** Link on fi->writepages */
struct list_head writepages_entry;
/** Request completion callback */
void (*end)(struct fuse_conn *, struct fuse_req *);
/** Request is stolen from fuse_file->reserved_req */
struct file *stolen_file;
};
FUSE简单命令处理流程
以access命令判断某个文件是否存在为例来描述一下简单命令的处理流程,虽然是简单命令,但是流程也是很复杂的,下面的列出的也只是主要的处理步骤。
- Fuse文件系统初始化完成后,在线程处理函数fuse_do_work里面会阻塞在对/dev/fuse节点的读操作里面,因为Android 11对读操作进行了加速优化,因此实际是调用splice函数并阻塞在里面,Kernel响应splice系统调用执行__arm64_sys_splice,最后会调用到fuse_dev_do_read函数,当Kernel数据没有准备好的时候,fuse_dev_do_read会阻塞在等待队列fiq->waitq
- 用户态应用调用access系统调用访问fuse文件系统路径,Kernel响应函数是__arm64_sys_faccessat,通过VFS的最后会调用到fuse_lookup,会分配一个lookup的fuse_req的命令结构体,经过一系列的传递,最后这个fuse_req结构体会进入等待队列fiq->pending,在queue_request函数里面,通过wake_up(&fiq->waitq)来唤醒fuse_dev_do_read继续执行
- fuse_dev_do_read在被唤醒后,把这个fuse_req发送到User空间,user空间的splice函数被唤醒,继续执行,fuse_req结构体会进入等待队列fpq->processing。
- Fuse文件系统的工作进程,对接收到是的数据进行处理,首先会调用到libfuse里面的do_lookup,然后会调用到MediaProvider里面的pf_lookup,继续调用MediaProvider里面的do_lookup。到这里完成了fuse_req的主要操作,然后会把这个请求用writev函数写入/dev/fuse节点,通知Kernel这个fuse_req处理结果。
- Kernel对writev系统调用的响应函数__arm64_sys_writev,经过VFS层最后调用到fuse_dev_write函数,根据unique在fpq->processing队列里面找到对应的fuse_req结构体,把他从fpq->processing队列里面移除,最后回到用到request_end来结束这个fuse_req的处理,至此本次fuse_req的处理完全结束。
FUSE队列处理
如上图,FUSE内核维护了多个请求队列,包括interrupt, forgets, pending, processing, backgroud, io。其中io队列是一个过渡状态,是在user空间和kernel空间进行数据拷贝的阶段。一个fuse_req在任意时刻只能属于一个队列。
FUSE文件系统的切换过程
因为FUSE是overlay的文件系统,它的底层文件系统是F2FS,文件的实际存储是在F2FS文件系统上,那么从对FUSE文件系统的操作如何转换为对F2FS文件系统的操作呢?中间经历了哪些环节呢?实际上在Android 11里面FUSE并不是直接就到了F2FS,中间还经历了一个sdcardfs来完成切换。下面以mkdir为例来看一下文件系统的切换流程,在内核中文件系统的选择是根据文件路径来进行自动选择的,因此在整个文件系统的切换过程中操作的路径也会发生变化。
- 在用户态命令行下执行命令mkdir /sdcard/testdir,会以系统调用的方式,触发内核处理函数do_mkdirat,此时根据路径判断是FUSE文件系统,所以会通过vfs_mkdir2调用到fuse_mkdir。
- 在fuse_mkdir里面会通过/dev/fuse节点,触发User空间FUSE文件系统的处理函数do_mkdir,调用FuseDeamon注册的pf_mkdir,在pf_mkdir里面会执行mkdir /storage/emulated/0/testdir。
- 内核处理函数do_mkdirat,此时判断是sdcardfs文件系统(这块还不太明白具体是如何确定的,因为看路径和FUSE是一样的,不过可以看出FUSE是在SDCARDFS之上的),所以会通过vfs_mkdir2调用到sdcardfs_mkdir,在sdcardfs_mkdir里面会通过vfs_mkdir2调用底层文件系统F2FS,执行f2fs_mkdir完成实际的文件夹创建操作。
- 实际的文件夹创建操作完成后,调用函数会依次返回,最后do_mkdirat返回,触发用户空间的mkdir /storage/emulated/0/testdir的返回。
- 用户空间的FUSE文件系统的do_mkdir会返回,触发内核空间的fuse_mkdir函数返回,依次返回最后是do_mkdirat返回。
- 内核处理函数do_mkdirat返回触发了mkdir /sdcard/testdir的返回,整个处理流程结束。从这个过程可以看到文件系统的切换是从FUSE-->SDCARDFS-->F2FS-->SDCARDFS-->FUSE。
索引
参考文献
jishuin.proginn.com/p/763bfbd30...