当前位置:   article > 正文

Linux下使用fuse编写自己的文件系统_fuse-2.9.7

fuse-2.9.7

一、前言

近几天调研了一下fuse编写文件系统的方法,先尝试拿fuse写一套类似tmpfs的简易文件系统,文件信息都保留在内存中。文件系统需要一个数据结构来管理文件节点 inode,正好《c语言实现map-使用内核红黑树》一文将rbtree结构拿出来了可以用上。

目标:支持文件读写操作:echo、cat;支持目录操作ls、mkdir、cd。

二、知识准备

FUSE(Filesystem in Userspace)为Linux下用户态的文件系统接口,通常情况文件系统的操作在内核态处理,存在调试不方便,开发效率低的情况,使用FUSE可以在用户空间进行方便地开发、调试。

如图所示,用户层的 list 操作,通过内核VFS\FUSE中转,在用户层通过libfuse到自定义程序hello中进行处理、返回。这种操作是非常灵活的,即list操作的结果是由你的应用来决定的,也就是说你能实现list展示你的自定义列表、自定义数据项等信息。当然,灵活性所需要付出的代价:用户态实现的操作系统会引入“内核态/用户态切换”额外的开销,进而影响性能。

FUSE structure.svg

fuse安装比较简单:

1、内核需要开启fuse的支持(默认带)

2、准备安装包 fuse-2.9.7.tar.gz

3、源码安装./configure --prefix=/usr && make -j4 && make install(编译过程提示我缺库,util-linux-ng-2.17.1.tar.gz)

三、实现

编译过程中需要指定库文件:-lfuse -pthread

首先需要定义文件系统支持的操作函数,填在结构体 struct fuse_operations 中,其他的可以详见[附录]:

  1. static struct fuse_operations memfs_oper = {
  2. .getattr = memfs_getattr,
  3. .access = memfs_access,
  4. .readdir = memfs_readdir,
  5. .open = memfs_open,
  6. .read = memfs_read,
  7. .write = memfs_write,
  8. .release = memfs_release,
  9. .mknod = memfs_mknod,
  10. .unlink = memfs_unlink,
  11. .mkdir = memfs_mkdir,
  12. .rmdir = memfs_rmdir,
  13. .statfs = memfs_statfs,
  14. };

主要包含了一些基础操作:

1、新建目录:mkdir、getattr;删除目录:rmdir;遍历目录:readdir;进入目录:access;

2、新建文件:getattr、mknod、open、write、read、release;删除文件:unlink;

3、状态查看:statfs;

然后看下数据结构,memfs为全局变量(多个终端操作为多线程访问该变量),并定义、初始化了statvfs结构来维护系统状态信息,定义了文件块BlockSize大小为4096,块上限MaxBlocks为1048576个,文件数MaxInode为1048576个:

  1. struct memfs {
  2. struct rb_root root;
  3. struct statvfs statvfs;
  4. pthread_mutex_t lock;
  5. pthread_mutex_t lock_write;
  6. };
  7. #define FUSE_SUPER_MAGIC 0x65735546
  8. #define BLOCKSIZE (1024UL * 4)
  9. #define MAX_NAME 255
  10. #define MAX_INODE (1024UL * 1024)
  11. #define MAX_BLOCKS (1024UL * 1024)
  12. /* Set global instance */
  13. static struct memfs memfs = {
  14. .root = RB_ROOT,
  15. .statvfs = {
  16. .f_bsize = BLOCKSIZE, /* Filesystem block size */
  17. .f_frsize = BLOCKSIZE, /* Fragment size */
  18. .f_blocks = MAX_BLOCKS, /* Size of fs in f_frsize units */
  19. .f_bfree = MAX_BLOCKS, /* Number of free blocks */
  20. .f_bavail = MAX_BLOCKS, /* Number of free blocks for unprivileged users */
  21. .f_files = MAX_INODE, /* Number of inodes */
  22. .f_ffree = MAX_INODE, /* Number of free inodes */
  23. .f_favail = MAX_INODE, /* Number of free inodes for unprivileged users */
  24. .f_fsid = 0x0123456701234567, /* Filesystem ID */
  25. // .f_flags = 0, /* Mount flags */
  26. .f_namemax = MAX_NAME, /* Maximum filename length */
  27. },
  28. .lock = PTHREAD_MUTEX_INITIALIZER,
  29. .lock_write = PTHREAD_MUTEX_INITIALIZER,
  30. };
  31. /* File inodes store in rbtree */
  32. struct memfs_file {
  33. char *path; /* File path */
  34. void *data; /* File content */
  35. u8 free_on_delete;
  36. struct stat vstat; /* File stat */
  37. pthread_mutex_t lock;
  38. struct rb_node node;
  39. };

