深入分析Android 11 FUSE文件系统(四)

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命令判断某个文件是否存在为例来描述一下简单命令的处理流程,虽然是简单命令,但是流程也是很复杂的,下面的列出的也只是主要的处理步骤。

  1. 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
  2. 用户态应用调用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继续执行
  3. fuse_dev_do_read在被唤醒后,把这个fuse_req发送到User空间,user空间的splice函数被唤醒,继续执行,fuse_req结构体会进入等待队列fpq->processing。
  4. Fuse文件系统的工作进程,对接收到是的数据进行处理,首先会调用到libfuse里面的do_lookup,然后会调用到MediaProvider里面的pf_lookup,继续调用MediaProvider里面的do_lookup。到这里完成了fuse_req的主要操作,然后会把这个请求用writev函数写入/dev/fuse节点,通知Kernel这个fuse_req处理结果。
  5. 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为例来看一下文件系统的切换流程,在内核中文件系统的选择是根据文件路径来进行自动选择的,因此在整个文件系统的切换过程中操作的路径也会发生变化。

  1. 在用户态命令行下执行命令mkdir /sdcard/testdir,会以系统调用的方式,触发内核处理函数do_mkdirat,此时根据路径判断是FUSE文件系统,所以会通过vfs_mkdir2调用到fuse_mkdir。
  2. 在fuse_mkdir里面会通过/dev/fuse节点,触发User空间FUSE文件系统的处理函数do_mkdir,调用FuseDeamon注册的pf_mkdir,在pf_mkdir里面会执行mkdir /storage/emulated/0/testdir。
  3. 内核处理函数do_mkdirat,此时判断是sdcardfs文件系统(这块还不太明白具体是如何确定的,因为看路径和FUSE是一样的,不过可以看出FUSE是在SDCARDFS之上的),所以会通过vfs_mkdir2调用到sdcardfs_mkdir,在sdcardfs_mkdir里面会通过vfs_mkdir2调用底层文件系统F2FS,执行f2fs_mkdir完成实际的文件夹创建操作。
  4. 实际的文件夹创建操作完成后,调用函数会依次返回,最后do_mkdirat返回,触发用户空间的mkdir /storage/emulated/0/testdir的返回。
  5. 用户空间的FUSE文件系统的do_mkdir会返回,触发内核空间的fuse_mkdir函数返回,依次返回最后是do_mkdirat返回。
  6. 内核处理函数do_mkdirat返回触发了mkdir /sdcard/testdir的返回,整个处理流程结束。从这个过程可以看到文件系统的切换是从FUSE-->SDCARDFS-->F2FS-->SDCARDFS-->FUSE。

索引

回首页

参考文献

jishuin.proginn.com/p/763bfbd30...

FUSE 内核实现代码分析(一) 初始化_sunedy的专栏-CSDN博客

FUSE 内核实现代码分析(二) 队列管理_sunedy的专栏-CSDN博客

相关推荐
百锦再20 分钟前
Android Studio开发 SharedPreferences 详解
android·ide·android studio
青春给了狗32 分钟前
Android 14 修改侧滑手势动画效果
android
CYRUS STUDIO39 分钟前
Android APP 热修复原理
android·app·frida·hotfix·热修复
火柴就是我2 小时前
首次使用Android Studio时,http proxy,gradle问题解决
android
limingade2 小时前
手机打电话时电脑坐席同时收听对方说话并插入IVR预录声音片段
android·智能手机·电脑·蓝牙电话·电脑打电话
浩浩测试一下2 小时前
计算机网络中的DHCP是什么呀? 详情解答
android·网络·计算机网络·安全·web安全·网络安全·安全架构
青春给了狗4 小时前
Android 14 系统统一修改app启动时图标大小和圆角
android
pengyu4 小时前
【Flutter 状态管理 - 柒】 | InheritedWidget:藏在组件树里的"魔法"✨
android·flutter·dart
居然是阿宋6 小时前
Kotlin高阶函数 vs Lambda表达式:关键区别与协作关系
android·开发语言·kotlin
凉、介6 小时前
PCI 总线学习笔记(五)
android·linux·笔记·学习·pcie·pci