8.2.6 有关操作的数据结构

VFS毕竟是虚拟的,它无法涉及到具体文件系统的细节,所以必然在VFS和具体文件系统之间有一些接口,这就是VFS设计的一些有关操作的数据结构。这些数据结构就好象是一个标准,具体文件系统要想被Linux支持,就必须按这个标准来编写自己操作函数。实际上,也正是这样,各种Linux支持的具体文件系统都有一套自己的操作函数,在安装时,这些结构体的成员指针将被初始化,指向对应的函数。如果说VFS体现了Linux的优越性的话,那么这些数据结构的设计就体现了VFS的优越性所在。图8.5VFS和具体文件系统的关系示意图。

这个示意图还无法完全反映这种关系。因为对每个具体文件系统来说,VFS都有相应的数据结构对应,而不是如图中那样简单化了。


8.5  VFS和具体文件系统的关系示意图

有关操作的数据结构主要有以下几个,分别用来操作VFS中的几个重要的数据结构。

1.超级块操作

   超级块操作是由super_operations数据结构来描述的,该结构的起始地址存放在超级块的s_op域中。该结构定义于fs.h中:

/*

* NOTE: write_inode, delete_inode, clear_inode, put_inode can be called

* without the big kernel lock held in all filesystems.

*/

struct super_operations {

        void (*read_inode) (struct inode *);

        void (*read_inode2) (struct inode *, void *) ;

        void (*dirty_inode) (struct inode *);

        void (*write_inode) (struct inode *, int);

        void (*put_inode) (struct inode *);

        void (*delete_inode) (struct inode *);

        void (*put_super) (struct super_block *);

        void (*write_super) (struct super_block *);

        void (*write_super_lockfs) (struct super_block *);

        void (*unlockfs) (struct super_block *);

        int (*statfs) (struct super_block *, struct statfs *);

        int (*remount_fs) (struct super_block *, int *, char *);

        void (*clear_inode) (struct inode *);

        void (*umount_begin) (struct super_block *);

    其中的每个函数就叫做超级块的一个方法,表8.3给予描述:

 

         

 

  8.3 超级块对象的方法及其描述

 

函数形式

描述

Read_inode(inode)

 Inode的地址是该函数的参数,inode中的i_no域表示从磁盘要读取的具体文件系统的inode用磁盘上的数据填充参数inode的域。

Dirty_inode(inode)

inode标记为“脏”

Write_inode(inode)

用参数指定的inode更新某个文件系统的inodeinodei_ino域标识指定磁盘上文件系统的索引节点。

Put_inode(inode)  

释放参数指定的索引节点对象。释放一个对象并不意味着释放内存,因为其它进程可能仍然在使用这个对象。该方法是可选的(即并非所有的文件系统都有相应的处理函数)

Delete_inode(inode)

删除那些包含文件、磁盘索引节点及VFS索引节点的数据块

Notify_change(dentry, iattr)

依照参数iattr值修改索引节点的一些属性。如果没有定义该函数,VFS转去执行write_inode(  ) 方法。

Put_super(super)

释放超级块对象

Write_super(super)

将超级块的信息写回磁盘,该方法是可选的

Statfs(super, buf, bufsize)

将文件系统的统计信息填写在buf缓冲区中。

Remount_fs(super, flags, data)

 

用新的选项重新安装文件系统(当某个安装选项必须被修改时进行调用)

Clear_inode(inode)

put_inode类似,但同时也把索引节点对应文件中的数据占用的所有页释放

Umount_begin(super)

 

中断一个安装操作(只在网络文件系统中使用)。

  上面这些方法对所有的文件系统都是适用的,但对于一个具体的文件系统来说,可能只用到其中的几个方法。如果那些方法没有定义,则对应的域为空。

 

2.索引节点操作inode_operations

索引节点操作是由inode_operations结构来描述的,主要是用来将VFS对索引节点的操作转化为具体文件系统处理相应操作的函数,在fs.h中描述如下:

struct inode_operations {

        int (*create) (struct inode *,struct dentry *,int);

        struct dentry * (*lookup) (struct inode *,struct dentry *);

        int (*link) (struct dentry *,struct inode *,struct dentry *);

        int (*unlink) (struct inode *,struct dentry *);

        int (*symlink) (struct inode *,struct dentry *,const char *);

        int (*mkdir) (struct inode *,struct dentry *,int);

        int (*rmdir) (struct inode *,struct dentry *);

        int (*mknod) (struct inode *,struct dentry *,int,int);

        int (*rename) (struct inode *, struct dentry *,

                        struct inode *, struct dentry *);

