首页 > 代码库 > Linux虚拟文件系统(VFS)学习

Linux虚拟文件系统(VFS)学习

  虚拟文件系统(Virtual Filesystem)也可称之为虚拟文件系统转换(Virtual Filesystem Switch),是一个内核软件层,用来处理与Unix标准文件系统相关的所有系统调用。其健壮性表现在能为各种文件系统提供一个通用的接口。

通用文件系统模型

  VFS所隐含的主要思想在于引入一个通用的文件系统模型(common file model),这个模型能够表示所有支持的文件系统。在通用文件模型中,每个目录被看做一个文件,可以包含若干文件和其他的子目录。

通用文件模型由下列对象类型组成:

超级块对象(superblock object)

存放已安装文件系统的有关信息(A superblock object represents a mounted filesystem)。对基于磁盘的文件系统,这类对象通常对应于存放在磁盘上的文件系统控制块

索引节点对象(inode object)

存放关于具体文件的一般信息(An inode object represents an object within the filesystem)。对于基于磁盘的文件系统,这类对象通常对应于存放在磁盘上的文件控制块。每个索引节点对象都有一个索引节点号,这个节点号唯一地标识文件系统中的文件。

文件对象(file object)

存放打开文件与进程之间进行交互的有关信息(A file object represents a file opened by a process)。这类信息仅当进程访问文件期间存在于内核内存中。

目录项对象(dentry object)

存放目录项(也就是文件的特定名称)与对应文件进行链接的有关信息。

VFS的数据结构

这里只列举和进程相关的结构

索引节点对象

文件系统处理文件所需要的所有信息都放在一个名为索引节点的数据结构中。文件名可以随时更改,但是索引节点对文件是唯一的,并且随着文件的存在而存在。内存中索引节点对象由一个struct inode数据结构构成。

struct inode {
	struct hlist_node	i_hash;     //用于散列链表
	struct list_head	i_list;		/* backing dev IO list */
	struct list_head	i_sb_list;  
	struct list_head	i_dentry;   //引用索引节点的目录项对象链表头
	unsigned long		i_ino;      //索引节点号
	atomic_t		i_count;        //引用计数器
	unsigned int		i_nlink;    //硬链接数目
	uid_t			i_uid;          //所有者标识符
	gid_t			i_gid;          //组标识符
	dev_t			i_rdev;         //实设备标识符
	u64			i_version;          //版本号(每次使用后递增)
	loff_t			i_size;         //文件的字节数
#ifdef __NEED_I_SIZE_ORDERED
	seqcount_t		i_size_seqcount;
#endif
	struct timespec		i_atime;    //上次访问文件的时间
	struct timespec		i_mtime;    //上次写文件的时间
	struct timespec		i_ctime;    //上次修改索引节点的时间
	blkcnt_t		i_blocks;       //文件的块数
	unsigned int		i_blkbits;  //块的位数
	unsigned short          i_bytes;//块的字节数
	umode_t			i_mode;         //文件的类型和访问权限
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	struct mutex		i_mutex;
	struct rw_semaphore	i_alloc_sem; //在直接I/O文件操作中避免出现竞争条件的读写信号量
	const struct inode_operations	*i_op; //索引节点的操作
	const struct file_operations	*i_fop;	/*缺省文件操作former ->i_op->default_file_ops */
	struct super_block	*i_sb;         //指向超级块的指针
	struct file_lock	*i_flock;
	struct address_space	*i_mapping; //指向address_space对象的指针
	struct address_space	i_data;     //文件的address_space对象
#ifdef CONFIG_QUOTA
	struct dquot		*i_dquot[MAXQUOTAS]; //索引节点磁盘限额
#endif
	struct list_head	i_devices;  //用于具体的字符或块设备索引节点链表
	union {
		struct pipe_inode_info	*i_pipe; //如果文件是个管道则使用它
		struct block_device	*i_bdev;     //指向块设备驱程序的指针
		struct cdev		*i_cdev;         //指向字符设备驱动程序的指针
	};

	__u32			i_generation; //索引节点的版本号

#ifdef CONFIG_FSNOTIFY
	__u32			i_fsnotify_mask; /* all events this inode cares about */
	struct hlist_head	i_fsnotify_mark_entries; /* fsnotify mark entries */
#endif

#ifdef CONFIG_INOTIFY
	struct list_head	inotify_watches; /* watches on this inode */
	struct mutex		inotify_mutex;	/* protects the watches list */
#endif

	unsigned long		i_state;  //索引节点状态标志
	unsigned long		dirtied_when;	/* jiffies of first dirtying */

	unsigned int		i_flags;  //文件系统安装标志

	atomic_t		i_writecount;
#ifdef CONFIG_SECURITY
	void			*i_security;
#endif
#ifdef CONFIG_FS_POSIX_ACL
	struct posix_acl	*i_acl;
	struct posix_acl	*i_default_acl;
#endif
	void			*i_private; /* fs or device private pointer */
};
文件对象

