一、Fuse框架及流程简介
Fuse(Filesystem in Userspace)就是用户空间的文件系统。传统的文件系统一般在内核中实现,而Fuse的出现让非内核开发者开发自己的文件系统成为可能。
对于开发者而言,Fuse更多是一个开发框架。这个框架主要分为三个部分:内核模块、Fuse库(libfuse)和Fuse文件系统守护进程。它们之间以及其上下游的交互关系如下图所示:
结合上图,一个简单的Fuse访问处理流程如下:
1、应用在Fuse文件系统上执行一些系统调用;
2、VFS将这些操作路由至Fuse driver;
3、Fuse driver创建一个Fuse请求结构体,并把请求保存在请求队列中,此时执行操作的进程会被阻塞;
4、Fuse文件系统守护进程通过读取/dev/fuse将请求从内核队列中取出,并进行处理;
5、当请求处理完成后,Fuse文件系统守护进程会将响应写回/dev/fuse;
6、Fuse driver把请求标记为完成状态,并最终唤醒用户进程。
二、Ceph文件系统Fuse客户端初始化流程
Ceph文件系统客户端(ceph-fuse)依赖libfuse项目,在初始化的流程中很多都是调用的libfuse提供的接口:
|- main (ceph_fuse.cc)
|-global_init (global/global_init.cc) // 生成ceph context
|-Preforker::prefork (common/Preforker.h) // 成为守护进程
|-MonClient::build_initial_monmap (mon/MonClient.cc) // 初始化mon client及monmap
|-Messenger::create_client_messenger (msg/Messenger.cc) // 创建client messenger
|-CephFuse::init (client/fuse_ll.cc) // 初始化CephFuse对象
|-CephFuse::Handle::init (client/fuse_ll.cc) // 初始化CephFuse::Handle对象
|-fuse_parse_cmdline (libfuse api) // 解析libfuse需要的参数
|-Messenger::start (msg/Messenger.cc) // 启动messenger线程,开始接收消息
|-StandaloneClient::init (client/Client.cc) // 初始化client,用来收发用户I/O请求
|-SafeTimer::init (common/Timer.cc) // 初始化client的定时器
|-ObjecterCacher::init (osdc/ObjecterCacher.cc) // 启动object cacher(对象缓存管理)
|-Objecter::init (osdc/Objecter.cc) // 初始化objecter并启动(objecter是跟osd打交道的client)
|-Client::mount (client/Client.cc) // 与mds建立交互
|-Client::subscribe_mdsmap (client/Client.cc) // 与mon完成认证,并订阅mdsmap
|-Client::tick (client/Client.cc) // 启动client定时任务
|-Client::make_request (client/Client.cc) // 发送请求给mds,获取mount目录属性
|-CephFuse::start (client/fuse_ll.cc)
|-CephFuse::Handle::start (client/fuse_ll.cc)
|-fuse_mount (libfuse api) // 调用libfuse接口完成目录挂载,此处创建一个通道,与/dev/fuse交互
|-fuse_lowlevel_new (libfuse api) // 调用libfuse接口创建lowlevel fuse session,其中需传入参数fuse_ll_oper(函数注册,包括各种posix接口的用户态实现)
|-fuse_session_add_chan(se, ch) // 调用libfuse接口将目录挂载时创建的通道与lowlevel fuse session关联
|-CephFuse::loop (client/fuse_ll.cc)
|-CephFuse::Handle::loop (client/fuse_ll.cc)
|-fuse_session_loop_mt (libfuse api) // 调用libfuse接口,开始监听处理I/O请求
三、Ceph文件系统Fuse客户端请求处理流程
3.1、元数据访问请求处理
以用户在cephfs挂载目录下执行mkdir命令为例,完成内核及libfuse的处理流程后,libfuse将调用到ceph-fuse通过fuse_lowlevel_new注册的mkdir接口用户态实现:fuse_ll_mkdir,核心流程如下:
|-fuse_ll_mkdir (client/fuse_ll.cc)
|-Client::ll_mkdir (client/Client.cc)
|-Client::_mkdir(client/Client.cc)
|-…… // 一系列的权限、配额等检查
|-Client::make_request (client/Client.cc) // 发送请求给mds,执行创建目录操作
|-fuse_reply_entry (libfuse api) // 成功则调用libfuse接口返回结果,包含目录entry
其中make_request方法将实现向MDS发送请求,流程如下:
|-Client::make_request (client/Client.cc)
|-Client::choose_target_mds (client/Client.cc)
|-Client::_get_or_open_mds_session (client/Client.cc) // 与选中的mds未有open状态的session时,将尝试打开一个session,有则跳过此步骤
|-Client::_open_mds_session (client/Client.cc)
|-Connection::send_message2 (msg/Connection.h) // 向mds打开session消息
|-Client::wait_on_context_list (client/Client.cc) // 尝试打开一个session后,对于刚打开的处于opening状态的session,将等待其变更为open状态,如session已处于open状态则跳过此步骤
|-Client::send_request (client/Client.cc) // 向mds发送业务请求
|-Cond::Wait (common/Cond.h) // 等待mds响应
|-Client::put_request (client/Client.cc)
3.2、数据读写请求处理
以用户在cephfs挂载目录下写入文件为例,完成内核及libfuse的处理流程后,libfuse将调用到ceph-fuse通过fuse_lowlevel_new注册的write接口用户态实现:fuse_ll_write,核心流程如下:
|-fuse_ll_write (client/fuse_ll.cc)
|-Client::ll_write (client/fuse_ll.cc)
|-Client::_write (client/fuse_ll.cc)
|-…… // 一系列的权限、配额等检查
|-ObjectCacher::file_write (osdc/ObjectCacher.h)
|-Objecter::write_trunc (osdc/Objecter.h)
|-Objecter::op_submit (osdc/Objecter.cc)
|-Objecter::_op_submit_with_budget (osdc/Objecter.cc)
|-Objecter::_op_submit (osdc/Objecter.cc)
|-Objecter::_send_op (osdc/Objecter.cc)
|-PrimaryLogPG::send_message (osd/PrimaryLogPG.h) // 发送对象写消息给osd
|-fuse_reply_write (libfuse api) // 成功则调用libfuse接口返回结果,包含写入的字节数