所以外部执行df,df -i的时候,将调用.statfs进行状态查询:

  1. static int memfs_statfs(const char *path, struct statvfs *stbuf)
  2. {
  3. printf("%s: %s\n", __FUNCTION__, path);
  4. *stbuf = memfs.statvfs;
  5. return 0;
  6. }

文件、目录节点均使用红黑树进行维护,相关的操作请看《c语言实现map-使用内核红黑树》;

由于数据结构将被多线程使用,所以使用mutex互斥锁对其进行保护;

getattr为非常常用的方法,用于查询节点是否存在、查询节点属性等动作:

  1. static int memfs_getattr(const char *path, struct stat *stbuf)
  2. {
  3. int res = 0;
  4. printf("%s: %s\n", __FUNCTION__, path);
  5. memset(stbuf, 0, sizeof(struct stat));
  6. pthread_mutex_lock(&memfs.lock);
  7. struct memfs_file *pf = __search(&memfs.root, path);
  8. if (!pf) {
  9. res = -ENOENT;
  10. }
  11. else {
  12. *stbuf = pf->vstat;
  13. }
  14. pthread_mutex_unlock(&memfs.lock);
  15. return res;
  16. }

进入目录、创建目录、删除目录:

  1. static int memfs_access(const char *path, int mask)
  2. {
  3. int res = 0;
  4. printf("%s: %s\n", __FUNCTION__, path);
  5. pthread_mutex_lock(&memfs.lock);
  6. struct memfs_file *pf = __search(&memfs.root, path);
  7. if (!pf) {
  8. res = -ENOENT;
  9. }
  10. pthread_mutex_unlock(&memfs.lock);
  11. return res;
  12. }
  13. static int memfs_mkdir(const char *path, mode_t mode)
  14. {
  15. int res = 0;
  16. struct memfs_file *pf = NULL;
  17. printf("%s: %s\n", __FUNCTION__, path);
  18. pf = __new(path, S_IFDIR | mode);
  19. if (!pf) {
  20. return -ENOMEM;
  21. }
  22. pthread_mutex_lock(&memfs.lock);
  23. res = __insert(&memfs.root, pf);
  24. if (res != SUCCESS) {
  25. __free(pf);
  26. res = -EEXIST;
  27. }
  28. pthread_mutex_unlock(&memfs.lock);
  29. __do_update_times(pf, U_ALL);
  30. return res;
  31. }
  32. static int memfs_rmdir(const char *path)
  33. {
  34. int res = 0;
  35. printf("%s: %s\n", __FUNCTION__, path);
  36. pthread_mutex_lock(&memfs.lock);
  37. if (__delete(&memfs.root, path) < 0) {
  38. res = -ENOENT;
  39. }
  40. pthread_mutex_unlock(&memfs.lock);
  41. return res;
  42. }

试验1:cd /mnt/fuse && mkdir 1 2 3 && rmdir 1 2 3

  1. memfs_getattr: /
  2. memfs_access: /
  3. memfs_getattr: /1
  4. memfs_mkdir: /1
  5. memfs_getattr: /1
  6. memfs_getattr: /2
  7. memfs_mkdir: /2
  8. memfs_getattr: /2
  9. memfs_getattr: /3
  10. memfs_mkdir: /3
  11. memfs_getattr: /3
  12. memfs_getattr: /
  13. memfs_getattr: /1
  14. memfs_rmdir: /1
  15. memfs_getattr: /2
  16. memfs_rmdir: /2
  17. memfs_getattr: /3
  18. memfs_rmdir: /3