        int (*readlink) (struct dentry *, char *,int);

        int (*follow_link) (struct dentry *, struct nameidata *);

        void (*truncate) (struct inode *);

       int (*permission) (struct inode *, int);

        int (*revalidate) (struct dentry *);

        int (*setattr) (struct dentry *, struct iattr *);

        int (*getattr) (struct dentry *, struct iattr *);

};

8.4对索引节点的每个方法给予描述:

8.4 索引节点对象的方法及其描述

函数形式

描述

Create(dir, dentry, mode)

 

在某个目录下,为与dentry目录项相关的常规文件创建一个新的磁盘索引节点。

Lookup(dir, dentry)

查找索引节点所在的目录,这个索引节点所对应的文件名就包含在dentry目录项中。

Link(old_dentry, dir, new_dentry)

创建一个新的名为new_dentry硬链接,这个新的硬连接指向dir目录下名为old_dentry文件。

unlink(dir, dentry)

dir目录删除dentry目录项所指文件的硬链接

symlink(dir, dentry, symname)

在某个目录下,为与目录项相关的符号链创建一个新的索引节点

mkdir(dir, dentry, mode)

在某个目录下,为与目录项对应的目录创建一个新的索引节点。

mknod(dir, dentry, mode, rdev)

dir目录下,为与目录项对象相关的特殊文件创建一个新的磁盘索引节点。其中参数mode rdev分别表示文件的类型和该设备的主码。

rename(old_dir, old_dentry, new_dir, new_dentry)

old_dir目录下的文件 old_dentry移到new_dir目录下,新文件名包含在 new_dentry指向的目录项中

readlink(dentry, buffer, buflen)

dentry所指定的符号链中对应的文件路径名拷贝到buffer所指定的内存区。

follow_link(inode, dir)

解释inode索引节点所指定的符号链;如果该符号链是相对路径名,从指定的dir目录开始进行查找。

truncate(inode)

修改索引节点inode所指文件的长度。在调用该方法之前,必须将inode对象的i_size域设置为需要的新长度值。

permission(inode, mask)

确认是否允许对inode索引节点所指的文件进行指定模式的访问。

revalidate(dentry)

更新由目录项所指定文件的已缓存的属性(通常由网络文件系统调用)

Setattrdentryattr

设置目录项的属性

Getattrdentryattr

获得目录项的属性

 

   以上这些方法均适用于所有的文件系统,但对某一个具体文件系统来说,可能只用到其中的一部分方。例如,msdos文件系统其公用索引节点的操作在fs/msdos/namei.c中定义如下:

    struct inode_operations msdos_dir_inode_operations = {

         create:         msdos_create,

         lookup:         msdos_lookup,

         unlink:         msdos_unlink,

         mkdir:          msdos_mkdir,

         rmdir:          msdos_rmdir,

         rename:         msdos_rename,

         setattr:        fat_notify_change,

};

3.目录项操作

  目录项操作是由dentry_operations数据结构来描述的,定义于include/linux/dcache.h中:

struct dentry_operations {

        int (*d_revalidate)(struct dentry *, int);

        int (*d_hash) (struct dentry *, struct qstr *);

        int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);

        int (*d_delete)(struct dentry *);

        void (*d_release)(struct dentry *);

        void (*d_iput)(struct dentry *, struct inode *);

};

下表给出目录项对象的方法及其描述:

8.5 目录项对象的方法及其描述

      函数形成

描述

d_revalidate(dentry)

判定目录项是否有效。默认情况下,VFS函数什么也不做,而网络文件系统可以指定自己的函数。

 

d_hash(dentry, hash)

生成一个哈希值;对目录项哈希表而言,这是一个具体文件系统的哈希函数。参数dentry标识包含该路径分量的目录。参数hash指向一个结构,该结构包含要查找的路径名分量以及由hash函数生成的哈希值。

 

d_compare(dir, name1, name2)

比较两个文件名;name1应该属于dir所指目录。默认情况下,VFS的这个函数就是常用的字符串匹配函数。不过,每个文件系统可用自己的方式实现这一方法。例如,MS-DOS文件系统不区分大写和小写字母。

 

d_delete(dentry)

如果对目录项的最后一个引用被删除(d_count变为“0”),就调用该方法。默认情况下,VFS的这个函数什么也不做。

 

d_release(dentry)

当要释放一个目录项时(放入slab分配器),就调用该方法。默认情况下,VFS的这个函数什么也不做。

 

d_iput(dentry, ino)

