从linux内核角度谈谈fuse用户态文件系统

网上介绍fuse的文章不少,但是感觉大部分都是在应用层介绍fuse工作原理,对实际调试fuse模块相关问题感觉帮助不大。本文以一个fuse hello例子为切入点,从内核角度谈谈fuse应用层与内核层的交互细节,同时还介绍fuse内核模块的关键代码

先从 github下载fuse-2.9.8源码,这个版本编译起来很方便,./congfigure、make、make install即可完成编译安装fuse。解压后的源码路径在 /mnt/hgfs/share/fuse-2.9.8/。cd example,找到 fuse 源码里经典的 hello demo,简单查看一下源码

[root@localhost example]# cat hello.c
static const char *hello_str = "Hello World!\n";
static const char *hello_path = "/hello";
static int hello_getattr(const char *path, struct stat *stbuf)
{
        int res = 0;

        memset(stbuf, 0, sizeof(struct stat));
        if (strcmp(path, "/") == 0) {
                stbuf->st_mode = S_IFDIR | 0755;
                stbuf->st_nlink = 2;
        } else if (strcmp(path, hello_path) == 0) {
                stbuf->st_mode = S_IFREG | 0444;
                stbuf->st_nlink = 1;
                stbuf->st_size = strlen(hello_str);
        } else
                res = -ENOENT;

        return res;
}

static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
                         off_t offset, struct fuse_file_info *fi)
{
        (void) offset;
        (void) fi;

        if (strcmp(path, "/") != 0)
                return -ENOENT;

        filler(buf, ".", NULL, 0);
        filler(buf, "..", NULL, 0);
        filler(buf, hello_path + 1, NULL, 0);

        return 0;
}static int hello_open(const char *path, struct fuse_file_info *fi)
{
        if (strcmp(path, hello_path) != 0)
                return -ENOENT;

        if ((fi->flags & 3) != O_RDONLY)
                return -EACCES;

        return 0;
}
//fuse读只是向传入的buf复制字符串"Hello World!\n",这就是read该 fuse文件系统的文件返回的数据
static int hello_read(const char *path, char *buf, size_t size, off_t offset,
                      struct fuse_file_info *fi)
{
        size_t len;
        (void) fi;
        if(strcmp(path, hello_path) != 0)
                return -ENOENT;

        len = strlen(hello_str);
        if (offset < len) {
                if (offset + size > len)
                        size = len - offset;
                memcpy(buf, hello_str + offset, size);
        } else
                size = 0;

        return size;
}

static struct fuse_operations hello_oper = {
        .getattr        = hello_getattr,
        .readdir        = hello_readdir,
        .open          = hello_open,
        .read           = hello_read,
};

int main(int argc, char *argv[])
{
        return fuse_main(argc, argv, &hello_oper, NULL);
}

下边开始该demo涉及的fuse文件系统挂载

[root@localhost  example]#mkdir fuse_dir
[root@localhost  example]#./hello fuse_dir

之后就会执行fuse库的入口函数fuse_main,创建几个线程,同时将该demo 的fuse文件系统挂载到fuse_dir目录

[root@localhost  fuse-2.9.8]#cd   fuse_dir
[root@localhost  fuse_dir]#ls
[root@localhost  fuse_dir]#hello

该文件系统只有一个hello文件

[root@localhost  fuse_dir]# cat hello
Hello World!

显然 hello 文件内容是字符串” Hello World!”。本文主要演示cat  hello命令与fuse内核模块的交互过程。了解过fuse文件系统的小伙伴,应该知道cat hello实际是调用fuse demo应用层注册struct fuse_operations的read函数hello_read(),这个read函数返回” Hello World!”字符串。所以cat  hello最后read的数据是” Hello World!”字符串。strace 跟踪一下

[root@localhost  fuse_dir]# strace cat hello
……….
//open  fuse 文件系统里的hello文件
open("hello", O_RDONLY)                 = 3
fstat(3, {st_mode=S_IFREG|0444, st_size=13, ...}) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
//read "Hello World!\n"字符串
read(3, "Hello World!\n", 65536)        = 13
//写入标准输出,之后就会在终端显示出来
write(1, "Hello World!\n", 13Hello World!
)          = 13
read(3, "", 65536)                      = 0
close(3)                              = 0
…………….

前文提过,挂载fuse hello那个文件系统时,在执行fuse库的fuse_main函数后,创建了几个线程,在读取fuse文件时负责与内核fuse模块的交互。

[root@localhost ~]# ps -lef | grep fuse
1 S root     19903     1  0  80   0 - 58115 futex_ 1月02 ?       00:00:00 /mnt/hgfs/share/fuse-2.9.8/example/.libs/lt-hello fuse_dir

可以看到创建的主进程是PID时19903,进程名字lt-hello。每一个fuse文件系统挂载到某个目录,最后都有一个这样的进程。它是fuse库创建的,当我们读写fuse文件系统里的文件,回调fuse文件系统注册的struct fuse_operations结构的read、write函数,然后把数据传回给读写fuse文件系统里的文件的进程。看下这个进程有哪些线程?

