深入分析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博客

相关推荐
HerayChen17 分钟前
HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac
android·macos·智能手机
顾北川_野18 分钟前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
hairenjing112320 分钟前
在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序
android·人工智能·windows·macos·智能手机
小黄人软件1 小时前
android浏览器源码 可输入地址或关键词搜索 android studio 2024 可开发可改地址
android·ide·android studio
dj15402252031 小时前
group_concat配置影响程序出bug
android·bug
周全全2 小时前
MySQL报错解决:The user specified as a definer (‘root‘@‘%‘) does not exist
android·数据库·mysql
- 羊羊不超越 -2 小时前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
wk灬丨3 小时前
Android Kotlin Flow 冷流 热流
android·kotlin·flow
千雅爸爸3 小时前
Android MVVM demo(使用DataBinding,LiveData,Fresco,RecyclerView,Room,ViewModel 完成)
android