当前位置:   article > 正文

使用Fuse编写文件系统_fuse c语言例子

fuse c语言例子

FUSE的全称是”Filesystem in Userspace”,即“用户空间的文件系统”,这是一个内核模块,能够让用户在用户空间实现文件系统并且挂载到某个目录,就像在内核实现的文件系统一样。使用FUSE有几个好处:一是因为在用户空间实现,开发和调试都比较方便;二是可以把一些常用的服务以文件系统的形式展现,方便操作,如ftpfs,sshfs,mailfs等;另外可以避免一些版权问题,如linux上对ntfs,zfs的操作都是通过FUSE实现的。当然用户空间的实现也有缺点,最明显的就是由多次在用户态/内核态切换带来的性能下降。

根据参考资料[1]的介绍,用户通过FUSE和内核的通信过程如下:

                   +----------------+
                   | myfs /tmp/fuse |
                   +----------------+
                          |   ^
+--------------+          v   |
| ls /tmp/fuse |    +--------------+
+--------------+    |    libfuse   |
      ^  |          +--------------+
      |  v                |   |
+--------------+    +--------------+
|     glibc    |    |     glibc    |
+--------------+    +--------------+
      ^  |                |   ^
~.~.~.|.~|~.~.~.~.~.~.~.~.|.~.|.~.~.~.~.~.~.~.~.
      |  v                v   |
+--------------+    +--------------+
|              |----|     FUSE     |
|              |    +--------------+
|      VFS     |           ...
|              |    +--------------+
|              |----|     Ext3     |
+--------------+    +--------------+

从图中可以看到,FUSE和ext3一样,是内核里的一个文件系统模块。例如我们用FUSE实现了一个文件系统并挂载在/tmp/fuse,当我们对该目录执行ls时,内核里的FUSE从VFS获得参数,然后调用我们自己实现的myfs中相应的函数,得到结果后再通过VFS返回给ls。

以下实验的环境是debian 6,需要安装libfuse-dev,fuse-utils及其它相关依赖。

hello, world

下面是一个简单的文件系统oufs,只支持ls操作:

  1. #define FUSE_USE_VERSION 26
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <fuse.h>
  5. static int ou_readdir(const char* path, void* buf, fuse_fill_dir_t filler,
  6. off_t offset, struct fuse_file_info* fi)
  7. {
  8. return filler(buf, "hello-world", NULL, 0);
  9. }
  10. static int ou_getattr(const char* path, struct stat* st)
  11. {
  12. memset(st, 0, sizeof(struct stat));
  13. if (strcmp(path, "/") == 0)
  14. st->st_mode = 0755 | S_IFDIR;
  15. else
  16. st->st_mode = 0644 | S_IFREG;
  17. return 0;
  18. }
  19. static struct fuse_operations oufs_ops = {
  20. .readdir = ou_readdir,
  21. .getattr = ou_getattr,
  22. };
  23. int main(int argc, char* argv[])
  24. {
  25. return fuse_main(argc, argv, &oufs_ops, NULL);
  26. }


和编译程序的Makefile:

  1. CC := gcc
  2. CFLAGS := -g -Wall -D_FILE_OFFSET_BITS=64
  3. OBJS := $(patsubst %.c, %.o, $(wildcard *.c))
  4. LIBS := -lfuse
  5. TARGET := oufs
  6. .PHONY: all clean
  7. all: $(TARGET)
  8. $(TARGET): $(OBJS)
  9. $(CC) $(CFLAGS) -o $@ $^ $(LIBS)
  10. .c.o:
  11. $(CC) $(CFLAGS) -c $<
  12. clean:
  13. rm -f $(TARGET) $(OBJS)
编译成功后会看到生成的可执行文件oufs。建立一个挂载点/tmp/mnt,然后运行

 
 
./oufs /tmp/mnt

成功后试试”ls /tmp/mnt”,就能看到一个文件hello-world。要调试的时候可以加上-d选项,这样就能看到FUSE和自己printf的调试输出。

代码第一行指定了要使用的FUSE API版本。这里使用的是2.6版本。

