如何将整型数转成字符型?- -| 回首页 | 2006年索引 | - -用FUSE开发自己的文件系统二 [转]

用FUSE开发自己的文件系统一 [转]

                                      

FUSE开发自己的文件系统

不需要内核编程知识



Level: Introductory

Sumit Singh (sumising@in.ibm.com), Software Engineer, IBM

28 Feb 2006

Filesystem in Userspace (FUSE), 你能开发一个用户空间的文件系统框架,而不需要了解文件系统地内部或者学习内核模块编程的相关知识. 按照本文的例子, 一步一步的指导你去安装, 定制, 和使能FUSE AFS文件系统, 因此你能在Linux®的用户空间创建一个你自己的拥有完全机能的文件系统.

一个文件系统是一种存储和管理计算机文件和目录以及他们包含的数据的有效方法, 而且还能让你很容易得去找道河访问它们.如果你使用过计算机, 你应该用过不止一种文件系统. 一个文件系统能提供一些扩展能力. 它能在一个基本的文件系统上写一个包装器去管理它的数据,并且还能够体统一个加强的,功能丰富的文件系统 (例如cvsfs-fuse, 就是一个给CVS提供了一个文件系统接口, 还有Wayback 文件系统, 就是为了保存老的拷贝数据提供了一个备份机制).

在用户空间文件系统没有出现之前, 文件系统开发是内核开发者的一个工作. 创建一个文件系统则需要了解内核编程和内核技术的相关知识 (就像 vfs). 并且调试的时候还需要C C++ 专门技术. 然而其他的开发者需要操控一个文件系统 —— 只是加入一些个人的特征(例如增加历史纪录或者正向缓存forward-caching) 和改进.

FUSE简介

FUSE让你开发一个拥有简单API库的全功能文件系统, 并能让非特权用户访问, 而且提供了一个安全实现. 另外, 更妙的是, FUSE有一个经过验证的可跟踪的稳定版.

使用 FUSE, 你能开发一个连接到FUSE 库的文件系统就像一个可执行的二进制程序一样 -- 也就是说, 这个文件系统框架不要求你去学习额外的文件系统内部和内核模块的编程知识.

FUSE真正成为一个文件系统之前, 用户空间的文件系统并不是一个新的设计. 下面有些关于商业和学术上实现的用户空间的文件系统的例子:

  • LUFS是一个混血的用户空间文件系统构架,它为所有应用程序提供了数量不定的文件系统. 它分成内核模块和用户空间的后台程序两部分. 基本上它把大多数VFS层的调用委派给专门来处理这些的后台进程.
  • UserFS 允许用户进程作为一个正常的文件系统被挂载. 以这个proof-of-concept为原型开发了ftpfs, 一个允许匿名FTP的文件系统.
  • Ufo 工程是一个Solaris的全局文件系统,它允许用户把远程文件当作本地文件来使用.
  • OpenAFS是一个Andrew FileSystem的开源版本.
  • CIFS一个普通的网际文件系统.

不像这些商业和学术例子, FUSE把文件系统设计的功能带到Linux. 因为FUSE 一个可执行的程序(而不是说, 作为LUFS 使用的一个共享对象), 它让调试和开发变得容易起来. FUSE能工作在内核(2.4.x 2.6.x) 以及现在支持Java™ 绑定, 因此你不用局限于使用C C++语言来进行文件系统的编程. (参照Resources 了解更多的使用FUSE的用户空间的文件系统.)

如果要用FUSE来创建文件系统, 你需要安装一个FUSE 内核模块,然后使用FUSE 库和 API集来创建你的文件系统.


 

解压缩FUSE