文件对象描述进程怎样与一个打开的文件进行交互。文件对象是在文件被打开时创建的,由一个file结构组成。文件对象在磁盘上是没有对应的映像,因此file结构中没有设置“脏”字段来表示文件对象是否已被修改。

struct file {
	/*
	 * fu_list becomes invalid after file_free is called and queued via
	 * fu_rcuhead for RCU freeing
	 */
	union {
		struct list_head	fu_list;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path;
#define f_dentry	f_path.dentry //与文件相关的目录项对象
#define f_vfsmnt	f_path.mnt    //含有该文件的已安装文件系统
	const struct file_operations	*f_op; //文件操作表指针
	spinlock_t		f_lock;  /* f_ep_links, f_flags, no IRQ */
	atomic_long_t		f_count; //文件对象的引用计数器
	unsigned int 		f_flags; //当打开文件时所指定的标志
	fmode_t			f_mode;      //进程访问模式
	loff_t			f_pos;       //当前的文件偏移量
	struct fown_struct	f_owner; //通过信号进行I/O事件通知的数据
	const struct cred	*f_cred;
	struct file_ra_state	f_ra; //文件预读状态

	u64			f_version; //版本号,每次使用后自动递增
#ifdef CONFIG_SECURITY
	void			*f_security;
#endif
	/* needed for tty driver, and maybe others */
	void			*private_data;//指向特定文件系统或设备驱动程序所需要数据的指针

#ifdef CONFIG_EPOLL
	/* Used by fs/eventpoll.c to link all the hooks to this file */
	struct list_head	f_ep_links;//文件的事件轮询等待着链表头
#endif /* #ifdef CONFIG_EPOLL */
	struct address_space	*f_mapping;//指向文件地址空间对象的指针
#ifdef CONFIG_DEBUG_WRITECOUNT
	unsigned long f_mnt_write_state;
#endif
};
文件对象通过一个名为filp的slab高速缓存分配,filp描述符地址存放在filp_cachep变量中。由于分配的文件对象数目是有限的,因此files_stat变量在其max_files字段中指定了可分配的文件对象的最大数目,也就是系统可同时访问的最大文件数。

内核初始化期间,files_init()函数把max_files字段设置为可用RAM大小的1/10。不过,系统管理员可以通过写/proc/sys/fs/file-max文件来修改这个值。而且即使max_files个文件对象已经被分配,超级用户也总是可以获得一个文件对象

void __init files_init(unsigned long mempages)
{ 
	int n; 

	filp_cachep = kmem_cache_create("filp", sizeof(struct file), 0,
			SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);

	/*
	 * One file with associated inode and dcache is very roughly 1K.
	 * Per default don't use more than 10% of our memory for files. 
	 */ 

	n = (mempages * (PAGE_SIZE / 1024)) / 10;
	files_stat.max_files = n; 
	if (files_stat.max_files < NR_FILE)
		files_stat.max_files = NR_FILE;
	files_defer_init();
	percpu_counter_init(&nr_files, 0);
} 
目录项对象

VFS把每个目录看做若干子目录和文件组成的一个普通文件。一旦目录项被读入内存,VFS就把它转换成基于dentry结构的一个目录项对象。对于进程查找的路径名中的每个分量,内核都为其创建一个目录项对象;目录项对象将每个分量与其对应的索引节点相联系。例如在查找路径名/tmp/test时,内核为根目录“/”创建一个目录项对象,为根目录下的tmp项创建第二级目录项对象,为/tmp目录下的test创建一个第三级目录项对象。

目录项对象在磁盘上并没有对应的映像,因此在dentry结构中不包含指出该对象已被修改的字段。目录项对象存放在名为dentry_cache的高速缓存中。

struct dentry {
	atomic_t d_count;        //目录项对象引用计数
	unsigned int d_flags;		/* 目录项高速缓存标志protected by d_lock */
	spinlock_t d_lock;		/* per dentry lock */
	int d_mounted;          //对目录而言,用于记录安装该目录项的文件系统计数器
	struct inode *d_inode;		/* 与文件名关联的索引节点Where the name belongs to - NULL is
					 * negative */
	/*
	 * The next three fields are touched by __d_lookup.  Place them here
	 * so they all fit in a cache line.
	 */
	struct hlist_node d_hash;	/* lookup hash list */
	struct dentry *d_parent;	/* 父目录的目录项对象parent directory */
	struct qstr d_name;         //文件名

	struct list_head d_lru;		/* LRU list */
	/*
	 * d_child and d_rcu can share memory
	 */
	union {
		struct list_head d_child;	/* child of parent list */
	 	struct rcu_head d_rcu;
	} d_u;
	struct list_head d_subdirs;	/* our children */
	struct list_head d_alias;	/* inode alias list */
	unsigned long d_time;		/* used by d_revalidate */
	const struct dentry_operations *d_op;//目录项方法
	struct super_block *d_sb;	/* 文件的超级块对象The root of the dentry tree */
	void *d_fsdata;			/* 依赖于文件系统的数据fs-specific data */