文件操作:创建文件mknod、打开文件open、关闭文件release、删除文件unlink;

注意mknod、unlink的时候需要更新statvfs中的inode计数器。

  1. static int memfs_mknod(const char *path, mode_t mode, dev_t rdev)
  2. {
  3. int res = 0;
  4. struct memfs_file *pf = NULL;
  5. printf("%s: %s\n", __FUNCTION__, path);
  6. pf = __new(path, mode);
  7. if (!pf) {
  8. return -ENOMEM;
  9. }
  10. pthread_mutex_lock(&memfs.lock);
  11. res = __insert(&memfs.root, pf);
  12. if (res != SUCCESS) {
  13. __free(pf);
  14. res = -EEXIST;
  15. }
  16. memfs.statvfs.f_favail = --memfs.statvfs.f_ffree;
  17. pthread_mutex_unlock(&memfs.lock);
  18. return res;
  19. }
  20. static int memfs_open(const char *path, struct fuse_file_info *fi)
  21. {
  22. int res = 0;
  23. struct memfs_file *pf = NULL;
  24. printf("%s: %s\n", __FUNCTION__, path);
  25. pthread_mutex_lock(&memfs.lock);
  26. pf = __search(&memfs.root, path);
  27. if (!pf) {
  28. if ((fi->flags & O_ACCMODE) == O_RDONLY ||
  29. !(fi->flags & O_CREAT)) {
  30. res = -ENOENT;
  31. goto unlock;
  32. }
  33. pf = __new(path, S_IFREG | 0755);
  34. __insert(&memfs.root, pf);
  35. }
  36. else {
  37. if (S_ISDIR(pf->vstat.st_mode)) {
  38. res = -EISDIR;
  39. goto unlock;
  40. }
  41. }
  42. fi->fh = (unsigned long)pf;
  43. unlock:
  44. pthread_mutex_unlock(&memfs.lock);
  45. return res;
  46. }
  47. static int memfs_release(const char *path, struct fuse_file_info *fi)
  48. {
  49. printf("%s: %s\n", __FUNCTION__, path);
  50. return 0;
  51. }
  52. static int memfs_unlink(const char *path)
  53. {
  54. int res = 0, blocks = 0;
  55. printf("%s: %s\n", __FUNCTION__, path);
  56. pthread_mutex_lock(&memfs.lock);
  57. blocks = __delete(&memfs.root, path);
  58. if (blocks < 0) {
  59. res = -ENOENT;
  60. goto unlock;
  61. }
  62. memfs.statvfs.f_bfree = memfs.statvfs.f_bavail += blocks;
  63. memfs.statvfs.f_favail = ++memfs.statvfs.f_ffree;
  64. unlock:
  65. pthread_mutex_unlock(&memfs.lock);
  66. return res;
  67. }

文件读写操作:read、write;

注意write过程中需要对statvfs的blocks计数器进行更新,并调用__do_update_times对文件时间戳更新;

思路是open获取文件节点后,将节点挂在struct fuse_file_info结构的fh成员内,文件内容写在了memfs_file::data中;