要开发文件系统, 首先下载FUSE源代码 (参看 Resources) 后进行解压缩: tar -zxvf fuse-2.2.tar.gz. 它将创建一个存放源代码的FUSE 目录. fuse-2.2版本的目录内容如下:

  • ./doc 包含FUSE相关的文档. 这里仅仅只有一个文件, how-fuse-works.
  • ./kernel 包含FUSE 内核模块源代码 (当然, 你不需要知道怎么用FUSE来开发文件系统).
  • ./include包含FUSE 用来创建一个文件系统所需的API 头文件. 目前你仅仅需要一个头文件fuse.h.
  • ./lib 保持着用来创建FUSE 库的源代码,他们将用来连接你的二进制程序来创建一个文件系统.
  • ./util 含有FUSE 工具库的源代码.
  • ./example, 当然, 也包含一些可供你参考的例子, 例如fusexmp.null hello 文件系统.

 

 

构建和安装FUSE

1.       运行和配置来自fuse-2.2目录下的脚本: ./configure. 这将创建一个编译所需要的makefiles文件等.

2.       运行 ./make 去构建库, 二进制程序, 和内核模块. 检查kernel 目录下是否有./kernel/fuse.ko文件 这是一个内核模块文件. 另外还要检查 lib 目录是否包含fuse.o, mount.o, helper.o文件.

3.       执行 ./make install 去完成 FUSE的安装.

备注: 如果你想要手动的使用insmod安装这些模块到内核里的话,可以掠过这一步. 此时你可以使用: /usr/local/sbin/insmod ./kernel/fuse.ko 或者/sbin/insmod ./kernel/fuse.ko. 注意要安装需要的模块得需要root权限.

你可以用一条命令行来代替上面的步骤. fuse-2.2 目录下执行 ./configure; make; make install;既可.

重要提示: 当编译FUSE, 你需要有内核头文件或者内核源代码. 为了使事情变得简单, 必须保证内核源代码在 /usr/src/ 目录下.

 

定制文件系统

现在让我们创建一个文件系统以便你能使用一个老版本的Linux内核就可以访问Linux主机上的最新版本的内核的AFS 空间. 你将拥有两个进城: 一个是运行在老版本Linux内核上的服务器端进程, 另一个是运行在最新的Linux内核上的FUSE客户端进程. 无论什么时候一个请求来到你的FUSE 客户端进程的时候, 它将联系远程服务器端进程. 为了达到通信的目的, 这个文件系统使用AFS 的一部分RX RPC 代码, 因此你需要先构建OpenAFS. ( 1 AFS 文件系统的框图.)


1. AFS-FUSE文件系统概况

 

构建 OpenAFS

1.       下载OpenAFS Linux 源代码并解压.

进入到你解压源代码包后的目录里, 执行./make; ./configure --enable-transarc-paths. 如果 ./configure 不知道要构建的sysname, 然后使用--with-afs-sysname 选项指定一个合适的sysname.

为了在Linux 2.4 内核上构建文件系统, 请使用下面的命令: ./configure --enable-transarc-paths --with-afs-sysname=i386_linux24.

2.       执行./make, 然后执行 ./make dest. 检查再构建期间的任何错误.

如果编译没有什么问题的话, 那么你得AFS源代码树就可以准备工作了. 在这个阶段, 你需要准备一个名字叫afsfuse的开发目录. 在这个目录下, 生成下面两个目录:

o        include 目录将包含来自OpenAFS FUSE的头文件.

o        lib 目录将包含来自OpenAFS FUSE的库文件.

3.       拷贝头文件和库文件.

首先拷贝AFS 的头文件。把dest\i386_linux24\include下面的目录和文件拷贝到include 目录下. 然后从fuse-2.2目录下拷贝FUSE 的头文件到include 目录下. 重复以上的操作把库文件拷贝到lib目录里.

4.       创建应用程序的结构.

你需要为两组进程准备两套源代码文件. 使用afsfuse_client.*这样的名字来命名客户端进程;使用afsfuse_server.*这样的名字来命名服务器端的进程.

afsfuse_client.c 文件里包含了FUSE 进程的代码, afsfuse_server.c 文件里包含了运行在远程机器上的进程的服务器代码, 另外还有makefile文件, 和一个 为创建RPC(例如afsfuse.xg)而准备的 rxgen 文件.