	unsigned char d_iname[DNAME_INLINE_LEN_MIN];	/* small names */
};
与进程相关的文件

每个进程都有它自己当前的工作目录和他自己的根目录。这仅仅是内核用来表示进程与文件系统相互作用所必须维护的数据的两个例子。类型为fs_struct的整个数据结构就用于此目的

struct fs_struct {
	int users;
	rwlock_t lock;
	int umask;    //当打开文件设置文件权限是所用的位掩码
	int in_exec;
	struct path root, pwd; /* 根目录的目录项,根目录所安装的文件系统对象 
                              当前工作的目录项,当前工作目录所安装的文件系统对象*/
};
进程当前打开的文件与files_struct结构有关

struct fdtable {
	unsigned int max_fds;  //文件对象的当前最大数目
	struct file ** fd;      /* current fd array */
	fd_set *close_on_exec;
	fd_set *open_fds;
	struct rcu_head rcu;
	struct fdtable *next;
};

/*
 * Open file table structure
 */
struct files_struct {
  /*
   * read mostly part
   */
	atomic_t count;     //共享进程的数目
	struct fdtable *fdt; //
	struct fdtable fdtab;
  /*
   * written part on a separate cache line in SMP
   */
	spinlock_t file_lock ____cacheline_aligned_in_smp;
	int next_fd;
	struct embedded_fd_set close_on_exec_init;
	struct embedded_fd_set open_fds_init;
	struct file * fd_array[NR_OPEN_DEFAULT];//文件对象指针的初始化数组
};

fd字段指向文件对象的指针数组。该数组的长度存放在max_fds字段中。通常,fd字段指向files_struct结构中的fd_array字段,该字段包括32个文件对象指针。如果进程打开的文件数据多于32,内核就分配一个新的、更大的文件指针数组,并将其地址存放在fd字段中,内核同时更新max_fds字段的值。

对应fd数组中有元素的每个文件来说,数组的索引就是文件描述符。通常,数组的第一个元素(索引0)是进程的标准输入文件,数组的第二个元素(索引1)是进程的标准输出文件,数组的第三个元素(索引2)是进程的标准错误输出文件。
内核在进程描述符的signal->rlim[RLIM_NLIMITS]结构上强制动态限制文件描述符的最大数;这个值通常为1024,但如果进程具有超级权限,就可以增大这个值。

最常用的特殊文件系统类型

名字           安装点     说明

bdev           无        块设备

binfmt_misc    任意      其他可执行格式

devpts         /dev/pts  伪终端支持

eventpollfs    无        由有效事件轮询机制使用

futexfs        无        由futex(快速用户态加锁)机制使用

pipefs         无        管道

proc           /proc     对内核数据结构的常规访问点

rootfs         无        为启动阶段提供一个空的根目录

shm            无        IPC共享线性区

mqueue         任意       实现POSIX消息队列时使用

sockfs         无         套接字

sysfs          /sys       对系统参数的常规访问

tmpfs          任意        临时文件(如果不被交换出去就保持在RAM中)

usbfs          /proc/bus/usb USB设备

文件系统注册

每个注册的文件系统都用一个类型为file_system_type的对象来表示

struct file_system_type {
	const char *name;  //文件系统名
	int fs_flags;      //文件系统标志
	int (*get_sb) (struct file_system_type *, int,
		       const char *, void *, struct vfsmount *); //读取超级块的方法
	void (*kill_sb) (struct super_block *); //删除超级块的方法
	struct module *owner;    //指向实现文件系统的模块的指针
	struct file_system_type * next;//指向文件系统链表中下一个元素的指针
	struct list_head fs_supers; //具有相同文件系统类型的超级块对象链表头

	struct lock_class_key s_lock_key;
	struct lock_class_key s_umount_key;

	struct lock_class_key i_lock_key;
	struct lock_class_key i_mutex_key;
	struct lock_class_key i_mutex_dir_key;
	struct lock_class_key i_alloc_sem_key;
};
以sockfs文件系统注册为例

static struct file_system_type sock_fs_type = {
	.name =		"sockfs",
	.get_sb =	sockfs_get_sb,
	.kill_sb =	kill_anon_super,
};

static int __init sock_init(void)
{
	/*
	 *      Initialize sock SLAB cache.
	 */

	sk_init();

	/*
	 *      Initialize skbuff SLAB cache
	 */
	skb_init();

	/*
	 *      Initialize the protocols module.
	 */

	init_inodecache();
    /* 注册sockfs文件系统 */
	register_filesystem(&sock_fs_type);
	sock_mnt = kern_mount(&sock_fs_type);

	/* The real protocol initialization is performed in later initcalls.
	 */

#ifdef CONFIG_NETFILTER
	netfilter_init();
#endif

	return 0;
}

Linux虚拟文件系统(VFS)学习