该例子仅对单次写入进行加锁保护,但并没有加入文件级别的锁,没解决同时多人打开文件写的问题。

  1. #define U_ATIME (1 << 0)
  2. #define U_CTIME (1 << 1)
  3. #define U_MTIME (1 << 2)
  4. #define U_ALL (U_ATIME | U_CTIME | U_MTIME)
  5. static inline void __do_update_times(struct memfs_file *pf, int which)
  6. {
  7. time_t now = time(0);
  8. if (which & U_ATIME) {
  9. pf->vstat.st_atime = now;
  10. }
  11. if (which & U_CTIME) {
  12. pf->vstat.st_ctime = now;
  13. }
  14. if (which & U_MTIME) {
  15. pf->vstat.st_mtime = now;
  16. }
  17. }
  18. static int memfs_write(const char *path,
  19. const char *buf, size_t size, off_t offset,
  20. struct fuse_file_info *fi)
  21. {
  22. struct memfs_file *pf = (struct memfs_file *)fi->fh;
  23. printf("%s: %s, size: %zd\n", __FUNCTION__, path, size);
  24. // TODO Check whether the file was opened for reading
  25. blkcnt_t req_blocks = (offset + size + BLOCKSIZE - 1) / BLOCKSIZE;
  26. pthread_mutex_lock(&pf->lock);
  27. if (pf->vstat.st_blocks < req_blocks) {
  28. void *newdata = realloc(pf->data, req_blocks * BLOCKSIZE);
  29. if (!newdata) {
  30. return -ENOMEM;
  31. }
  32. memfs.statvfs.f_bfree = memfs.statvfs.f_bavail -= req_blocks - pf->vstat.st_blocks;
  33. pf->data = newdata;
  34. pf->vstat.st_blocks = req_blocks;
  35. }
  36. memcpy(pf->data + offset, buf, size);
  37. // Update file size if necessary
  38. off_t minsize = offset + size;
  39. if (minsize > pf->vstat.st_size) {
  40. pf->vstat.st_size = minsize;
  41. }
  42. pthread_mutex_unlock(&pf->lock);
  43. __do_update_times(pf, U_ALL);
  44. return size;
  45. }
  46. static int memfs_read(const char *path,
  47. char *buf, size_t size, off_t offset,
  48. struct fuse_file_info *fi)
  49. {
  50. struct memfs_file *pf = (struct memfs_file *)fi->fh;
  51. printf("%s: %s\n", __FUNCTION__, path);
  52. // TODO Check whether the file was opened for reading
  53. off_t filesize = pf->vstat.st_size;
  54. if (offset > filesize) {
  55. return 0;
  56. }
  57. size_t avail = filesize - offset;
  58. size_t rsize = (size < avail) ? size : avail;
  59. memcpy(buf, pf->data + offset, rsize);
  60. __do_update_times(pf, U_ATIME);
  61. return rsize;
  62. }

试验2:cd /mnt/fuse && echo "Helloworld" >test.txt && cat test.txt && rm -rf test.txt

  1. memfs_getattr: /
  2. memfs_access: /
  3. memfs_getattr: /test.txt
  4. memfs_mknod: /test.txt
  5. memfs_getattr: /test.txt
  6. memfs_open: /test.txt
  7. memfs_write: /test.txt, size: 11
  8. memfs_release: /test.txt
  9. memfs_getattr: /
  10. memfs_getattr: /test.txt
  11. memfs_open: /test.txt
  12. memfs_read: /test.txt
  13. memfs_getattr: /test.txt
  14. memfs_release: /test.txt
  15. memfs_getattr: /test.txt
  16. memfs_unlink: /test.txt

最后是遍历目录readdir的实现,basename、dirname字符串处理麻烦一点点,基本思路是先找到父目录节点,然后后序遍历直到离开父目录(rbtree能够实现范围查找,hashmap则不行)。

文件名处理也可以用#include <libgen.h>里面的方法,为了深入理解处理,本文使用__is_parent来代替basename()函数;

filler函数中填写的文件名为basename,不能带'/';

  1. /*
  2. * @parent - "/tmp"
  3. * @path - "/tmp/1.txt"
  4. */
  5. static inline const char *__is_parent(const char *parent, const char *path)
  6. {
  7. const char delim = '/';
  8. if (parent[1] == '\0' && parent[0] == '/' && path[0] == '/') {
  9. return path;
  10. }
  11. while (*parent != '\0' && *path != '\0' && *parent == *path) {
  12. ++parent, ++path;
  13. }
  14. return (*parent == '\0' && *path == delim) ? path : NULL;
  15. }
  16. static int __do_readdir(const char *dirname, void *buf, fuse_fill_dir_t filler)
  17. {
  18. struct rb_node *node = NULL;
  19. struct memfs_file *pentry = __search(&memfs.root, dirname);
  20. if (!pentry) {
  21. return -ENOENT;
  22. }
  23. else if (!S_ISDIR(pentry->vstat.st_mode)) {
  24. return -ENOTDIR;
  25. }
  26. for (node = rb_next(&pentry->node); node; node = rb_next(node)) {
  27. const struct memfs_file *pf = rb_entry(node, struct memfs_file, node);
  28. const char *basename = __is_parent(dirname, pf->path);
  29. if (!basename) {
  30. break;
  31. }
  32. else if (strchr(basename + 1, '/')) {
  33. continue;
  34. }
  35. filler(buf, basename + 1, &pf->vstat, 0);
  36. printf(" readdir: %10s, path: %10s\n", basename, pf->path);
  37. }
  38. return 0;
  39. }
  40. static int memfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
  41. off_t offset, struct fuse_file_info *fi)
  42. {
  43. int res = 0;
  44. printf("%s: %s\n", __FUNCTION__, path);
  45. filler(buf, ".", NULL, 0);
  46. if (strcmp(path, "/") != 0) {
  47. filler(buf, "..", NULL, 0);
  48. }
  49. pthread_mutex_lock(&memfs.lock);
  50. res = __do_readdir(path, buf, filler);
  51. pthread_mutex_unlock(&memfs.lock);
  52. return res;
  53. }

试验3:cd /mnt/fuse && mkdir 1 2 3 4 && ls -l

  1. memfs_getattr: /
  2. memfs_access: /
  3. memfs_getattr: /1
  4. memfs_mkdir: /1
  5. memfs_getattr: /1
  6. memfs_getattr: /2
  7. memfs_mkdir: /2
  8. memfs_getattr: /2
  9. memfs_getattr: /3
  10. memfs_mkdir: /3
  11. memfs_getattr: /3
  12. memfs_getattr: /4
  13. memfs_mkdir: /4
  14. memfs_getattr: /4
  15. memfs_getattr: /
  16. memfs_readdir: /
  17. readdir: /1, path: /1
  18. readdir: /2, path: /2
  19. readdir: /3, path: /3
  20. readdir: /4, path: /4

四、结论

本文对fuse开发文件系统进行了探索,并简单实现了基于内存的文件系统,开发、调试过程是比较方便,遇到不会写的函数就参考一下fuse/example底下的案例,或者看下sshfs的源码。另外线程安全的问题也是需要在应用中重点考虑的部分。

然后尝试大批小文件写入发现速度达到10000ops,对比了一下tmpfs居然有40000ops的速度,果然多了两层内核态/用户态的切换性能影响还是挺大的。所以对于重扩展不重性能的应用,可以考虑fuse去实现(网络文件协议挂载到本地),但对于性能型应用还是考虑调用api比较合适。

 