要实现ls的功能,FUSE需要提供两个函数:readdir()和getattr(),这两个接口是struct fuse_operations里的两个函数指针:

  1. /usr/include/fuse/fuse.h
  2. /** Function to add an entry in a readdir() operation
  3. *
  4. * @param buf the buffer passed to the readdir() operation
  5. * @param name the file name of the directory entry
  6. * @param stat file attributes, can be NULL
  7. * @param off offset of the next entry or zero
  8. * @return 1 if buffer is full, zero otherwise
  9. */
  10. typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
  11. const struct stat *stbuf, off_t off);
  12. struct fuse_operations {
  13. /** Get file attributes.
  14. *
  15. * Similar to stat(). The 'st_dev' and 'st_blksize' fields are
  16. * ignored. The 'st_ino' field is ignored except if the 'use_ino'
  17. * mount option is given.
  18. */
  19. int (*getattr) (const char *, struct stat *);
  20. /** Read directory
  21. *
  22. * This supersedes the old getdir() interface. New applications
  23. * should use this.
  24. *
  25. * The filesystem may choose between two modes of operation:
  26. *
  27. * 1) The readdir implementation ignores the offset parameter, and
  28. * passes zero to the filler function's offset. The filler
  29. * function will not return '1' (unless an error happens), so the
  30. * whole directory is read in a single readdir operation. This
  31. * works just like the old getdir() method.
  32. *
  33. * 2) The readdir implementation keeps track of the offsets of the
  34. * directory entries. It uses the offset parameter and always
  35. * passes non-zero offset to the filler function. When the buffer
  36. * is full (or an error happens) the filler function will return
  37. * '1'.
  38. *
  39. * Introduced in version 2.3
  40. */
  41. int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
  42. struct fuse_file_info *);
  43. ......
  44. };

这里要实现getattr()是因为我们要遍历根目录的内容,要通过getattr()获取根目录权限等信息。getattr()实现类似stat()的功能,返回相关的信息如文件权限,类型,uid等。参数里的path有可能是文件或目录或软链接或其它,因此除了权限外还要填充类型信息。程序里除了根目录就只有一个文件hello-world,因此只有目录(S_IFDIR)和普通文件(S_IFREG)两种情况。使用”ls -l”查看/tmp/mnt下的内容可以发现,hello-world的link数,修改时间等都是错误的,那是因为ou_getattr()实现中没有填充这些信息。查看manpage可以知道stat都有哪些字段。

readdir()的参数fuse_fill_dir_t是一个函数指针,每次往buf中填充一个entry的信息。程序中的ou_readdir()采用了注释中的第一种模式,即把所有的entry一次性放到buffer中。如果entry较多,把readdir()中的offset传给filler即可,buffer满了filler返回1。

最后在main函数中调用的是fuse_main(),把文件系统的操作函数和参数(如这里的挂载点/tmp/mnt)传给FUSE。

创建/删除文件

