在linux的文件系统VFS中,所有的的文件信息都由inode结构来表示,不论普通文件、设备文件、目录都无一例外,即使是根目录\。inode是linux管理文件系统的最基本单位。它是内核找到文件数据或者设备资源的桥梁,同时也是文件与页缓存的映射关系的桥梁。
- struct inode {
- struct hlist_node i_hash;
- struct list_head i_list;
- struct list_head i_dentry;
- unsigned long i_ino;
- atomic_t i_count;
- umode_t i_mode;
- unsigned int i_nlink;
- uid_t i_uid;
- gid_t i_gid;
- dev_t i_rdev;
- loff_t i_size;
- struct timespec i_atime;
- struct timespec i_mtime;
- struct timespec i_ctime;
- unsigned int i_blkbits;
- unsigned long i_blksize;
- unsigned long i_version;
- unsigned long i_blocks;
- unsigned short i_bytes;
- spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
- struct semaphore i_sem;
- struct rw_semaphore i_alloc_sem;
- struct inode_operations *i_op;
- 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;
- struct address_space i_data;
- #ifdef CONFIG_QUOTA
- struct dquot *i_dquot[MAXQUOTAS];
- #endif
- /* These three should probably be a union */
- struct list_head i_devices;
- struct pipe_inode_info *i_pipe;
- struct block_device *i_bdev;
- struct cdev *i_cdev;
- int i_cindex;
- unsigned long i_dnotify_mask; /* Directory notify events */
- struct dnotify_struct *i_dnotify; /* for directory notifications */
- unsigned long i_state;
- unsigned long dirtied_when; /* jiffies of first dirtying */
- unsigned int i_flags;
- unsigned char i_sock;
- atomic_t i_writecount;
- void *i_security;
- __u32 i_generation;
- union {
- void *generic_ip;
- } u;
- #ifdef __NEED_I_SIZE_ORDERED
- seqcount_t i_size_seqcount;
- #endif
- };
然而仅仅有inode来表示文件的个体信息还是不足够的,我们还需要各个文件之间的结构关系。在bash中我们都习惯了使用路径,它是查找文件的必要手段,在VFS中路径的每一项就是一个dentry
- struct dentry {
- atomic_t d_count;
- unsigned int d_flags; /* protected by d_lock */
- spinlock_t d_lock; /* per dentry lock */
- 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 16-byte range, with 16-byte alignment.
- */
- struct dentry *d_parent; /* parent directory */
- struct hlist_head *d_bucket; /* lookup hash bucket */
- struct qstr d_name;
- struct list_head d_lru; /* LRU list */
- struct list_head d_child; /* child of parent list */
- struct list_head d_subdirs; /* our children */
- struct list_head d_alias; /* inode alias list */
- unsigned long d_time; /* used by d_revalidate */
- struct dentry_operations *d_op;
- struct super_block *d_sb; /* The root of the dentry tree */
- int d_mounted;
- void *d_fsdata; /* fs-specific data */
- struct rcu_head d_rcu;
- struct dcookie_struct *d_cookie; /* cookie, if any */
- struct hlist_node d_hash; /* lookup hash list */
- unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
- };
也许你会注意到在dentry中有指向inode的指针,而inode中没有指向dentry的指针。平常系统要操作文件之前,都需要先定位文件的位置,这个查找定位的工作是通过目录项完成的,当定位完成以后。才使用inode中的信息去操作文件的数据。
现在我们有了索引节点inode和目录项dentry两个基本要素。但这还不够,我们还需要一套组织文件、管理文件的策略,这些策略就是各种文件系统,如:ext2、ext3、fat、ntfs、nfs、sysfs、proc等。
文件系统规定了文件如何存放,文件头需要包含哪些信息,这些信息的排列顺序,如此一来程序就可以按既定顺序来解析文件头中信息,再根据信息定位文件的数据块所在的位置,然后就可以通过设备驱动进行读写操作了。
不同的文件系统对文件信息有着不同的定义,因此内核中有许多套文件系统的代码来实现各自特别的数据结构和操作方法。但是在各种不同的文件系统之上,linux按照系统调用的操作需求规划出了统一的数据结构和接口,形成了VFS文件系统。VFS使得内核更上层的部分(系统调用)可以忽略各种文件系统的不同,单纯地进行打开、关闭、读、写等操作。
各个文件系统的信息抽象到VFS后由super_block结构体表示
- struct super_block {
- struct list_head s_list; /* Keep this first */
- dev_t s_dev; /* search index; _not_ kdev_t */
- unsigned long s_blocksize;
- unsigned long s_old_blocksize;
- unsigned char s_blocksize_bits;
- unsigned char s_dirt;
- unsigned long long s_maxbytes; /* Max file size */
- struct file_system_type *s_type;
- struct super_operations *s_op;
- struct dquot_operations *dq_op;
- struct quotactl_ops *s_qcop;
- struct export_operations *s_export_op;
- unsigned long s_flags;
- unsigned long s_magic;
- struct dentry *s_root;
- struct rw_semaphore s_umount;
- struct semaphore s_lock;
- int s_count;
- int s_syncing;
- int s_need_sync_fs;
- atomic_t s_active;
- void *s_security;
- struct list_head s_dirty; /* dirty inodes */
- struct list_head s_io; /* parked for writeback */
- struct hlist_head s_anon; /* anonymous dentries for (nfs) exporting */
- struct list_head s_files;
- struct block_device *s_bdev;
- struct list_head s_instances;
- struct quota_info s_dquot; /* Diskquota specific options */
- int s_frozen;
- wait_queue_head_t s_wait_unfrozen;
- char s_id[32]; /* Informational name */
- void *s_fs_info; /* Filesystem private info */
- /*
- * The next field is for VFS *only*. No filesystems have any business
- * even looking at it. You had been warned.
- */
- struct semaphore s_vfs_rename_sem; /* Kludge */
- };
虽然super_block规划出了统一的数据结构和接口,但是如何从不同的文件系统中取出相应数据,却需要不同的方法。因此super_block中提供了一个域s_type,其类型是file_system_type,在file_system_type中有get_sb()函数,允许各个文件系统定义自己抓取文件系统信息方法。
- struct file_system_type {
- const char *name;
- int fs_flags;
- struct super_block *(*get_sb) (struct file_system_type *, int,
- const char *, void *);
- void (*kill_sb) (struct super_block *);
- struct module *owner;
- struct file_system_type * next;
- struct list_head fs_supers;
- };
此外,linux中还有一个特别的地方。
linux的挂载点其实是一个目录项,因此各个挂载点之间可能不是独立的,有可能存在从属关系。比如我们将A分区挂载在/home/a/,然后将B分区挂载在/home/a/b/。如果在这种情况下,我们umount A分区,那么B分区的挂载路径将被破坏。所以linux需要一套机制来管理挂载点之间的关系,这套机制中的核心数据就是vfsmount
- struct vfsmount
- {
- struct list_head mnt_hash;
- struct vfsmount *mnt_parent; /* fs we are mounted on */
- struct dentry *mnt_mountpoint; /* dentry of mountpoint */
- struct dentry *mnt_root; /* root of the mounted tree */
- struct super_block *mnt_sb; /* pointer to superblock */
- struct list_head mnt_mounts; /* list of children, anchored here */
- struct list_head mnt_child; /* and going through their mnt_child */
- atomic_t mnt_count;
- int mnt_flags;
- int mnt_expiry_mark; /* true if marked for expiry */
- char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
- struct list_head mnt_list;
- struct list_head mnt_fslink; /* link in fs-specific expiry list */
- struct namespace *mnt_namespace; /* containing namespace */
- };
有了以上的了解,我们可以来看看文件系统是如何创建的
在安装Linux的时候,需要一个分区作为根分区”/”,类型通常为ext3,这个分区中的ext3文件系统就是最初的那棵树,linux启动初始化的时候,会将这个分区的文件系统挂载在VFS中的”/”目录上,”/” 也是一个目录,那它属于什么文件系统呢? 它属于rootfs文件系统,这是特殊文件系统中的一种,存在于内存中,那是如何生成”\”的呢? 当启动时候,引导程序会将linux的内核镜像(不是全部linux)加载的内存后会执行main,main中的Start_kernel()中会调用到mnt_init函数,可以看到这个函数中其中调用了两个函数:init_rootfs()和init_mount_tree(),init_rootfs()函数的作用是通过调用register_filesystem(&rootfs_fs_type)注册rootfs文件系统,init_mount_tree()函数的作用是rootfs文件系统的挂载,并在此文件系统中建立一个”\”目录。其实就是生成一个dentry对象,d_iname域就是\,然后完善其他域的信息。
-----------------------------------------------------------------------------
然后是将根分区进行挂载。由此我们来了解mount挂载的过程。
挂载中首先会生成vfsmount对象,来管理此挂载点与其他挂载点间的关系,首次确认关系其实是通过dentry项进行的,因为dentry中有记录符dentry项的域。
然后挂载一个分区意味着要添加一个新的文件系统对象到内核中,因此要建立对应的super_block对象。vfsmount中的mnt_sb域会指向super_block对象。挂载时会指定文件系统类型,通过super_block中的file_system_type.get_sb()函数可以从相应的设备中获取到super_block所需要的信息。同时会将此super_block的添加到file_system_type.fs_supers域中。
并将super_block中指向其主目录的域s_root,指向所挂载的dentry。然后修改dentry.d_mounted,域d_mounted表示这个目录项是否是一个挂载点。凡是super_block指向的dentry当然都是一个挂载点。
因为挂载后此dentry其实是指向了挂载分区的根目录,挂载完成后,其inode所指向的内容已经变了,因此要更新inode信息,正是在此时将新的文件系统类型的f_ops函数表传给了inode。(从filesystem层传给了VFS层)
-----------------------------------------------------------------------------
之前所介绍的结构体,都是VFS用来进行文件管理所需的,而接下来要介绍的,是VFS提供给上层系统调用的数据接口file结构体。对于用户和进程来说,一个文件就是一个file。用户不会关心超级块,不会关心索引节点,他们关心的只是这个文件,是读是写、操作的偏移位置。file对象是通过open()创建,通过close()销毁。
file是文件在进程中的代表,因此同一个文件会在不同的进程中有各自的代表,也就是说可能有多个file对象指向同一个dentry,但是dentry和inode是唯一的,与磁盘文件、设备文件等一一对应。
文件使用对象的计数、用户信息、访问模式、偏移位置这些参数都是大家所熟悉的。与vfs层的关联就是通过dentry域,与页缓存层相关的就是address_space域,它们都是在使用open()系统调用时关联上的。
- struct file {
- struct list_head f_list;
- struct dentry *f_dentry;
- struct vfsmount *f_vfsmnt;
- struct file_operations *f_op;
- atomic_t f_count;
- unsigned int f_flags;
- mode_t f_mode;
- int f_error;
- loff_t f_pos;
- struct fown_struct f_owner;
- unsigned int f_uid, f_gid;
- struct file_ra_state f_ra;
- unsigned long f_version;
- void *f_security;
- /* 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;
- spinlock_t f_ep_lock;
- #endif /* #ifdef CONFIG_EPOLL */
- struct address_space *f_mapping;
- };
-----------------------------------------------------------------------------
然后还有两个重要的结构体:
和文件系统相关的数据结构
- struct file_system_type {
- const char *name;
- int fs_flags;
- struct super_block *(*get_sb) (struct file_system_type *, int,
- const char *, void *);
- void (*kill_sb) (struct super_block *);
- struct module *owner;
- struct file_system_type * next;
- struct list_head fs_supers;
- };
和进程相关的数据结构
- struct vfsmount
- {
- struct list_head mnt_hash;
- struct vfsmount *mnt_parent; /* fs we are mounted on */
- struct dentry *mnt_mountpoint; /* dentry of mountpoint */
- struct dentry *mnt_root; /* root of the mounted tree */
- struct super_block *mnt_sb; /* pointer to superblock */
- struct list_head mnt_mounts; /* list of children, anchored here */
- struct list_head mnt_child; /* and going through their mnt_child */
- atomic_t mnt_count;
- int mnt_flags;
- int mnt_expiry_mark; /* true if marked for expiry */
- char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
- struct list_head mnt_list;
- struct list_head mnt_fslink; /* link in fs-specific expiry list */
- struct namespace *mnt_namespace; /* containing namespace */
- };
这两个结构的分析将在《通过一个文件操作了解内核系统间的关系》中介绍。
-----------------------------------------------------------------------------
最后总结下VFS的功能:
1.提供面向文件的通用操作
2.将进程、资源、设备、设备中的数据统一抽象成文件形式,并使文件与其相关联
3.将文件实体与页高速缓存形成映射,便于在操作文件时使用页高速缓存
阅读(549) | 评论(0) | 转发(0) |