这个afsfuse_client.c 文件将生成一个由FUSE 文件系统调用的afsfuse_client 进程代码来创建你属于自己的文件系统(请使用例子 fuse-2.2/example/fusexmp.c 来创建这个文件).

定义必要的函数

使用 FUSE来创建文件系统, 你需要声明一个fuse_operations类型的结构体变量并且把它传递到fuse_main函数里. 这个fuse_operations 结构体装载了指向为一系列特定动作而准备调用的函数表. Listing 1 显示了 fuse_operations 结构体的内容.


Listing 1. fuse_operation
结构体中必要的函数

struct fuse_operations {

    int (*getattr) (const char *, struct stat *);

    int (*readlink) (const char *, char *, size_t);

    int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t);

    int (*mknod) (const char *, mode_t, dev_t);

    int (*mkdir) (const char *, mode_t);

    int (*unlink) (const char *);

    int (*rmdir) (const char *);

    int (*symlink) (const char *, const char *);

    int (*rename) (const char *, const char *);

    int (*link) (const char *, const char *);

    int (*chmod) (const char *, mode_t);

    int (*chown) (const char *, uid_t, gid_t);

    int (*truncate) (const char *, off_t);

    int (*utime) (const char *, struct utimbuf *);

    int (*open) (const char *, struct fuse_file_info *);

    int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *);

    int (*write) (const char *, const char *, size_t, off_t,struct fuse_file_info *);

    int (*statfs) (const char *, struct statfs *);

    int (*flush) (const char *, struct fuse_file_info *);

    int (*release) (const char *, struct fuse_file_info *);

    int (*fsync) (const char *, int, struct fuse_file_info *);

    int (*setxattr) (const char *, const char *, const char *, size_t, int);

    int (*getxattr) (const char *, const char *, char *, size_t);

    int (*listxattr) (const char *, char *, size_t);

    int (*removexattr) (const char *, const char *);

};

其中没有一个函数是绝对必要的, 但是它们当中的很多函数也需要为文件系统适当的工作. 你能用特定目标的方法(.flush, .release, 或者 .fsync)来实现一个完全特征的文件系统. (本文不涉及到了任何xattr 函数.) Listing 1中的这些函数在下面有其说明:

  • getattr: int (*getattr) (const char *, struct stat *);
    stat()相似的一个函数. st_dev st_blksize 域被忽略了.除非use_ino挂载选项被指定 st_ino 域也将被忽略.
  • readlink: int (*readlink) (const char *, char *, size_t);
    读一个符号连接的目标文件. 该函数将会用一个有终结符的字符串来填充缓冲区. 这个缓冲区的 size 参数也要包含一个终结符. 如果linkname 太长以至于不能填充到这个缓冲区里的话, 它将被截断. 这个函数的返回值在成功时返回"0".
  • getdir: int (*getdir) (const char *, fuse_dirh_t, fuse_dirfil_t);
    读一个目录的内容. 它是 opendir(), readdir(), ..., closedir() 这一系列操作中的一个调用. 为了把每一个目录entry填充到特定的缓冲区里, filldir() 函数将会被调用.
  • mknod: int (*mknod) (const char *, mode_t, dev_t);
    创建一个文件的索引节点. 它不是一个 create() 操作; mknod() 将被用于创建所有的非目录,非符号连接的索引节点.
  • mkdir: int (*mkdir) (const char *, mode_t);
    rmdir: int (*rmdir) (const char *);
    分别是创建和删除一个目录.
  • unlink: int (*unlink) (const char *);
    rename: int (*rename) (const char *, const char *);
    分别时删除和重命名一个文件.
  • symlink: int (*symlink) (const char *, const char *);
    创建一个符号连接.
  • link: int (*link) (const char *, const char *);
    给指定的文件创建一个硬连接.
  • chmod: int (*chmod) (const char *, mode_t);
    chown: int (*chown) (const char *, uid_t, gid_t);
    truncate: int (*truncate) (const char *, off_t);
    utime: int (*utime) (const char *, struct utimbuf *);
    它们分别是改变的文件的权限位, 拥有者和属组, 大小, 以及访问/更改时间.
  • open: int (*open) (const char *, struct fuse_file_info *);
    打开一个文件. 不需要把创建或者截断标志(O_CREAT, O_EXCL, O_TRUNC) 传递到 open(). 它将检查该操作根据制定的标志符是否被允许. 另外, open() 也将返回一个文件描述符到fuse_file_info 结构体里.
  • read: int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *);
    从一个被打开的文件里读数据. read() 将准确的返回读出数据的字节数, 但是除了遇到文件尾或者一个错误; 否则, 其余的数据将被用0来取代. direct_io 挂载选项被指定时将返回一个异常, 而将用read() 系统调用的返回值代替本函数的返回值.
  • write: int (*write) (const char *, const char *, size_t, off_t, struct fuse_file_info *);
    写数据到打开的文件里. write() 将准确的返回写进文件里的数据的字节数除非遇到一个错误. direct_io 挂载选项被指定时也会抛出一个异常 ( read() 函数一样).
  • statfs: int (*statfs) (const char *, struct statfs *);
    取得文件系统的统计数据. f_type f_fsid 域将被忽略.
  • flush: int (*flush) (const char *, struct fuse_file_info *);
    将把缓存中的数据刷新到磁盘上. 它和 fsync() 函数不一样 - 不会同步脏数据. close() 关闭文件描述符时会调用flush() 函数, 因此如果一个文件系统想要在close()期间返回写错误并且这个文件已经缓存了一些脏数据, 这是一个写回数据并返回一些错误的好地方. 而许多的应用程序都忽略close() 的错误, 它不总是有用的.