附录:

  1. /* from fuse.h */
  2. struct fuse_operations
  3. {
  4. /** Get file attributes. */
  5. int (*getattr) (const char *, struct stat *);
  6. /** Read the target of a symbolic link */
  7. int (*readlink) (const char *, char *, size_t);
  8. /** Create a file node */
  9. int (*mknod) (const char *, mode_t, dev_t);
  10. /** Create a directory */
  11. int (*mkdir) (const char *, mode_t);
  12. /** Remove a file */
  13. int (*unlink) (const char *);
  14. /** Remove a directory */
  15. int (*rmdir) (const char *);
  16. /** Create a symbolic link */
  17. int (*symlink) (const char *, const char *);
  18. /** Rename a file */
  19. int (*rename) (const char *, const char *);
  20. /** Create a hard link to a file */
  21. int (*link) (const char *, const char *);
  22. /** Change the permission bits of a file */
  23. int (*chmod) (const char *, mode_t);
  24. /** Change the owner and group of a file */
  25. int (*chown) (const char *, uid_t, gid_t);
  26. /** Change the size of a file */
  27. int (*truncate) (const char *, off_t);
  28. /** Change the access and/or modification times of a file */
  29. int (*utime) (const char *, struct utimbuf *);
  30. /** File open operation */
  31. int (*open) (const char *, struct fuse_file_info *);
  32. /** Read data from an open file */
  33. int (*read) (const char *, char *, size_t, off_t,
  34. struct fuse_file_info *);
  35. /** Write data to an open file */
  36. int (*write) (const char *, const char *, size_t, off_t,
  37. struct fuse_file_info *);
  38. /** Get file system statistics */
  39. int (*statfs) (const char *, struct statvfs *);
  40. /** Possibly flush cached data */
  41. int (*flush) (const char *, struct fuse_file_info *);
  42. /** Release an open file */
  43. int (*release) (const char *, struct fuse_file_info *);
  44. /** Synchronize file contents */
  45. int (*fsync) (const char *, int, struct fuse_file_info *);
  46. /** Set extended attributes */
  47. int (*setxattr) (const char *, const char *, const char *, size_t, int);
  48. /** Get extended attributes */
  49. int (*getxattr) (const char *, const char *, char *, size_t);
  50. /** List extended attributes */
  51. int (*listxattr) (const char *, char *, size_t);
  52. /** Remove extended attributes */
  53. int (*removexattr) (const char *, const char *);
  54. /** Open directory */
  55. int (*opendir) (const char *, struct fuse_file_info *);
  56. /** Read directory */
  57. int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
  58. struct fuse_file_info *);
  59. /** Release directory */
  60. int (*releasedir) (const char *, struct fuse_file_info *);
  61. /** Synchronize directory contents */
  62. int (*fsyncdir) (const char *, int, struct fuse_file_info *);
  63. /** Initialize filesystem */
  64. void *(*init) (struct fuse_conn_info *conn);
  65. /** Clean up filesystem */
  66. void (*destroy) (void *);
  67. /** Check file access permissions */
  68. int (*access) (const char *, int);
  69. /** Create and open a file */
  70. int (*create) (const char *, mode_t, struct fuse_file_info *);
  71. /** Change the size of an open file */
  72. int (*ftruncate) (const char *, off_t, struct fuse_file_info *);
  73. /** Get attributes from an open file */
  74. int (*fgetattr) (const char *, struct stat *, struct fuse_file_info *);
  75. /** Perform POSIX file locking operation */
  76. int (*lock) (const char *, struct fuse_file_info *, int cmd,
  77. struct flock *);
  78. /**
  79. * Change the access and modification times of a file with
  80. * nanosecond resolution
  81. */
  82. int (*utimens) (const char *, const struct timespec tv[2]);
  83. /** Map block index within file to block index within device */
  84. int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
  85. /** Ioctl */
  86. int (*ioctl) (const char *, int cmd, void *arg,
  87. struct fuse_file_info *, unsigned int flags, void *data);
  88. /** Poll for IO readiness events */
  89. int (*poll) (const char *, struct fuse_file_info *,
  90. struct fuse_pollhandle *ph, unsigned *reventsp);
  91. /** Write contents of buffer to an open file */
  92. int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
  93. struct fuse_file_info *);
  94. /** Store data from an open file in a buffer */
  95. int (*read_buf) (const char *, struct fuse_bufvec **bufp,
  96. size_t size, off_t off, struct fuse_file_info *);
  97. /** Perform BSD file locking operation */
  98. int (*flock) (const char *, struct fuse_file_info *, int op);
  99. /** Allocates space for an open file */
  100. int (*fallocate) (const char *, int, off_t, off_t,
  101. struct fuse_file_info *);
  102. };

参考文章:

[1] https://zh.wikipedia.org/zh-hans/FUSE

[2] http://libfuse.github.io/doxygen/index.html

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

闽ICP备14008679号