VFS毕竟是虚拟的,它无法涉及到具体文件系统的细节,所以必然在VFS和具体文件系统之间有一些接口,这就是VFS设计的一些有关操作的数据结构。这些数据结构就好象是一个标准,具体文件系统要想被Linux支持,就必须按这个标准来编写自己操作函数。实际上,也正是这样,各种Linux支持的具体文件系统都有一套自己的操作函数,在安装时,这些结构体的成员指针将被初始化,指向对应的函数。如果说VFS体现了Linux的优越性的话,那么这些数据结构的设计就体现了VFS的优越性所在。图8.5是VFS和具体文件系统的关系示意图。
这个示意图还无法完全反映这种关系。因为对每个具体文件系统来说,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更新某个文件系统的inode。inode的i_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) |
更新由目录项所指定文件的已缓存的属性(通常由网络文件系统调用) |
Setattr(dentry,attr) |
设置目录项的属性 |
Getattr(dentry,attr) |
获得目录项的属性 |
以上这些方法均适用于所有的文件系统,但对某一个具体文件系统来说,可能只用到其中的一部分方。例如,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.2和2.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 */
};
这种方式显然简单明了,在设备驱动程序的开发中,经常会用到这种形式。