注意: flush() 方法对于每个open()可能被调用不止一次. 如果文件通过dup(), dup2(), 或者fork() 系统调用被打开它将会发生很多次. 还不能确定一个刷新是否是最终的, 因此每一个刷新都将被公平的被对待. 多次的写-刷新序列是相对的少见, 所以这将不是一个问题.

  • release: int (*release) (const char *, struct fuse_file_info *);
    释放一个被打开的文件. 当他们不在引用一个打开的文件时release() 被调用-- 所有的文件描述符都被关闭后并且所有的内存映射都被解除映射后. 对于每一个 open() 调用, 都将是一个带有同样标志位和文件描述符的release() 调用. 一个文件很可能被打开不止一次, 而在这种情况下仅仅是最后一次释放操作将被统计并且不在对文件进行读写. 释放操作的返回值将被忽略.
  • fsync: int (*fsync) (const char *, int, struct fuse_file_info *);
    同步文件的内容. 如果 datasync 参数是非零值, 那么仅仅是用户数据将被刷新到磁盘上, 而不是元数据.
  • setxattr: int (*setxattr) (const char *, const char *, const char *, size_t, int);
    getxattr: int (*getxattr) (const char *, const char *, char *, size_t);
    listxattr: int (*listxattr) (const char *, char *, size_t);
    removexattr: int (*removexattr) (const char *, const char *);
    这些分别是设置, 取得, 列出, 并且删除文件的扩展属性.

最终的文件系统

你的文件系统看起来有下面的一些东西:

afsfuse_client   <--RX[RPC]-->   afsfuse_server

afsfuse_client 经运送文件系统的调用到驻留在远程机器上的afsfuse_server. afsfuse_server 将为所有的来自于客户端的请求提供服务并且返回结果给客户端. 它将作所有的必要的工作. RPC提供的机制是RX. 不为用户数据或者是元数据提供缓存.

【作者: colding】【访问统计:】【2006年08月24日 星期四 17:51】【 加入博采】【打印

Trackback

你可以使用这个链接引用该篇文章 http://publishblog.blogchina.com/blog/tb.b?diaryID=5571743

回复

验证码:   
评论内容: