FuseDaemon分析
上文已经介绍了在Android 11的FUSE文件系统和之前的FUSE文件系统有很多不一样,在Android 11整个FUSE文件系统的用户态Daemon是在MediaProvider里面实现的,是用JNI来实现的。而FUSE文件系统的核心代码的实现都是在libfuse中,由FuseDaemon来调用的。
MediaProvider的代码路径是packages/providers/MediaProvider
libfuse的代码路径是external/libfuse
FuseDaemon主要数据结构
Figure 2-1 FuseDaemon主要数据结构关系图
Figure 2-1给出了在FuseDaemon里面有一些主要的数据结构和相互关系,为了简化图中并没有列出所有的成员变量,只是把主要的成员列了出来。
FuseDaemon
FuseDaemon是JNI层的一个类,开机MediaProvider就会为每一个用户(u0, u10等),创建一个类的实例,直到关机。Fuse文件系统的用户态Daemon入口就是FuseDaemon::Start。
主要成员变量
c
MediaProviderWrapper mp; ---用于JNI和JAVA层通信
std::atomic_bool active; ---FuseDaemon状态标志
struct ::fuse* fuse; ---指向struct fuse实例的指针
struct fuse
struct fuse是JNI里面定义的和FUSE文件系统相关的一个结构体,只有一个实例,在FuseDaemon::Start函数里面初始化。
主要成员变量
lua
MediaProviderWrapper* mp; ---指向MediaProviderWrapper的指针
struct fuse_session* se; ---指向fuse_session的指针
char* zero_addr; ---指向一块全0的区域,用来加速fuse的读操作
std::atomic_bool* active; ---指向FuseDaemon状态标志的指针
struct fuse_session
struct fuse_session是libfuse里面的结构体,保存fuse文件系统的整体信息,只有一个实例,在FuseDaemon::Start函数里面初始化。
主要成员变量
arduino
struct fuse_req list; ---普通request链表
struct fuse_req interrupts; ---interrupts request链表
struct fuse_notify_req notify_list; ---notify request链表
void *userdata; ---指向struct fuse的指针
pthread_key_t pipe_key; ---多线程私有数据,用来保存fuse_ll_pipe结构,加速功能使用
volatile int exited; ---fuse session结束标志
struct fuse_lowlevel_ops op; ---底层文件系统操作函数接口
struct fuse_mt
struct fuse_mt是FUSE多线程工作模式的总的结构体,只有一个实例,在fuse_session_loop_mt函数里面初始化。
主要成员变量
sql
int numworker; ---fuse_worker数目
int numavail; ---可用线程数目
struct fuse_session *se; ---指向fuse_session的指针
struct fuse_worker main; ---fuse_worker链表头
sem_t finish; ---结束信号量标志
int exit; ---退出标志
int clone_fd; ---/dev/fuse节点clone的fd
int max_idle; ---最大idle线程数目
struct fuse_worker
struct fuse_worker是一个链表节点数据结构,每次执行fuse_loop_start_thread函数时会初始化一个实例,所有的实例都会添加到fuse_mt->main链表中。
主要成员变量
lua
struct fuse_worker *prev; ---双向链表指针
struct fuse_worker *next; ---双向链表指针
pthread_t thread_id; ---线程ID
size_t bufsize; ---Buffer大小
struct fuse_buf fbuf; ---数据Buffer结构体
struct fuse_chan *ch; ---clone fd的结构体
struct fuse_mt *mt; ---指向fuse_mt实例的指针
struct fuse_buf
struct fuse_buf是一个Fuse数据Buffer的结构体,是fuse_worker结构体里面的一个变量,所以也是一个线程就对应一个实例。
arduino
size_t size; ---data size
enum fuse_buf_flags flags; ---buffer flag
void *mem; ---内存地址,当FUSE_BUF_IS_FD没有设置时有效
int fd; ---fd,当FUSE_BUF_IS_FD设置时有效
off_t pos; ---文件offset, 当FUSE_BUF_FD_SEEK设置时有效
FuseDaemon整体软件流程
Figure 2-2 FuseDaemon软件流程图
FuseDaemon::Start
JNI层FuseDaemon类是FUSE文件系统在用户空间的Daemon实现,开机后MediaProvider的Java层代码会调用JNI层代码,启动FuseDaemon,FuseDaemon的主要实现就是FuseDaemon::Start函数,这就是FuseDaemon的入口,这个函数做了一些初始化操作后,就调用了fuse_session_loop_mt函数,正常情况下这个函数一直不会退出。
rust
void FuseDaemon::Start(android::base::unique_fd fd, const std::string& path) {
...
// fuse_default初始化
struct fuse fuse_default(path);
fuse_default.mp = ∓
// fuse_default is stack allocated, but it's safe to save it as an instance variable because
// this method blocks and FuseDaemon#active tells if we are currently blocking
fuse = &fuse_default;
// Used by pf_read: redacted ranges are represented by zeroized ranges of bytes,
// so we mmap the maximum length of redacted ranges in the beginning and save memory allocations
// on each read.
fuse_default.zero_addr = static_cast<char*>(mmap(
NULL, MAX_READ_SIZE, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, /*fd*/ -1, /*off*/ 0));
...
// Custom logging for libfuse
if (android::base::GetBoolProperty("persist.sys.fuse.log", false)) {
fuse_set_log_func(fuse_logger);
}
// fuse_session实例初始化
struct fuse_session
* se = fuse_session_new(&args, &ops, sizeof(ops), &fuse_default);
if (!se) {
PLOG(ERROR) << "Failed to create session ";
return;
}
fuse_default.se = se;
fuse_default.active = &active;
se->fd = fd.release(); // libfuse owns the FD now
se->mountpoint = strdup(path.c_str());
// Single thread. Useful for debugging
// fuse_session_loop(se);
// Multi-threaded
LOG(INFO) << "Starting fuse...";
// fuse文件系统处理,这个函数一直不会退出
fuse_session_loop_mt(se, &config);
return;
}
fuse_session_loop_mt
在本例中FUSE_USE_VERSION是34,所以fuse_session_loop_mt对应的函数是fuse_session_loop_mt_32。fuse_session_loop_mt_32做的主要就是初始化fuse_mt的实例,然后启动一个FUSE文件系统处理线程,然后就是通过等待一个信号量的方式一直挂起,直到关机或者FUSE文件系统出现错误。
ini
FUSE_SYMVER(".symver fuse_session_loop_mt_32,fuse_session_loop_mt@@FUSE_3.2");
int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config)
{
int err;
struct fuse_mt mt;
struct fuse_worker *w;
// fuse_mt 实例初始化
memset(&mt, 0, sizeof(struct fuse_mt));
mt.se = se;
mt.clone_fd = config->clone_fd;
mt.error = 0;
mt.numworker = 0;
mt.numavail = 0;
mt.max_idle = config->max_idle_threads;
mt.main.thread_id = pthread_self();
mt.main.prev = mt.main.next = &mt.main;
sem_init(&mt.finish, 0, 0);
fuse_mutex_init(&mt.lock);
pthread_mutex_lock(&mt.lock);
// 启动一个FUSE处理线程
err = fuse_loop_start_thread(&mt);
pthread_mutex_unlock(&mt.lock);
if (!err) {
/* sem_wait() is interruptible */
// 通过等一个信号量一直等待, 正常情况下不会退出
while (!fuse_session_exited(se))
sem_wait(&mt.finish);
pthread_mutex_lock(&mt.lock);
for (w = mt.main.next; w != &mt.main; w = w->next)
pthread_cancel(w->thread_id);
mt.exit = 1;
pthread_mutex_unlock(&mt.lock);
while (mt.main.next != &mt.main)
fuse_join_worker(&mt, mt.main.next);
err = mt.error;
}
pthread_mutex_destroy(&mt.lock);
sem_destroy(&mt.finish);
if(se->error != 0)
err = se->error;
fuse_session_reset(se);
return err;
}
fuse_loop_start_thread
fuse_loop_start_thread的功能就是分配一个fuse_worker的实例,然后启动一个线程,把fuse_worker的实例作为参数传给线程处理函数fuse_do_work
ini
static int fuse_loop_start_thread(struct fuse_mt *mt)
{
int res;
// 分配一个新的struct fuse_worker并进行初始化
struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
if (!w) {
fuse_log(FUSE_LOG_ERR, "fuse: failed to allocate worker structure\n");
return -1;
}
memset(w, 0, sizeof(struct fuse_worker));
w->fbuf.mem = NULL;
w->mt = mt;
w->ch = NULL;
if (mt->clone_fd) {
w->ch = fuse_clone_chan(mt);
if(!w->ch) {
/* Don't attempt this again */
fuse_log(FUSE_LOG_ERR, "fuse: trying to continue "
"without -o clone_fd.\n");
mt->clone_fd = 0;
}
}
// 启动FUSE处理线程,fuse_worker结构体实例作为参数传入
res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
if (res == -1) {
fuse_chan_put(w->ch);
free(w);
return -1;
}
list_add_worker(w, &mt->main);
mt->numavail ++;
mt->numworker ++;
return 0;
}
fuse_do_work
fuse_do_work是FUSE文件系统的核心工作线程,完成FUSE文件系统相关的所有命令的处理。简单说来就是从/dev/fuse节点读取数据,解析数据,再把处理结果回写到/dev/fuse节点。fuse_do_work里面有一个很巧妙的设计,就是在发现没有可用的工作线程后,会调用fuse_loop_start_thread来创建一个新的线程,而在空闲进程超过max_idle(本例中是10)的时候,则会结束当前的线程。因此在FUSE文件系统中如果操作很繁忙的时候FUSE工作线程的数目会快速增长,然后再空闲下来的时候仍然保持max_idle个线程,这样既充分的利用多线程来提高峰值处理速度,又保证了不会反复进行线程的创建和删除。
markdown
static void *fuse_do_work(void *data)
{
struct fuse_worker *w = (struct fuse_worker *) data;
struct fuse_mt *mt = w->mt;
// 判读FUSE结束标志,只有关机或者出现错误的时候才会设置,正常情况下一直循环
while (!fuse_session_exited(mt->se)) {
int isforget = 0;
int res;
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
// 分配receive_buf,并从/dev/fuse读取数据,如果没有数据到来会在阻塞在read函数
res = fuse_session_receive_buf_int(mt->se, &w->fbuf, w->ch);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
if (res == -EINTR)
continue;
if (res <= 0) {
if (res < 0) {
fuse_session_exit(mt->se);
mt->error = res;
}
break;
}
pthread_mutex_lock(&mt->lock);
if (mt->exit) {
pthread_mutex_unlock(&mt->lock);
return NULL;
}
/*
* This disgusting hack is needed so that zillions of threads
* are not created on a burst of FORGET messages
*/
if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) {
struct fuse_in_header *in = w->fbuf.mem;
if (in->opcode == FUSE_FORGET ||
in->opcode == FUSE_BATCH_FORGET)
isforget = 1;
}
if (!isforget)
mt->numavail--;
// 如果现在没有可用的线程,则新建一个FUSE的工作线程
if (mt->numavail == 0){
fuse_loop_start_thread(mt);
}
pthread_mutex_unlock(&mt->lock);
// 完成了Fuse基本命令的解析处理,并把结果写入/dev/fuse节点
fuse_session_process_buf_int(mt->se, &w->fbuf, w->ch);
pthread_mutex_lock(&mt->lock);
if (!isforget)
mt->numavail++;
// 如果当前可用线程数大于max_idle,则结束当前这个工作线程
if (mt->numavail > mt->max_idle) {
if (mt->exit) {
pthread_mutex_unlock(&mt->lock);
return NULL;
}
list_del_worker(w);
mt->numavail--;
mt->numworker--;
pthread_mutex_unlock(&mt->lock);
pthread_detach(w->thread_id);
free(w->fbuf.mem);
fuse_chan_put(w->ch);
free(w);
return NULL;
}
pthread_mutex_unlock(&mt->lock);
}
sem_post(&mt->finish);
return NULL;
}