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

Fuse内核分析

Fuse内核入口

fuse_init是FUSE文件系统内核部分的入口

module_init(fuse_init);

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);




        。。。




}
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的执行。

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,





};
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是用到最多的结构体,所有的操作都是通过这个结构体来完成的







/**





 * 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为例来看一下文件系统的切换流程,在内核中文件系统的选择是根据文件路径来进行自动选择的,因此在整个文件系统的切换过程中操作的路径也会发生变化。

image.png

  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博客

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYG4wsCh' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片