接下来让我们扩充上面的程序,增加创建/删除普通文件的功能:

  1. #define FUSE_USE_VERSION 26
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <errno.h>
  6. #include <fuse.h>
  7. #include "list.h"
  8. #define MAX_NAMELEN 255
  9. struct ou_entry {
  10. mode_t mode;
  11. struct list_node node;
  12. char name[MAX_NAMELEN + 1];
  13. };
  14. static struct list_node entries;
  15. static int ou_readdir(const char* path, void* buf, fuse_fill_dir_t filler,
  16. off_t offset, struct fuse_file_info* fi)
  17. {
  18. struct list_node* n;
  19. filler(buf, ".", NULL, 0);
  20. filler(buf, "..", NULL, 0);
  21. list_for_each (n, &entries) {
  22. struct ou_entry* o = list_entry(n, struct ou_entry, node);
  23. filler(buf, o->name, NULL, 0);
  24. }
  25. return 0;
  26. }
  27. static int ou_getattr(const char* path, struct stat* st)
  28. {
  29. struct list_node* n;
  30. memset(st, 0, sizeof(struct stat));
  31. if (strcmp(path, "/") == 0) {
  32. st->st_mode = 0755 | S_IFDIR;
  33. st->st_nlink = 2;
  34. st->st_size = 0;
  35. list_for_each (n, &entries) {
  36. struct ou_entry* o = list_entry(n, struct ou_entry, node);
  37. ++st->st_nlink;
  38. st->st_size += strlen(o->name);
  39. }
  40. return 0;
  41. }
  42. list_for_each (n, &entries) {
  43. struct ou_entry* o = list_entry(n, struct ou_entry, node);
  44. if (strcmp(path + 1, o->name) == 0) {
  45. st->st_mode = o->mode;
  46. st->st_nlink = 1;
  47. return 0;
  48. }
  49. }
  50. return -ENOENT;
  51. }
  52. static int ou_create(const char* path, mode_t mode, struct fuse_file_info* fi)
  53. {
  54. struct ou_entry* o;
  55. struct list_node* n;
  56. if (strlen(path + 1) > MAX_NAMELEN)
  57. return -ENAMETOOLONG;
  58. list_for_each (n, &entries) {
  59. o = list_entry(n, struct ou_entry, node);
  60. if (strcmp(path + 1, o->name) == 0)
  61. return -EEXIST;
  62. }
  63. o = malloc(sizeof(struct ou_entry));
  64. strcpy(o->name, path + 1); /* skip leading '/' */
  65. o->mode = mode | S_IFREG;
  66. list_add_prev(&o->node, &entries);
  67. return 0;
  68. }
  69. static int ou_unlink(const char* path)
  70. {
  71. struct list_node *n, *p;
  72. list_for_each_safe (n, p, &entries) {
  73. struct ou_entry* o = list_entry(n, struct ou_entry, node);
  74. if (strcmp(path + 1, o->name) == 0) {
  75. __list_del(n);
  76. free(o);
  77. return 0;
  78. }
  79. }
  80. return -ENOENT;
  81. }
  82. static struct fuse_operations oufs_ops = {
  83. .getattr = ou_getattr,
  84. .readdir = ou_readdir,
  85. .create = ou_create,
  86. .unlink = ou_unlink,
  87. };
  88. int main(int argc, char* argv[])
  89. {
  90. list_init(&entries);
  91. return fuse_main(argc, argv, &oufs_ops, NULL);
  92. }

代码中用到的链表操作list.h见附录。本来想用c++的list,但是用g++编译时出错了,搜错误信息的时候发现了一个gcc的bug。多嘴插一句,clang3非常好用,错误提示非常人性化,谁用谁知道。

新增了两个函数ou_create()和ou_unlink(),分别用于创建和删除文件;增加了一个全局变量entries用于保存所有的entry;ou_readdir()和ou_getattr()也做了相应的修改。由于这些函数相当于实现VFS中的接口,因此不能在错误时返回-1了事,而是要根据不同的错误类型返回不同的值。关于错误代码头文件为errno.h,值和含义在/usr/include/asm-generic/errno-base.h和/usr/include/asm-generic/errno.h有定义。

编译挂载后试试执行”touch /tmp/mnt/abc”,然后ls看看是否多了一个文件abc?

程序中用到的list.h(相关原理可以参考这里):
  1. #ifndef __LIST_H__
  2. #define __LIST_H__
  3. /* doubly linked list implementation from linux kernel */
  4. #define offset_of(type, member) \
  5. ((unsigned long)(&(((type*)0)->member)))
  6. #define container_of(ptr, type, member) \
  7. ((type*)((unsigned long)(ptr) - offset_of(type, member)))
  8. #define list_entry(ptr, type, member) container_of(ptr, type, member)
  9. struct list_node {
  10. struct list_node *prev, *next;
  11. };
  12. static inline void list_init(struct list_node* list)
  13. {
  14. list->prev = list;
  15. list->next = list;
  16. }
  17. static inline void list_add_prev(struct list_node* node,
  18. struct list_node* next)
  19. {
  20. node->next = next;
  21. node->prev = next->prev;
  22. next->prev = node;
  23. node->prev->next = node;
  24. }
  25. static inline void __list_del(struct list_node* node)
  26. {
  27. node->prev->next = node->next;
  28. node->next->prev = node->prev;
  29. }
  30. static inline void list_del(struct list_node* node)
  31. {
  32. __list_del(node);
  33. list_init(node);
  34. }
  35. #define list_for_each(p, head) \
  36. for ((p) = (head)->next; (p) != (head); (p) = (p)->next)
  37. #define list_for_each_safe(p, n, head) \
  38. for ((p) = (head)->next, (n) = (p)->next; \
  39. (p) != (head); \
  40. (p) = (n), (n) = (p)->next)
  41. #endif

原文地址:http://ouonline.net/building-your-own-fs-with-fuse-1


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/363504
推荐阅读
相关标签
  

闽ICP备14008679号