[root@localhost ~]# ls /proc/19903/task/
19903  19904  19905  23583
[root@localhost ~]# cat /proc/19903/task/19904/comm
lt-hello
[root@localhost ~]# cat /proc/19903/task/19905/comm
lt-hello
[root@localhost ~]# cat /proc/19903/task/23583/comm
lt-hello

19903是fuse库创建的主进程,19904、19905、23583是它的三个线程,真正干活的也是这3个线程,线程名字也是lt-hello。主进程19903跟踪过,一直在休眠。19904、19905、23583这三个线程一般情况处于休眠状态,当读写fuse文件时,才会工作起来,这里姑且称为fuse库线程。执行strace跟踪一下

[root@localhost ~]# strace -p 19904
strace: Process 19904 attached
read(3,
[root@localhost ~]# strace -p 19905
strace: Process 19905 attached
read(3,
[root@localhost ~]# strace -p 23583
strace: Process 23583 attached
read(3,

随便找一个查看一下内核栈回溯,都是执行fuse_dev_do_read()->request_wait()后在fc->waitq等待队列头休眠

[root@localhost ~]# cat  /proc/19904/stack
[<ffffffffc06a56a4>] fuse_dev_do_read.isra.18+0x274/0x870 [fuse]
[<ffffffffc06a5f7d>] fuse_dev_read+0x7d/0xa0 [fuse]
[<ffffffffa7641b33>] do_sync_read+0x93/0xe0
[<ffffffffa764256f>] vfs_read+0x9f/0x170
[<ffffffffa764342f>] SyS_read+0x7f/0xf0
[<ffffffffa7b7706b>] tracesys+0xa3/0xc9
[<ffffffffffffffff>] 0xffffffffffffffff

实际测试表明,19904、19905、23583这三个fuse库线程,在fuse数据传输环节,只会执行read、writev两个系统调用,平时它们都是执行read系统调用后休眠。另外,本文是演示cat  hello命令执行后,fuse内核模块是怎么工作的,这里有牵涉到open、read、writev  等系统调用。如果能跟踪这几个系统调用的内核层层函数执行流程,对我们了解fuse文件系统内核fuse模块的工作细节应该是有帮助的。该怎么跟踪呢?用sytemtap捕捉这些内核函数抓取信息是个好的选择。同时执行下边3个命令,跟踪这3个fuse库线程的系统调用

[root@localhost ~]# strace -p 19904
strace: Process 19904 attached
read(3,

[root@localhost ~]# strace -p 19905
strace: Process 19905 attached
read(3,

[root@localhost ~]# strace -p 23583
strace: Process 23583 attached
read(3,

执行cat  hello 触发fuse文件系统读

[root@localhost  fuse_dir]# cat hello
Hello World!

strace跟踪3个fuse库线程系统调用打印是

root@localhost ~]# strace -p 19904
strace: Process 19904 attached
read(3, ".\0\0\0\1\0\0\0\337\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 135168) = 46
writev(3, [{"\220\0\0\0\0\0\0\0\337\0\0\0\0\0\0\0", 16}, {"\2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0"..., 128}], 2) = 144
read(3, "@\0\0\0\22\0\0\0\342\0\0\0\0\0\0\0\2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 135168) = 64
writev(3, [{"\20\0\0\0\0\0\0\0\342\0\0\0\0\0\0\0", 16}], 1) = 16
read(3,

[root@localhost ~]# strace -p 19905
strace: Process 19905 attached
read(3, "0\0\0\0\16\0\0\0\340\0\0\0\0\0\0\0\2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 135168) = 48
writev(3, [{" \0\0\0\0\0\0\0\340\0\0\0\0\0\0\0", 16}, {"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16}], 2) = 32
read(3,

[root@localhost ~]# strace -p 23583
strace: Process 23583 attached
read(3, "P\0\0\0\17\0\0\0\341\0\0\0\0\0\0\0\2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 135168) = 80
//cat hello后,触发执行该fuse文件系统注册的struct fuse_operations的read函数,这个read函数只是向
//传入的buf复制"Hello World!\n"字符串,然后再执行writev系统调用把这个字符串写入fuse内核模块。
//在fuse内核模块里,把"Hello World!\n"传递给cat hello 进程,这即是cat hello读取的的文件数据。
writev(3, [{"\35\0\0\0\0\0\0\0\341\0\0\0\0\0\0\0", 16}, {"Hello World!\n", 13}], 2) = 29
read(3,

可以发现,这3个fuse库线程最后又执行read函数进入休眠状态,等待下次有fuse读写行为再被唤醒。cat  hello命令执行后,这3个fuse库线程以及cat hello进程在fuse内核模块的交互是怎么一个过程呢?用systemtap跟踪这个过程涉及的内核sys_open、sys_read、sys_writev以及fuse相关的函数,来看看整个过程。先根据我的调试结果,把关键流程先列一下:

1  cat  hello进程首先执行open系统调用,内核关键流程是:sys_open->do_sys_open->do_filp_open->path_openat->do_last->lookup_fast->fuse_dentry_revalidate ,这个流程主要是open  hello文件的前期lookup工作。open一个文件,在内核里肯定需要先vfs层探测一下这个文件是否存在等等。

2 cat  hello进程调用fuse_open函数。内核流程是:do_sys_open->do_filp_open->path_openat->do_last->vfs_open->do_dentry_open->fuse_open,这是调用fuse内核模块的open函数。open过程感觉没不是重点,重点在read过程,接着看。

3  cat  hello 进程执行read系统系统开始读取数据。这里是重点,执行sys_open-> do_sync_read-> fuse_file_aio_read->generic_file_aio_read->page_cache_sync_readahead->ondemand_readahead->__do_page_cache_readahead->fuse_readpages,调用到fuse内核模块的fuse_readpages函数,正式开始读数据了。在该函数中,首先分配一个struct fuse_req,这里就简称req。req下文多次提到,需记住。

4 接着从fuse_readpages进入read_cache_pages->fuse_readpages_fill函数。在fuse_readpages_fill函数执行fuse_send_readpages函数,然后还设置了req.in、req.out、req->pages[]等成员。req->pages[]需要注意,它应该保存的应该是VFS层传进来将来保存本次read要读取的” Hello World!”字符串的内存page。还有一点需注意,该函数中执行fuse_read_fill函数重点设置了req->in.h.opcode=FUSE_READ,即fuse操作码是读,表示本次是fuse 读。fuse_send_readpages->fuse_request_send->__fuse_request_send函数中,执行queue_request函数,把req加入 fc->pending链表,同时执行wake_up(&fc->waitq)唤醒在fc->waitq等待队列休眠的PID是23583的fuse库线程,线程名字是lt-hello。这个fuse库线程前文介绍过,是主要干活的。之后cat  hello 进程进程执行request_wait_answer函数,在req->waitq等待队列休眠。

5 PID是23583 的lt-hello线程被唤醒,重头戏来了,接下来是PID是23583 的lt-hello线程的表演时刻。该lt-hello线程被唤醒前的是执行了read系统调用,函数流程是sys_read->vfs_read->do_sync_read->fuse_dev_read->fuse_dev_do_read->request_wait,就是在request_wait函数中在fc->waitq等待队列休眠的。然后lt-hello线程被cat hello进程唤醒,接着执行fuse_dev_do_read剩余的代码。执行req = list_entry(fc->pending.next, struct fuse_req, list)取出req,这个req就是第4步cat  hello进程在fuse_send_readpages函数,放入fc->pending 链表的req。

6 fuse_dev_do_read函数中,lt-hello线程接着执行fuse_copy_one(cs, &in->h, sizeof(in->h))把req->in的数据拷贝到cs->buf,cs->buf被设置指向了iov[0]-> iov_base对应的内存。iov[0]-> iov_base是什么?看下fuse_dev_read函数,就是lt-hello线程read系统调用传入的用户空间buf地址,就形如应用层read(fd,buf,sizeof(buf))这种buf。简单来说,req->in.h.opcode包含了cat  hello进程fuse 读操作的操作码,lt-hello线程重点是把这些数据复制到read系统调用的buf。将来lt-hello线程返回用户空间,根据req->in.h.opcode知道了cat  hello进程是要取hello这个fuse文件系统中的文件。在fuse_dev_do_read最后,执行list_move_tail(&req->list, &fc->processing)把req加入fc->processing链表

7 紧接着,PID是23583 的lt-hello线程调用应用层fuse文件系统注册struct fuse_operations的read函数,就是前文fuse  demo  hello.c文件的hello_read()函数,复制得到” Hello World!”字符串。然后lt-hello线程执行writev系统调用,把” Hello World!”字符串发送给fuse内核模块。需要强调的一点是这个writev系统调用与普通的write不同,它通过struct iovec结构一次发送多片内存的数据,回过头看下strace –p 23583跟踪该线程执行writev的系统调用情况,如下:

writev(3, [{"\35\0\0\0\0\0\0\0\341\0\0\0\0\0\0\0", 16}, {"Hello World!\n", 13}], 2) = 29

这是一次发送两片buf,第1片buf的数据是"\35\0\0\0\0\0\0\0\341\0\0\0\0\0\0\0",一共16字节数据。第2片buf数据是"Hello World!\n",一共13个字节,这才是cat  hello最终想要获取的数据。writev函数内核流程是:sys_writev->vfs_writev->do_readv_writev->do_sync_readv_writev->fuse_dev_write->fuse_dev_do_write

8 fuse_dev_do_write函数是另一个重点,lt-hello线程首先执行 fuse_copy_one(cs, &oh, sizeof(oh))把本次writev传递的用户空间第1片buf的数据数据复制到struct  fuse_out_header  oh。接着执行req = request_find(fc, oh.unique)从fc->processing链表取出req,这个req打印证实是第6步执行fuse_dev_do_read函数最后放入fc->processing链表的 req,这个req.page[]保存了cat  hello进程read系统调用传入的保存待读取的数据的内存page。接着req->out.h=oh,再执行copy_out_args(cs, &req->out, nbytes),这个函数最终把本次lt-hello线程writev系统调用传递第2片用户空间buf的数据即 "Hello World!\n"复制到req.page[]指定的内存page。就相当于把cat  hello进程read系统调用要读取的数据"Hello World!\n"保存的内核VFS层read指定的buf。最后,lt-hello线程执行request_end()函数,唤醒在req->waitq 等待队列上休眠的cat hello进程。

9  cat hello 进程从read系统调用返回,"Hello World!\n"字符串已经被保存到了read系统调用传入的用户空间buf。这个在VFS层read完成,我们重点介绍怎么从fuse内核模块获取"Hello World!\n"就行了,总之终于获取到了要读取的数据。然后cat hello就在终端打印"Hello World!\n"字符串。PID是23583 的lt-hello线程再次执行sys_read->vfs_read->do_sync_read->fuse_dev_read->fuse_dev_do_read->request_wait在fc->waitq等待队列休眠,等待下次cat hello等读取fuse文件系统中的文件再被唤醒

好了,终于把cat hello读取fuse文件系统中的文件的流程讲完了,挺啰嗦的。这里再简单介绍一下核心点。

  1. cat  hello命令 open  hello文件
  2. cat  hello进程执行read系统调用开始读取数据,设置fuse读的指令码,然后把保存本次read数据的内存page设置到req.page[]。接着唤醒PID是23583 的lt-hello线程,lt-hello线程得到fuse读的指令码,lt-hello线程返回应用层。
  3.  lt-hello线程根据fuse读指令码,执行应用层fuse文件系统注册struct fuse_operations的hello_read函数,获取到"Hello World!\n"字符串。
  4.  lt-hello线程执行writev系统调用把"Hello World!\n"字符串发送到fuse内核模块。该系统调用最后执行fuse_dev_do_write函数,把"Hello World!\n"字符串复制到req.page[]指定的内存page。就把"Hello World!\n"字符串复制到了cat  hello进程read系统调用VFS层传入的内存,cat  hello进程获取到了本次read系统调用要读取的数据。
  5. cat hello从read系统调用返回应用层,在终端打印"Hello World!\n"字符串。lt-hello线程执行read系统调用进入内核空间后,执行fuse_dev_do_read->request_wait后休眠,等待下次被唤醒。

如果想看更详细的fuse内核模块源码注释,看https://github.com/dongzhiyan-stack/kernel-code-comment。本文到这里结束了,有哪里描述有误的请指出,特别是fuse_dev_do_write函数中执行copy_out_args,向cat hello进程read系统调用传入内存page复制"Hello World!\n"字符串的过程,还有点疑惑。如果哪个小伙伴想看下使用systemtap调试fuse的过程,这里把调试代码和调试过程的打印单独贴下

[root@localhost ~]# cat  fuse.stp
#####################fuse模块 read、write、open###########
#fuse read
probe module("fuse").function("fuse_dev_do_read") 
{
    printf("%s %s %d fuse_conn:%p\n",ppfunc(),execname(),tid(),$fc)
}
probe module("fuse").function("fuse_dev_do_read").return
{
    printf("%s return %s %d reqsize:%d\n",ppfunc(),execname(),tid(),$return)
}
#fuse write
probe module("fuse").function("fuse_dev_do_write") 
{
    printf("%s %s %d fuse_conn:%p\n",ppfunc(),execname(),tid(),$fc)
}
probe module("fuse").function("fuse_dev_do_write").return
{
    printf("%s return %s %d nbytes:%d\n",ppfunc(),execname(),tid(),$return)
}
#fuse open
probe module("fuse").function("fuse_open") 
{
    printf("%s %s %d\n",ppfunc(),execname(),tid())
}
probe module("fuse").function("fuse_open").return
{
    printf("%s return %s %d\n",ppfunc(),execname(),tid())
}
#fuse read page
probe module("fuse").function("fuse_readpages")
{
    printf("%s %s %d\n",ppfunc(),execname(),tid())
}
probe module("fuse").function("fuse_readpages").return
{
    printf("%s return %s %d\n",ppfunc(),execname(),tid())
}
probe module("fuse").function("fuse_readpages_fill")
{
    printf("%s %s %d\n",ppfunc(),execname(),tid())
}
probe module("fuse").function("fuse_dev_write")
{
    if(tid() == 23583){#就是这个fuse库线程 lt-hello pid:23583发送最终的文件内容"Hello World!"
        iov1=$iov+0x10;#iov偏移16字节到iov1
        #printf("%s %s %d %s\n",ppfunc(),execname(),tid(),user_string($iov[1]->iov_base))---直接用user_string($iov[1]->iov_base)也可以
        printf("%s %s %d iov0_len:%d iov1_string:%s iov1_len:%d\n",ppfunc(),execname(),tid(),$iov->iov_len,user_string(@cast(iov1,"struct iovec")->iov_base),@cast(iov1,"struct iovec")->iov_len)
    }else{
        printf("%s %s %d\n",ppfunc(),execname(),tid())
    }
}

probe module("fuse").function("copy_out_args")
{
    printf("%s %s %d\n",ppfunc(),execname(),tid())
}

probe module("fuse").function("__fuse_get_req").return
{
    printf("%s return %s %d req:%p fuse_conn:%p for_background:%d\n",ppfunc(),execname(),tid(),$return,$fc,$for_background)
}
#probe module("fuse").function("request_find").return
#{
#   printf("%s return %s %d req:%p fuse_conn:%p\n",ppfunc(),execname(),tid(),$return,$fc)
#}
#probe kernel.function("fuse_dentry_revalidate")
#{
#    printf("%s %s %d\n",ppfunc(),execname(),tid())
#}

probe module("fuse").function("__fuse_request_send")
{
    printf("%s %s %d fuse_conn:%p req:%p queue_request()、request_wait_answer()\n",ppfunc(),execname(),tid(),$fc,$req)
}
probe module("fuse").function("queue_request")
{
    printf("%s %s %d fuse_conn:%p req:%p list_add_tail(&req->list, &fc->pending) wake_up(&fc->waitq)\n",ppfunc(),execname(),tid(),$fc,$req)
}
probe module("fuse").function("fuse_copy_one")
{
    printf("%s %s %d req:%p\n",ppfunc(),execname(),tid(),$cs->req)
}
probe module("fuse").function("request_end")
{
    printf("%s %s %d req:%p fuse_conn:%p\n",ppfunc(),execname(),tid(),$req,$fc)
}


###open、read、write、writev系统调用#######################################################
#系统调用 open
probe kernel.function("do_sys_open") 
{
    if(execname() == "lt-hello"  || execname() == "cat")
        printf("%s %s %d\n",ppfunc(),execname(),tid())
}
probe kernel.function("do_sys_open").return
{
    if(execname() == "lt-hello"  || execname() == "cat")
        printf("%s return %s %d\n\n",ppfunc(),execname(),tid())
}
#系统调用read
probe kernel.function("do_sync_read") 
{
    if(execname() == "lt-hello"  || execname() == "cat")
        printf("%s %s %d\n",ppfunc(),execname(),tid())
}
probe kernel.function("do_sync_read").return
{
    if(execname() == "lt-hello"  || execname() == "cat")
        printf("%s return %s %d\n\n",ppfunc(),execname(),tid())
}
#系统调用write
probe kernel.function("do_sync_write") 
{
    if(execname() == "lt-hello"  || execname() == "cat")
        printf("%s %s %d write ***%s***\n",ppfunc(),execname(),tid(),user_string($buf))
}
probe kernel.function("do_sync_write").return
{
    if(execname() == "lt-hello"  || execname() == "cat")
        printf("%s return %s %d\n\n",ppfunc(),execname(),tid())
}
#系统调用 writev
probe kernel.function("vfs_writev") 
{
    if(execname() == "lt-hello"  || execname() == "cat")
        printf("%s %s %d\n",ppfunc(),execname(),tid())
}
probe kernel.function("vfs_writev").return
{
    if(execname() == "lt-hello"  || execname() == "cat")
        printf("%s return %s %d\n\n",ppfunc(),execname(),tid())
}

##########休眠唤醒########################################################
#在fc->waitq 等待队列头休眠
probe kernel.function("add_wait_queue_exclusive")
{
    if(execname() == "lt-hello"  || execname() == "cat")
        printf("request_wait->%s %s %d wait fc->waitq:%p\n",ppfunc(),execname(),tid(),$q)
}
#在 req->waitq 等待队列头休眠
probe kernel.function("prepare_to_wait")
{
    if(execname() == "lt-hello"  || execname() == "cat")
        printf("%s %s %d wait req->waitq:%p\n",ppfunc(),execname(),tid(),$q)
}
#唤醒等待队列头q上的进程
probe kernel.function("__wake_up")
{
    if(execname() == "lt-hello"  || execname() == "cat")
        printf("%s %s %d wakeup waitq:%p\n",ppfunc(),execname(),tid(),$q)
}
#分配 req->waitq 等待队列头
probe kernel.function("__init_waitqueue_head")
{
    if(execname() == "lt-hello"  || execname() == "cat")
        printf("%s %s %d alloc req->waitq:%p\n",ppfunc(),execname(),tid(),$q)
}
#进程唤醒唤醒
probe kernel.function("try_to_wake_up")
{
    if(kernel_string($p->comm) == "lt-hello" || kernel_string($p->comm) == "cat"){
        printf("%s %s %d wake up %s %d\n",ppfunc(),execname(),tid(),kernel_string($p->comm),$p->pid);
        //print_backtrace();
        //printf("\n");
    }
}
//进程休眠
probe kernel.function("__schedule")
{
    if(execname() == "lt-hello" || execname() == "cat"){
        printf("%s %s %d\n",ppfunc(),execname(),tid());
        //print_backtrace();
        //printf("\n");
    }
}

stap –vg fuse.tap 后cat hello的打印是

//cat open系统调用开始
do_sys_open cat 11724
__init_waitqueue_head cat 11724 alloc req->waitq:0xffff927f161f0bd0
__fuse_get_req return cat 11724 req:0xffff927f161f0af0 fuse_conn:0xffff927f2e8da800 for_background:0
__fuse_request_send cat 11724 fuse_conn:0xffff927f2e8da800 req:0xffff927f161f0af0 queue_request()、request_wait_answer()
queue_request cat 11724 fuse_conn:0xffff927f2e8da800 req:0xffff927f161f0af0 list_add_tail(&req->list, &fc->pending) wake_up(&fc->waitq)
__wake_up cat 11724 wakeup waitq:0xffff927f2e8da868
try_to_wake_up cat 11724 wake up lt-hello 19904
prepare_to_wait cat 11724 wait req->waitq:0xffff927f161f0bd0
__schedule cat 11724
fuse_copy_one lt-hello 19904 req:0xffff927f161f0af0
fuse_copy_one lt-hello 19904 req:0xffff927f161f0af0
__schedule lt-hello 19904
try_to_wake_up strace 7076 wake up lt-hello 19904
__schedule lt-hello 19904
try_to_wake_up strace 7076 wake up lt-hello 19904
vfs_writev lt-hello 19904
fuse_dev_write lt-hello 19904
fuse_dev_do_write lt-hello 19904 fuse_conn:0xffff927f2e8da800
fuse_copy_one lt-hello 19904 req:0x0
fuse_copy_one lt-hello 19904 req:0xffff927f161f0af0
request_end lt-hello 19904 req:0xffff927f161f0af0 fuse_conn:0xffff927f2e8da800
__wake_up lt-hello 19904 wakeup waitq:0xffff927f161f0bd0
try_to_wake_up lt-hello 19904 wake up cat 11724
fuse_dev_do_write return lt-hello 19904 nbytes:144
vfs_writev return lt-hello 19904

__schedule lt-hello 19904
try_to_wake_up strace 7076 wake up lt-hello 19904
__schedule lt-hello 19904
try_to_wake_up strace 7076 wake up lt-hello 19904
do_sync_read lt-hello 19904
fuse_dev_do_read lt-hello 19904 fuse_conn:0xffff927f2e8da800
request_wait->add_wait_queue_exclusive lt-hello 19904 wait fc->waitq:0xffff927f2e8da868
__schedule lt-hello 19904
prepare_to_wait cat 11724 wait req->waitq:0xffff927f161f0bd0

//cat 调用 fuse_open
fuse_open cat 11724
__init_waitqueue_head cat 11724 alloc req->waitq:0xffff927f161f0bd0
__init_waitqueue_head cat 11724 alloc req->waitq:0xffff927f2f8c31d8
__init_waitqueue_head cat 11724 alloc req->waitq:0xffff927f161f0ef0
__fuse_get_req return cat 11724 req:0xffff927f161f0e10 fuse_conn:0xffff927f2e8da800 for_background:0
//-------分配req 0xffff927f161f0e10 
__fuse_request_send cat 11724 fuse_conn:0xffff927f2e8da800 req:0xffff927f161f0e10 queue_request()、request_wait_answer()
queue_request cat 11724 fuse_conn:0xffff927f2e8da800 req:0xffff927f161f0e10 list_add_tail(&req->list, &fc->pending) wake_up(&fc->waitq)
__wake_up cat 11724 wakeup waitq:0xffff927f2e8da868
try_to_wake_up cat 11724 wake up lt-hello 19905
prepare_to_wait cat 11724 wait req->waitq:0xffff927f161f0ef0
__schedule cat 11724
fuse_copy_one lt-hello 19905 req:0xffff927f161f0e10
fuse_copy_one lt-hello 19905 req:0xffff927f161f0e10
__schedule lt-hello 19905
try_to_wake_up strace 7078 wake up lt-hello 19905
__schedule lt-hello 19905
try_to_wake_up strace 7078 wake up lt-hello 19905
vfs_writev lt-hello 19905
fuse_dev_write lt-hello 19905
fuse_dev_do_write lt-hello 19905 fuse_conn:0xffff927f2e8da800
fuse_copy_one lt-hello 19905 req:0x0
fuse_copy_one lt-hello 19905 req:0xffff927f161f0e10
request_end lt-hello 19905 req:0xffff927f161f0e10 fuse_conn:0xffff927f2e8da800
__wake_up lt-hello 19905 wakeup waitq:0xffff927f161f0ef0
try_to_wake_up lt-hello 19905 wake up cat 11724
fuse_dev_do_write return lt-hello 19905 nbytes:32
vfs_writev return lt-hello 19905

__schedule lt-hello 19905
try_to_wake_up strace 7078 wake up lt-hello 19905
__schedule lt-hello 19905
try_to_wake_up strace 7078 wake up lt-hello 19905
do_sync_read lt-hello 19905
fuse_dev_do_read lt-hello 19905 fuse_conn:0xffff927f2e8da800
request_wait->add_wait_queue_exclusive lt-hello 19905 wait fc->waitq:0xffff927f2e8da868
__schedule lt-hello 19905
prepare_to_wait cat 11724 wait req->waitq:0xffff927f161f0ef0
fuse_open return cat 11724
//cat 系统调用open结束
do_sys_open return cat 11724

//cat 系统调用read开始
do_sync_read cat 11724
//cat read调用到 fuse_readpages函数
fuse_readpages cat 11724
__init_waitqueue_head cat 11724 alloc req->waitq:0xffff927f161f0ef0
//分配 req:0xffff927f161f0e10 ,设置 req->in.h.opcode=FUSE_READ 操作码
__fuse_get_req return cat 11724 req:0xffff927f161f0e10 fuse_conn:0xffff927f2e8da800 for_background:1
fuse_readpages_fill cat 11724
//cat进程 send req,把req加入 fc->pending 链表
queue_request cat 11724 fuse_conn:0xffff927f2e8da800 req:0xffff927f161f0e10 list_add_tail(&req->list, &fc->pending) wake_up(&fc->waitq)
__wake_up cat 11724 wakeup waitq:0xffff927f2e8da868
//cat 唤醒 lt-hello PID:23583 线程,唤醒前cat 进程在 fuse_dev_do_read 中休眠
try_to_wake_up cat 11724 wake up lt-hello 23583
//cat 进程从 fuse_readpages 返回
fuse_readpages return cat 11724
//cat 进程休眠
__schedule cat 11724

//lt-hello 23583 线程在 fuse_dev_do_read函数中 取出 req:0xffff927f161f0e10,向req.in复制到它read系统调用传入的
//buf中,这里边有fuse read操作码,之后fuse线程根据这个判断本次是fuse read读。
fuse_copy_one lt-hello 23583 req:0xffff927f161f0e10
fuse_copy_one lt-hello 23583 req:0xffff927f161f0e10
__schedule lt-hello 23583
try_to_wake_up strace 7080 wake up lt-hello 23583
__schedule lt-hello 23583
try_to_wake_up strace 7080 wake up lt-hello 23583

//这里边有个问题,没有捕捉到"do_sync_read return  lt-hello 23583",测试证明是必须得先do_sync_read
//函数执行前运行捕捉命令,才能捕捉到,systemtap的问题

//lt-hello PID:23583 线程执行vfs_writev系统调用开始
vfs_writev lt-hello 23583
//lt-hello PID:23583 线程执行vfs_writev发送"hello World!"字符串
fuse_dev_write lt-hello 23583 iov0_len:16 iov1_string:Hello World!
 iov1_len:13
fuse_dev_do_write lt-hello 23583 fuse_conn:0xffff927f2e8da800
fuse_copy_one lt-hello 23583 req:0x0
//从 fc->processing 链表 取出 req:0xffff927f161f0e10
req = request_find(fc, oh.unique)
//lt-hello PID:23583 writev req:0xffff927f161f0e10 发送字符串 
err = copy_out_args(cs, &req->out, nbytes)
request_end lt-hello 23583 req:0xffff927f161f0e10 fuse_conn:0xffff927f2e8da800
__wake_up lt-hello 23583 wakeup waitq:0xffff927f161f0ef0
__wake_up lt-hello 23583 wakeup waitq:0xffff927fbffa5bd8
//cat 进程被唤醒,开始读取数据
try_to_wake_up lt-hello 23583 wake up cat 11724
fuse_dev_do_write return lt-hello 23583 nbytes:29
//lt-hello PID:23583 线程执行vfs_writev系统调结束
vfs_writev return lt-hello 23583

__schedule lt-hello 23583
try_to_wake_up strace 7080 wake up lt-hello 23583
__schedule lt-hello 23583
try_to_wake_up strace 7080 wake up lt-hello 23583
do_sync_read lt-hello 23583
//lt-hello 23583 read系统调用后休眠,等待下次fuse 读被唤醒
fuse_dev_do_read lt-hello 23583 fuse_conn:0xffff927f2e8da800
request_wait->add_wait_queue_exclusive lt-hello 23583 wait fc->waitq:0xffff927f2e8da868
__schedule lt-hello 23583
//cat read系统调用结束
do_sync_read return cat 11724

__wake_up cat 11724 wakeup waitq:0xffff927f15c101c0
try_to_wake_up cat 11724 wake up cat 11724
__wake_up cat 11724 wakeup waitq:0xffff927f15c101c0
try_to_wake_up cat 11724 wake up cat 11724
__wake_up cat 11724 wakeup waitq:0xffff927f15c101c0
do_sync_read cat 11724
do_sync_read return cat 11724

__wake_up cat 11724 wakeup waitq:0xffff927f2f8c31d8
queue_request cat 11724 fuse_conn:0xffff927f2e8da800 req:0xffff927f161f0af0 list_add_tail(&req->list, &fc->pending) wake_up(&fc->waitq)
__wake_up cat 11724 wakeup waitq:0xffff927f2e8da868
try_to_wake_up cat 11724 wake up lt-hello 19904
fuse_copy_one lt-hello 19904 req:0xffff927f161f0af0
fuse_copy_one lt-hello 19904 req:0xffff927f161f0af0
fuse_dev_do_read return lt-hello 19904 reqsize:64
do_sync_read return lt-hello 19904

__schedule lt-hello 19904
__init_waitqueue_head cat 11724 alloc req->waitq:0xffff927f0f6b3df8
try_to_wake_up strace 7076 wake up lt-hello 19904
__schedule lt-hello 19904
try_to_wake_up strace 7076 wake up lt-hello 19904
vfs_writev lt-hello 19904
fuse_dev_write lt-hello 19904
fuse_dev_do_write lt-hello 19904 fuse_conn:0xffff927f2e8da800
fuse_copy_one lt-hello 19904 req:0x0
request_end lt-hello 19904 req:0xffff927f161f0af0 fuse_conn:0xffff927f2e8da800
__wake_up lt-hello 19904 wakeup waitq:0xffff927f161f0bd0
fuse_dev_do_write return lt-hello 19904 nbytes:16
vfs_writev return lt-hello 19904

__schedule cat 11724
__schedule lt-hello 19904
try_to_wake_up strace 7076 wake up lt-hello 19904
__schedule lt-hello 19904
try_to_wake_up strace 7076 wake up lt-hello 19904
do_sync_read lt-hello 19904
fuse_dev_do_read lt-hello 19904 fuse_conn:0xffff927f2e8da800
request_wait->add_wait_queue_exclusive lt-hello 19904 wait fc->waitq:0xffff927f2e8da868
__schedule lt-hello 19904
  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Fuse(Filesystem in Userspace)是一个允许非特权用户用户空间中实现自己的文件系统的框架。Fuse可以允许用户创建虚拟文件系统,将不同的物理文件夹组合为单个文件夹等等。 在Linux系统中,Fuse可以通过Fuse API实现自定义文件系统Fuse API提供了一组C语言函数,可以实现文件系统的挂载、卸载、读写、文件创建和删除等基本操作。用户可以通过Fuse API编写自己的文件系统模块,然后将其挂载到本地文件系统中。 创建一个Fuse文件系统的基本步骤是: 1. 安装Fuse库和相关的开发工具。 2. 编写Fuse文件系统程序并通过gcc进行编译。 3. 执行Fuse文件系统程序,将其挂载到Linux本地文件系统中。 4. 通过系统的标准文件操作接口来使用Fuse文件系统。 下面是一个示例程序,它可以创建一个简单的Fuse文件系统: 1. 首先,安装Fuse应用程序和开发包。这里以Ubuntu为例: sudo apt-get install fuse libfuse-dev 2. 编写Fuse文件系统程序。下面的程序实现了一个简单的只读文件系统,它将远程的文件读取并映射到本地的文件系统中: ``` #include <fuse.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <fcntl.h> static const char *hello_str = "Hello World!\n"; static const char *hello_path = "/hello"; static int hello_getattr(const char *path, struct stat *stbuf) { int res = 0; memset(stbuf, 0, sizeof(struct stat)); if (strcmp(path, "/") == 0) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; } else if (strcmp(path, hello_path) == 0) { stbuf->st_mode = S_IFREG | 0444; stbuf->st_nlink = 1; stbuf->st_size = strlen(hello_str); } else res = -ENOENT; return res; } static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi) { (void) offset; (void) fi; if (strcmp(path, "/") != 0) return -ENOENT; filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); filler(buf, hello_path + 1, NULL, 0); return 0; } static int hello_open(const char *path, struct fuse_file_info *fi) { if (strcmp(path, hello_path) != 0) return -ENOENT; if ((fi->flags & 3) != O_RDONLY) return -EACCES; return 0; } static int hello_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { size_t len; (void) fi; if(strcmp(path, hello_path) != 0) return -ENOENT; len = strlen(hello_str); if (offset < len) { if (offset + size > len) size = len - offset; memcpy(buf, hello_str + offset, size); } else size = 0; return size; } static struct fuse_operations hello_oper = { .getattr = hello_getattr, .readdir = hello_readdir, .open = hello_open, .read = hello_read, }; int main(int argc, char *argv[]) { return fuse_main(argc, argv, &hello_oper, NULL); } ``` 3. 编译程序: gcc -Wall hello.c -o hello `pkg-config fuse --cflags --libs` 4. 在本地文件系统中创建一个挂载点: mkdir /tmp/myfuse 5. 运行Fuse文件系统程序: ./hello /tmp/myfuse 6. 使用cat或其他标准文件操作接口,读取`/hello`文件: cat /tmp/myfuse/hello 输出:Hello World! Fuse文件系统提供了一种强大而灵活的方法来扩展和定制Linux文件系统,通过定制Fuse文件系统可以实现各种场景下的文件读写、解压缩、加密解密等操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值