虚拟文件系统作为内核子系统,为用户空间程序提供了文件和文件系统相关的接口。
VFS(虚拟文件系统)。VFS使得用户可以直接使用open(),read()和write()这样的系统调用而不用关注具体文件系统和实际物理介质。也许你感觉不是很新奇啊,告诉你新奇的事情:在老式操作系统上(比如DOS),任何对非本地文件系统的访问都必须依靠特殊工具才能完成。这种实现的方式是内核在它的底层文件系统接口上建立了一个抽象层。该抽象层是linux能够支持各种文件系统,即便是它们在功能和行为上存在很大差别。为了支持文件系统,VFS提供了一个通用文件系统模型,该模型囊括了我们所能想到的文件系统的常用功能和行为。这个VFS抽象层之所以能衔接各种各样的文件系统,是因为它定义了所有文件系统都支持的基本抽象接口和数据结构,同时实际系统也将自身的诸如“如何打开文件”,“目录是什么”等概念在形式上与VFS的定义保持一致。因为实际文件系统的代码在统一的接口和数据结构隐藏了具体的实现细节,所以在VFS层和内核的其他部分看来,所有文件系统都是相同的,它们都支持像文件和目录这样的概念,同时也支持像创建和删除文件这样的操作。
实际文件系统通过编程提供VFS所期望的抽象接口和数据结构,这样,内核就可以毫不费力地和任何文件系统协同工作。那么接下的问题,它们直接的关系如何呢,看下边的例子:
1
|
write(f,&buf,len);
|
该代码不用说,应该明白。这个用户调用首先被一个通用系统调用sys_write()处理,sys_write()函数要找到f所在的文件系统实际给出的是哪个写操作,然后再执行该操作。实际文件系统的写方法是文件系统实现的一部分,数据最终通过该操作写入介质。下图给出流程:
VFS有四个主要对象类型
(1)超级块对象,它代表的是一个具体的已安装的文件系统
(2)索引节点对象,它代表一个具体文件
(3)目录项对象,它代表一个目录项,是路径的一个组成部分
(4)文件对象,它代笔由进程打开的文件。
下面就详细对这四种对象结构对操作进行分析。
1.超级块对象:代表一个已安装的文件系统。由数据结构super_block结构体表示,定义在linux/fs.h中。如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
struct
super_block {
struct
list_head s_list;
/* list of all superblocks */
dev_t s_dev;
/* identifier */
unsigned
long
s_blocksize;
/* block size in bytes */
unsigned
long
s_old_blocksize;
/* old block size in bytes */
unsigned
char
s_blocksize_bits;
/* block size in bits */
unsigned
char
s_dirt;
/* dirty flag */
unsigned
long
long
s_maxbytes;
/* max file size */
struct
file_system_type s_type;
/* filesystem type */
struct
super_operations s_op;
/* superblock methods */
struct
dquot_operations *dq_op;
/* quota methods */
struct
quotactl_ops *s_qcop;
/* quota control methods */
struct
export_operations *s_export_op;
/* export methods */
unsigned
long
s_flags;
/* mount flags */
unsigned
long
s_magic;
/* filesystem's magic number */
struct
dentry *s_root;
/* directory mount point */
struct
rw_semaphore s_umount;
/* unmount semaphore */
struct
semaphore s_lock;
/* superblock semaphore */
int
s_count;
/* superblock ref count */
int
s_syncing;
/* filesystem syncing flag */
int
s_need_sync_fs;
/* not-yet-synced flag */
atomic_t s_active;
/* active reference count */
void
*s_security;
/* security module */
struct
list_head s_dirty;
/* list of dirty inodes */
struct
list_head s_io;
/* list of writebacks */
struct
hlist_head s_anon;
/* anonymous dentries */
struct
list_head s_files;
/* list of assigned files */
struct
block_device *s_bdev;
/* associated block device */
struct
list_head s_instances;
/* instances of this fs */
struct
quota_info s_dquot;
/* quota-specific options */
char
s_id[32];
/* text name */
void
*s_fs_info;
/* filesystem-specific info */
struct
semaphore s_vfs_rename_sem;
/* rename semaphore */
};
|
创建,管理和销毁超级块对象的代码位于文件fs/super.c中,超级块对象通过alloc_super()函数创建并初始化。在文件系统安装时,内核会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存中的超级块对象中。其中最重要的一个是s_op,指向超级块的操作函数表,由super_operations结构体表示,定义在linux/fs.h中,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
struct
super_operations {
struct
inode *(*alloc_inode) (
struct
super_block *sb);
void
(*destroy_inode) (
struct
inode *);
void
(*read_inode) (
struct
inode *);
void
(*dirty_inode) (
struct
inode *);
void
(*write_inode) (
struct
inode *,
int
);
void
(*put_inode) (
struct
inode *);
void
(*drop_inode) (
struct
inode *);
void
(*delete_inode) (
struct
inode *);
void
(*put_super) (
struct
super_block *);
void
(*write_super) (
struct
super_block *);
int
(*sync_fs) (
struct
super_block *,
int
);
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 *);
int
(*show_options) (
struct
seq_file *,
struct
vfsmount *);
};
|
当文件系统需要对其超级块执行操作时,首先要在超级块对象中寻找需要的操作方法。比如一个文件系统要写自己的超级块,需要调用:sb->s_op->write_super(sb)这里的sb是指向文件系统超级块的指针,沿着该指针进入超级块操作函数表,并从表中取得希望得到的write_super()函数,该函数执行写入超级块的实际操作。
2.索引节点对象:由inode结构体表示,定义在linux/fs.h中,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
struct
inode {
struct
hlist_node i_hash;
/* hash list */
struct
list_head i_list;
/* list of inodes */
struct
list_head i_dentry;
/* list of dentries */
unsigned
long
i_ino;
/* inode number */
atomic_t i_count;
/* reference counter */
umode_t i_mode;
/* access permissions */
unsigned
int
i_nlink;
/* number of hard links */
uid_t i_uid;
/* user id of owner */
gid_t i_gid;
/* group id of owner */
kdev_t i_rdev;
/* real device node */
loff_t i_size;
/* file size in bytes */
struct
timespec i_atime;
/* last access time */
struct
timespec i_mtime;
/* last modify time */
struct
timespec i_ctime;
/* last change time */
unsigned
int
i_blkbits;
/* block size in bits */
unsigned
long
i_blksize;
/* block size in bytes */
unsigned
long
i_version;
/* version number */
unsigned
long
i_blocks;
/* file size in blocks */
unsigned
short
i_bytes;
/* bytes consumed */
spinlock_t i_lock;
/* spinlock */
struct
rw_semaphore i_alloc_sem;
/* nests inside of i_sem */
struct
semaphore i_sem;
/* inode semaphore */
struct
inode_operations *i_op;
/* inode ops table */
struct
file_operations *i_fop;
/* default inode ops */
struct
super_block *i_sb;
/* associated superblock */
struct
file_lock *i_flock;
/* file lock list */
struct
address_space *i_mapping;
/* associated mapping */
struct
address_space i_data;
/* mapping for device */
struct
dquot *i_dquot[MAXQUOTAS];
/* disk quotas for inode */
struct
list_head i_devices;
/* list of block devices */
struct
pipe_inode_info *i_pipe;
/* pipe information */
struct
block_device *i_bdev;
/* block device driver */
unsigned
long
i_dnotify_mask;
/* directory notify mask */
struct
dnotify_struct *i_dnotify;
/* dnotify */
unsigned
long
i_state;
/* state flags */
unsigned
long
dirtied_when;
/* first dirtying time */
unsigned
int
i_flags;
/* filesystem flags */
unsigned
char
i_sock;
/* is this a socket? */
atomic_t i_writecount;
/* count of writers */
void
*i_security;
/* security module */
__u32 i_generation;
/* inode version number */
union
{
void
*generic_ip;
/* filesystem-specific info */
} u;
};
|
有时,某些文件系统可能并不能完整的包含索引节点结构体要求的所有信息。举个例子,有的文件系统可能并不记录文件的创建时间,这时,该文件系统就可以在实现中选择任意合适的办法来解决这个问题,它可以在i_ctime中存储0,或者让i_ctime等于i_mtime,甚至任何其他值。索引节点对象中的inode_operations项存放了操作函数列表,定义在linux/fs.h中,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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
, dev_t);
int
(*
rename
) (
struct
inode *,
struct
dentry *,
struct
inode *,
struct
dentry *);
int
(*readlink) (
struct
dentry *,
char
*,
int
);
int
(*follow_link) (
struct
dentry *,
struct
nameidata *);
int
(*put_link) (
struct
dentry *,
struct
nameidata *);
void
(*truncate) (
struct
inode *);
int
(*permission) (
struct
inode *,
int
);
int
(*setattr) (
struct
dentry *,
struct
iattr *);
int
(*getattr) (
struct
vfsmount *,
struct
dentry *,
struct
kstat *);
int
(*setxattr) (
struct
dentry *,
const
char
*,
const
void
*,
size_t
,
int
);
ssize_t (*getxattr) (
struct
dentry *,
const
char
*,
void
*,
size_t
);
ssize_t (*listxattr) (
struct
dentry *,
char
*,
size_t
);
int
(*removexattr) (
struct
dentry *,
const
char
*);
};
|
同样,操作调用时,用以下方式:i->i_op->truncate(i).
3.目录项对象.目录项的概念上节已经说了,我就不多说.目录项中也可包括安装点.在路径/mnt/cdrom/foo中,/,mnt,cdrom都属于目录项对象。目录项由dentry结构体表示,定义在文件linux/dcache.h中,描述如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
struct
dentry {
atomic_t d_count;
/* usage count */
unsigned
long
d_vfs_flags;
/* dentry cache flags */
spinlock_t d_lock;
/* per-dentry lock */
struct
inode *d_inode;
/* associated inode */
struct
list_head d_lru;
/* unused list */
struct
list_head d_child;
/* list of dentries within */
struct
list_head d_subdirs;
/* subdirectories */
struct
list_head d_alias;
/* list of alias inodes */
unsigned
long
d_time;
/* revalidate time */
struct
dentry_operations *d_op;
/* dentry operations table */
struct
super_block *d_sb;
/* superblock of file */
unsigned
int
d_flags;
/* dentry flags */
int
d_mounted;
/* is this a mount point? */
void
*d_fsdata;
/* filesystem-specific data */
struct
rcu_head d_rcu;
/* RCU locking */
struct
dcookie_struct *d_cookie;
/* cookie */
struct
dentry *d_parent;
/* dentry object of parent */
struct
qstr d_name;
/* dentry name */
struct
hlist_node d_hash;
/* list of hash table entries */
struct
hlist_head *d_bucket;
/* hash bucket */
unsigned
char
d_iname[DNAME_INLINE_LEN_MIN];
/* short name */
};
|
由于目录项并非真正保存在磁盘上,所有目录项没有对应的磁盘数据结构,VFS根据字符串形式的路径名现场创建它,目录项结构体也没有是否被修改的标志。目录项对象有三种状态:被使用,未被使用和负状态。一个被使用的目录项对应一个有效的索引节点(即d_inode指向相应的索引节点)并且该对象存在一个或多个使用者(即d_count为正值)。一个未被使用的目录项对应一个有效的索引节点(d_inode指向一个索引节点),但是VFS当前并未使用它(d_count为0)。该目录项对象仍然指向一个有效对象,而且被保留在内存中以便需要时再使用它。显然这样要比重新创建要效率高些。一个负状态的目录项没有对应的有效索引节点(d_inode为NULL).因为索引节点已被删除了,或路径不再正确了,但是目录项仍然保留,以便快速解析以后的路径查询。虽然负的状态目录项有些用处,但如果需要的话话,还是可以删除的,可以销毁它。
结构体dentry_operation指明了VFS操作目录的所有方法,如下:
1
2
3
4
5
6
7
8
|
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 *);
};
|
其实,如果VFS遍历路径名中所有的元素并将它们逐个地解析成目录项对象,将是一件非常耗时的事情。所以内核将目录项对象缓存在目录项缓存(dcache)中,目录项缓存包括三个主要部分:
1.“被使用的”目录项链表,该链表通过索引节点对象中的i_dentry项连接相关的索引节点,因为一个给定的索引节点可能有多个链接,所以就可能有多 个目录项对象,因此用一个链表来连接它们。 2.“最近被使用的”双向链表。该链表包含未被使用的和负状态的目录项对象。该链表是按时间插入的。 3. 哈希表和相应的哈希函数用来快速地将给定路径解析为相关目录项对象。 |
哈希表有数组dentry_hashtable表示,其中每一个元素都是一个指向具有相同键值的目录项对象链表的指针。数组的大小取决于系统中物理内存的大小。实际的哈希值由d_hash()计算,它是内核提供给文件系统的唯一的一个哈希函数。查找哈希表要通过d_lookup()函数,如果该函数在dcache中发现了与其相匹配的目录项对像,则匹配对象被返回;否则,返回NULL指针。dcache在一定意义上也提供了对索引节点的缓存。和目录项对象相关的索引节点对象不会被释放,因为目录项会让相关索引节点的使用计数为正,这样就可以确保索引节点留在内存中。只要目录项被缓存,其相应的索引节点也就被缓存了。
4.文件对象:文件对象表示进程以打开的文件。文件对象仅仅在进程观点上代表已打开文件,它反过来指向目录项对象(反过来指向索引节点),其实只有目录项对象才表示已打开的实际文件。虽然一个文件对应的文件对象不是唯一的,但对应的索引节点和目录项对象无疑是唯一的。文件对象由file结构表示,定义在文件linux/fs.h中,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
struct
file {
struct
list_head f_list;
/* list of file objects */
struct
dentry *f_dentry;
/* associated dentry object */
struct
vfsmount *f_vfsmnt;
/* associated mounted fs */
struct
file_operations *f_op;
/* file operations table */
atomic_t f_count;
/* file object's usage count */
unsigned
int
f_flags;
/* flags specified on open */
mode_t f_mode;
/* file access mode */
loff_t f_pos;
/* file offset (file pointer) */
struct
fown_struct f_owner;
/* owner data for signals */
unsigned
int
f_uid;
/* user's UID */
unsigned
int
f_gid;
/* user's GID */
int
f_error;
/* error code */
struct
file_ra_state f_ra;
/* read-ahead state */
unsigned
long
f_version;
/* version number */
void
*f_security;
/* security module */
void
*private_data;
/* tty driver hook */
struct
list_head f_ep_links;
/* list of eventpoll links */
spinlock_t f_ep_lock;
/* eventpoll lock */
struct
address_space *f_mapping;
/* page cache mapping */
};
|
文件对象的操作有file_operations结构表示,在linux/fs.h中,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
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 (*aio_read) (
struct
kiocb *,
char
*,
size_t
, loff_t);
ssize_t (*write) (
struct
file *,
const
char
*,
size_t
, loff_t *);
ssize_t (*aio_write) (
struct
kiocb *,
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
);
int
(*aio_fsync) (
struct
kiocb *,
int
);
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 (*sendfile) (
struct
file *, loff_t *,
size_t
,
read_actor_t,
void
*);
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
);
int
(*check_flags) (
int
flags);
int
(*dir_notify) (
struct
file *filp, unsigned
long
arg);
int
(*flock) (
struct
file *filp,
int
cmd,
struct
file_lock *fl);
};
|
最后,除了以上几种VFS基础对象外,内核还使用了另外一些数据结构来管理文件系统的其它相关数据,如下:
与文件系统相关的数据结构
1.file_system_type:因为linux支持众多的文件系统,所以内核必有由一个特殊的结构来描述每种文件系统的功能和行为:
1
2
3
4
5
6
7
8
9
10
11
12
|
struct
file_system_type {
const
char
*name;
/* filesystem's name */
struct
subsystem subsys;
/* sysfs subsystem object */
int
fs_flags;
/* filesystem type flags */
/* the following is used to read the superblock off the disk */
struct
super_block *(*get_sb) (
struct
file_system_type *,
int
,
char
*,
void
*);
/* the following is used to terminate access to the superblock */
void
(*kill_sb) (
struct
super_block *);
struct
module *owner;
/* module owning the filesystem */
struct
file_system_type *next;
/* next file_system_type in list */
struct
list_head fs_supers;
/* list of superblock objects */
};
|
其中,get_sb()函数从磁盘上读取超级块,并且在文件系统被安装时,在内存中组装超级块对象,剩余的函数描述文件系统的属性。每种文件系统,不管有多少个实力安装到系统中,还是根本就没有安装到系统中,都只有一个file_system_type结构。更有趣的是,当文件系统被实际安装时,将有一个vfsmount结构体在安装点被创建。该结构体被用来代表文件系统的实例----换句话说,代表一个安装点.
2.vfsmount结构被定义在linux/mount.h中,下面是具体结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
struct
vfsmount {
struct
list_head mnt_hash;
/* hash table list */
struct
vfsmount *mnt_parent;
/* parent filesystem */
struct
dentry *mnt_mountpoint;
/* dentry of this mount point */
struct
dentry *mnt_root;
/* dentry of root of this fs */
struct
super_block *mnt_sb;
/* superblock of this filesystem */
struct
list_head mnt_mounts;
/* list of children */
struct
list_head mnt_child;
/* list of children */
atomic_t mnt_count;
/* usage count */
int
mnt_flags;
/* mount flags */
char
*mnt_devname;
/* device file name */
struct
list_head mnt_list;
/* list of descriptors */
struct
list_head mnt_fslink;
/* fs-specific expiry list */
struct
namespace
*mnt_namespace
/* associated namespace */
};
|
vfs中维护的各种链表是为了跟踪文件系统和所有其他安装点的关系,mnt_flags保存了安装时指定的标志信息,下表给出了标准的安装标志:
安装那些管理不充分信任的移动设备时,这些标志很有用处。
与进程相关的数据结构
系统中每一个进程都有自己的一组打开的文件,有三个数据结构将VFS层和文件的进程紧密联系在一起,它们分别是file_struct,fs_struct和namespace.
1.file_struct:该结构体有进程描述符中的files域指向,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
struct
files_struct {
atomic_t count;
/* structure's usage count */
spinlock_t file_lock;
/* lock protecting this structure */
int
max_fds;
/* maximum number of file objects */
int
max_fdset;
/* maximum number of file descriptors */
int
next_fd;
/* next file descriptor number */
struct
file **fd;
/* array of all file objects */
fd_set *close_on_exec;
/* file descriptors to close on exec() */
fd_set *open_fds;
/* pointer to open file descriptors */
fd_set close_on_exec_init;
/* initial files to close on exec() */
fd_set open_fds_init;
/* initial set of file descriptors */
struct
file *fd_array[NR_OPEN_DEFAULT];
/* default array of file objects */
};
|
fd数组指针指向以打开的文件对象链表,默认情况下,指向fd_arrar数组。NR_OPEN_DEFAULT默认是32,所以该数组可以容纳32个文件对象。如果一个进程所打开的文件对象超过32个,内核将分配一个新数组,并且将fd指针指向它。这个值也是可以调整的。
2.第二个结构体是fs_struct:由进程描述符的fs域指向。它包含文件系统和进程相关的信息,在linux/fs_struct.h中,如下:
1
2
3
4
5
6
7
8
9
10
11
|
struct
fs_struct {
atomic_t count;
/* structure usage count */
rwlock_t lock;
/* lock protecting structure */
int
umask;
/* default file permissions*/
struct
dentry *root;
/* dentry of the root directory */
struct
dentry *pwd;
/* dentry of the current directory */
struct
dentry *altroot;
/* dentry of the alternative root */
struct
vfsmount *rootmnt;
/* mount object of the root directory */
struct
vfsmount *pwdmnt;
/* mount object of the current directory */
struct
vfsmount *altrootmnt;
/* mount object of the alternative root */
};
|
该结构包含了当前进程的当前工作目录和根目录。
3.最后一个是namespace:由进程描述符namespace域指向,定义在linux/namespace.h中,如下:
1
2
3
4
5
6
|
struct
namespace
{
atomic_t count;
/* structure usage count */
struct
vfsmount *root;
/* mount object of root directory */
struct
list_head list;
/* list of mount points */
struct
rw_semaphore sem;
/* semaphore protecting the namespace */
};
|
list域是连接已安装文件系统的双向链表,它包含的元素组成了全体命令空间。 上述这些数据结构都是通过进程描述符连接起来的。对多数进程来说,它们的描述符都指向唯一的files_struct和fs_struct结构体。但是,对于那些使用克隆标志CLONE_FILES或CLONE_FS创建的进程,会共享这两个结构体。所以多个进程描述符可能指向同一个files_struct或fs_struct结构体。每个结构体都维护一个count域作为引用计数,它防止进程正使用该结构时,该结构被销毁。而namespace却不是这样,默认情况下,所有的进程共享同样的命名空间,也就是说,它们都看到同一个文件层层结构。只有在进行clone()操作时使用CLONE_NEWS标志,才会给进程一个另外的命名空间结构体的拷贝。