当要丢弃目录项对应的索引节点时,就调用该方法。默认情况下,VFS的这个函数调用iput( )释放索引节点。

 

 

4.文件操作

文件操作是由file_operations结构来描述的。其定义在fs.h中:

  /*

813  * NOTE:

814  * read, write, poll, fsync, readv, writev can be called

815  *   without the big kernel lock held in all filesystems.

  */

 struct file_operations {

      struct module *owner;

      loff_t (*llseek) (struct file *, loff_t, int);

      ssize_t (*read) (struct file *, char *, size_t, loff_t *);

      ssize_t (*write) (struct file *, const char *, size_t, loff_t *);

      int (*readdir) (struct file *, void *, filldir_t);

      unsigned int (*poll) (struct file *, struct poll_table_struct *);

      int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

      int (*mmap) (struct file *, struct vm_area_struct *);

      int (*open) (struct inode *, struct file *);

      int (*flush) (struct file *);

      int (*release) (struct inode *, struct file *);

      int (*fsync) (struct file *, struct dentry *, int datasync);

      int (*fasync) (int, struct file *, int);

      int (*lock) (struct file *, int, struct file_lock *);

    ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);

    ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);

    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long,      unsigned long, unsigned long);

};

   这个数据结构就是连接VFS文件操作与具体文件系统的文件操作之间的枢纽,也是编写设备驱动程序的重要接口,后面还会给出进一步的说明。对每个函数的描述如表8.6

 

8.6 文件操作的描述

  

函数形式

描述

Owner()

 指向模块的指针。只有驱动程序才把这个域置为THIS_MODULE,文件系统一般忽略这个域。

llseek(file, offset, whence)

修改文件指针

read(file, buf, count, offset)

从文件的 offset处开始读出count字节,然后增加*offset的值

write(file, buf, count, offset)

从文件的*offset处开始写入count字节,然后增加*offset的值

readdir(dir, dirent, filldir)

返回dir所指目录的下一个目录项,这个值存入参数dirent;参数filldir存放一个辅助函数的地址,该函数可以提取目录项的各个域

poll(file, poll_table)

检查是否存在关于某文件的操作事件,如果没有则睡眠,直到发生该类操作事件为止

ioctl(inode, file, cmd, arg)

向一个基本硬件设备发送命令。该方法只适用于设备文件。

mmap(file, vma)

执行文件的内存映射,并将这个映射放入进程的地址空间

open(inode, file)

通过创建一个新的文件而打开一个文件,并把它链接到相应的索引节点

flush(file)

当关闭对一个打开文件的引用时,就调用该方法。也就是说,减少该文件对象f_count域的值。该方法的实际用途是依赖于具体文件系统的。

release(inode, file)

释放文件对象。当关闭对打开文件的最后一个引用时,也就是说,该文件对象f_count域的值变为0时,调用该方法。

fsync(file, dentry)

file文件在高速缓存中的全部数据写入磁盘

fasync(file, on)

通过信号来启用或禁用异步I/O通告

check_media_change(dev)

检测自上次对设备文件操作以来是否存在介质的改变(可以对块设备使用这一方法,因为它支持可移动介质——比如软盘和CD-ROM)。

 

revalidate(dev)

恢复设备的一致性(由网络文件系统使用,这是在确认某个远程设备上的介质已被改变之后才使用。

lock(file, cmd, file_lock)

file文件申请一个锁

readv(file, iovec, count, offset)

read()类似,所不同的是,readv()把读入的数据放在多个缓冲区中(叫缓冲区向量)。

Writev(file, buf, iovec, offset)

write()类似。所不同的是,writev()把数据写入多个缓冲区中(叫缓冲区向量)

 

VFS中定义的这个file_operations数据结构相当于一个标准模板,对于一个具体的文件系统来说,可能只用到其中的一些函数。注意,2.22.4版在对file_operations进行初始化时有所不同,在2.2版中,如果某个函数没有定义,则将其置为NULL,如:

struct file_operations device_fops = {

NULL,           /* seek */

device_read,       /* read */

device_write,      /* write */

NULL,           /* readdir */

NULL,           /* poll */

NULL,           /* ioctl */

NULL,           /* mmap */

device_open,       /* open */

NULL,           /* flush */

device_release     /* release */

};

  这是标准C的用法,在2.4版中,采用了gcc的扩展用法,如:

   struct file_operations device_fops = {

    read : device_read,    /* read */

    write : device_write,      /* write */

    open : device_open,    /* open */

    release : device_release   /* release */

};

这种方式显然简单明了,在设备驱动程序的开发中,经常会用到这种形式。