之前计划中是没有这一篇的,不过在书中经常提到v节点,i节点的,我觉得是有必要去研究一波,不能要是盯着API看吧,API是使用的,总要看看底层的原理吧。
4.1 虚拟文件系统
虚拟文件系统(有时也称作虚拟文件交换,更常见的是简称VFS)做为内核子系统,为用户空间程序提供了文件和文件系统相关的接口。系统中的其他的具体的文件系统也是依靠VFS系统协同工作的,应用层的系统调用也是通过VFS工作的,所以VFS是一个中间抽象层的作用。
我们在以前几节,总结了应用编程使用的函数,比如open(),read(),write() 等函数,之所以可以使用这些通通用接口对所有类型的文件系统进行操作,是因为内核在它的底层文件系统接口上建立了一个抽象层(VFS)。该抽象层是linux能够支持各种文件系统,并且为应用层提供了一套通用的接口。
VFS抽象层之所以能够衔接各种各样的文件系统,是因为抽象出了所有文件系统的共性(设计模式中有讲,设计一个通用性的接口),这些共性部分,比如文件,目录这些概念还有删除文件,创建文件这些操作,我们经常使用文件系统,也明白这些操作就是我们日常使用的,然后各个文件系统会支持VFS所期望的这些概念和操作。
我们应用直接调用write函数,之后这个系统调用会被VFS的一个通用的系统调用sys_write()处理,VFS再根据fd所在的文件系统找到实际的文件系统中的写操作,然后执行具体的写操作,这样才写入到存储介质中,完成写操作。
4.2 Unix文件系统
Unix使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点和安装点。
安装点:Unix中,文件系统被安装在一个特定的安装点上,所有的已安装文件系统都作为根文件系统(/)树的枝叶出现在系统中。
文件:每一个文件为了便于系统和用户识别,都被分配了一个便于理解的名字。典型的文件操作有 读、写、创建、删除等。
目录:文件通过目录组织起来。文件目录好比一个文件夹,用来容纳相关文件,并且目录也可以包含其他目录,即子目录,所以目录也是可以嵌套的,这样就形成了文件路径。在Unix中,目录属于普通文件,它列出包含在其中的所有文件,并且可以对目录执行和文件相同的操作。
索引节点:Unix系统将文件的相关信息和文件本身这两个概念加以区分,例如访问控制权限,大小。拥有者,创建时间等信息。文件相关信息,有时候被称作文件的元数据,被存储在一个单独的数据结构中,该结构被称为索引结点inode。(大名鼎鼎的i节点,我们在应用编程的stat函数,就是获取i节点的数据)。
4.3 VFS对象及其数据结构
VFS是采用面向对象的设计思路,使用一组数据结构来代表通用文件对象。这些数据结构类似于对象。但是内核是c语言写的,所以都是使用结构体来模拟类,这类结构体中包含操作这些数据的函数指针。
VFS中有四个主要的对象类型,他们分别是:
- 超级块对象,它代表一个具体的已安装文件系统,一个文件系统挂载上就会创建一个超级块对象
- 索引节点对象,它代表一个具体文件
- 目录项对象,它代表一个目录项,是路径的一个组成部分。
- 文件对象,它代表由进程打开的文件
VFS将目录作为一个文件来处理,所以不存在目录对象。
4.3.1 超级块对象
各个文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,通常对应于存放在磁盘特定扇区中的文件系统超级块或文件系统控制块(所以称为超级块对象)。对于并非基于磁盘的文件系统(如基于内存的文件系统,比如sysfs),他们会在使用现场创建超级块并将其保存到内存中。
下面是以内核4.4.16版本的的超级块源码
struct super_block {
struct list_head s_list; /* 指向所有超级块的链表 */
dev_t s_dev; /* 设备标识符 */
unsigned char s_blocksize_bits; /* 以位为单位的快大小 */
unsigned long s_blocksize; /* 以字节为单位的快大小 */
loff_t s_maxbytes; /* 最大文件大小 */
struct file_system_type *s_type; /* 文件系统类型 */
const struct super_operations *s_op; /* 超级块操作方法 */
const struct dquot_operations *dq_op; /* 磁盘限额方法(具体不清楚) */
const struct quotactl_ops *s_qcop; /* 限额控制方法 */
const struct export_operations *s_export_op; /* 导出方法 */
unsigned long s_flags; /* 挂在标记 */
unsigned long s_iflags; /* internal SB_I_* flags(不知道是啥) */
unsigned long s_magic; /* 文件系统的幻数(标记一个文件系统的id,是唯一的) */
struct dentry *s_root; /* 目录挂载点 */
struct rw_semaphore s_umount; /* 卸载信号量 */
int s_count; /* 超级块引用计数 */
atomic_t s_active; /* 活动引用计数 */
#ifdef CONFIG_SECURITY
void *s_security; /* 安全模块 */
#endif
const struct xattr_handler **s_xattr; /* 扩展的属性操作 */
struct hlist_bl_head s_anon; /* 匿名目录项 */
struct list_head s_mounts; /* list of mounts; _not_ for fs use */
struct block_device *s_bdev; /* 相关的块设备 */
struct backing_dev_info *s_bdi; /* */
struct mtd_info *s_mtd; /* 存储磁盘的信息 */
struct hlist_node s_instances; /* 该类型文件系统 */
unsigned int s_quota_types; /* Bitmask of supported quota types */
struct quota_info s_dquot; /* Diskquota specific options */
struct sb_writers s_writers;
char s_id[32]; /* Informational name */
u8 s_uuid[16]; /* UUID */
void *s_fs_info; /* Filesystem private info */
unsigned int s_max_links;
fmode_t s_mode; /* 安装权限 */
/* Granularity of c/m/atime in ns.
Cannot be worse than a second */
u32 s_time_gran;
/*
* The next field is for VFS *only*. No filesystems have any business
* even looking at it. You had been warned.
*/
struct mutex s_vfs_rename_mutex; /* Kludge */
/*
* Filesystem subtype. If non-empty the filesystem type field
* in /proc/mounts will be "type.subtype"
*/
char *s_subtype; /* 子类性名称 */
/*
* Saved mount options for lazy filesystems using
* generic_show_options()
*/
char __rcu *s_options; /* 已存安装选项 */
const struct dentry_operations *s_d_op; /* default d_op for dentries */
/*
* Saved pool identifier for cleancache (-1 means none)
*/
int cleancache_poolid;
struct shrinker s_shrink; /* per-sb shrinker handle */
/* Number of inodes with nlink == 0 but still referenced */
atomic_long_t s_remove_count;
/* Being remounted read-only */
int s_readonly_remount;
/* AIO completions deferred from interrupt context */
struct workqueue_struct *s_dio_done_wq;
struct hlist_head s_pins;
/*
* Keep the lru lists last in the structure so they always sit on their
* own individual cachelines.
*/
struct list_lru s_dentry_lru ____cacheline_aligned_in_smp;
struct list_lru s_inode_lru ____cacheline_aligned_in_smp;
struct rcu_head rcu;
struct work_struct destroy_work;
struct mutex s_sync_lock; /* sync serialisation lock */
/*
* Indicates how deep in a filesystem stack this SB is
*/
int s_stack_depth;
/* s_inode_list_lock protects s_inodes */
spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;
struct list_head s_inodes; /* all inodes */
};
创建、管理和撤销超级快对象的代码在于文件fs/super.c中。超级快对象通过alloc_super()函数创建并初始化,在文件系统安装时,文件系统会调用该函数以便从磁盘读取文件系统超级快,并且将其信息填充到内存中的超级快对象中。
超级快操作方法:
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb); //创建一个新的索引节点对象
void (*destroy_inode)(struct inode *); //用于释放给定的索引节点
void (*dirty_inode) (struct inode *, int flags); //索引节点被修改时会调用此函数
int (*write_inode) (struct inode *, struct writeback_control *wbc);//给定的索引节点写入磁盘
int (*drop_inode) (struct inode *); //
void (*evict_inode) (struct inode *);
void (*put_super) (struct super_block *);
int (*sync_fs)(struct super_block *sb, int wait); //使文件系统的数据元与磁盘上的文件系统同步
int (*freeze_super) (struct super_block *);
int (*freeze_fs) (struct super_block *);
int (*thaw_super) (struct super_block *);
int (*unfreeze_fs) (struct super_block *);
int (*statfs) (struct dentry *, struct kstatfs *); //获取文件系统状态,stat函数就是调用这个
int (*remount_fs) (struct super_block *, int *, char *);
void (*umount_begin) (struct super_block *);
int (*show_options)(struct seq_file *, struct dentry *);
int (*show_devname)(struct seq_file *, struct dentry *);
int (*show_path)(struct seq_file *, struct dentry *);
int (*show_stats)(struct seq_file *, struct dentry *);
#ifdef CONFIG_QUOTA
ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
struct dquot **(*get_dquots)(struct inode *);
#endif
int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
long (*nr_cached_objects)(struct super_block *,
struct shrink_control *);
long (*free_cached_objects)(struct super_block *,
struct shrink_control *);
};
感觉意义也不大,就是熟悉一下超级快的概念,现在肯定不能分析内核源码,比较我们现在是应用编程专题,只是简单的普及一下。
4.3.2 索引节点对象
索引节点对象包含了内核在操作文件或目录时需要的全部信息。**对于Unix风格的文件系统来说,这些信息可以从磁盘索引节点直接读入。**如果一个文件系统没有索引节点,文件系统都必须从中提取这些信息。有些文件系统是将文件描述信息作为文件的一部分存放,但是不管采用哪种方式,索引节点对象必须在内存中创建,以便于文件系统使用。
索引节点的结构:
struct inode {
umode_t i_mode; //访问权限
unsigned short i_opflags; //
kuid_t i_uid; //使用者id
kgid_t i_gid; //使用组id
unsigned int i_flags;
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
const struct inode_operations *i_op; //索引节点操作函数
struct super_block *i_sb; //相关的超级快
struct address_space *i_mapping; //相关的地址映射
#ifdef CONFIG_SECURITY
void *i_security;
#endif
/* Stat data, not accessed from path walking */
unsigned long i_ino; //节点号
/*
* Filesystems may only read i_nlink directly. They shall use the
* following functions for modification:
*
* (set|clear|inc|drop)_nlink
* inode_(inc|dec)_link_count
*/
union { //硬连接数
const unsigned int i_nlink;
unsigned int __i_nlink;
};
dev_t i_rdev; //实际设备标识符
loff_t i_size; //以字节为单位的文件大小。stat函数可以获取
struct timespec i_atime; //最后访问时间
struct timespec i_mtime; //最后修改时间
struct timespec i_ctime; //最后改变时间
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned short i_bytes; //使用的字节数
unsigned int i_blkbits; //以位为单位的快大小
blkcnt_t i_blocks; //文件块数
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
/* Misc */
unsigned long i_state;
struct mutex i_mutex;
unsigned long dirtied_when; /* jiffies of first dirtying */
unsigned long dirtied_time_when;
struct hlist_node i_hash;
struct list_head i_io_list; /* backing dev IO list */
#ifdef CONFIG_CGROUP_WRITEBACK
struct bdi_writeback *i_wb; /* the associated cgroup wb */
/* foreign inode detection, see wbc_detach_inode() */
int i_wb_frn_winner;
u16 i_wb_frn_avg_time;
u16 i_wb_frn_history;
#endif
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
union {
struct hlist_head i_dentry;
struct rcu_head i_rcu;
};
u64 i_version; //版本号
atomic_t i_count; //引用计数
atomic_t i_dio_count;
atomic_t i_writecount;
#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct file_lock_context *i_flctx;
struct address_space i_data;
struct list_head i_devices; /* 块设备链表 */
union {
struct pipe_inode_info *i_pipe; //管道信息
struct block_device *i_bdev; //块设备驱动
struct cdev *i_cdev; //字符设备驱动
char *i_link;
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_marks;
#endif
void *i_private; /* fs or device private pointer */
};
一个索引节点代表文件系统中(索引节点仅当文件打开被访问时,才在内存中创建)的一个文件,它可以是设备或者管道这样的特殊文件。
这个就是我们所知道的i节点,里面存放了文件的一些信息,现在看到结构体中的数据,确实也发现了,可以在应用通过stat函数获取。
索引节点操作:
struct inode_operations {
struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
const char * (*follow_link) (struct dentry *, void **);
int (*permission) (struct inode *, int);
struct posix_acl * (*get_acl)(struct inode *, int);
int (*readlink) (struct dentry *, char __user *,int);
void (*put_link) (struct inode *, void *);
int (*create) (struct inode *,struct dentry *, umode_t, bool);
//VFS通过系统调用creat()和open()来调用该函数,从而为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 *,umode_t);
//被系统mkdir调用,创建一个新的目录
int (*rmdir) (struct inode *,struct dentry *);
int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t);
//创建特殊文件:设备文件,命名管道或套接字
int (*rename) (struct inode *, struct dentry *,
struct inode *, struct dentry *);
//移动文件
int (*rename2) (struct inode *, struct dentry *,
struct inode *, struct dentry *, unsigned int);
int (*setattr) (struct dentry *, struct iattr *);
//在修改索引节点后,通过发生了“改变事件”
int (*getattr) (struct vfsmount *mnt, 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);
//向value中拷贝给定文件的扩展属性name对应的数值
ssize_t (*listxattr) (struct dentry *, char *, size_t);
int (*removexattr) (struct dentry *, const char *);
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
u64 len);
int (*update_time)(struct inode *, struct timespec *, int);
int (*atomic_open)(struct inode *, struct dentry *,
struct file *, unsigned open_flag,
umode_t create_mode, int *opened);
int (*tmpfile) (struct inode *, struct dentry *, umode_t);
int (*set_acl)(struct inode *, struct posix_acl *, int);
} ____cacheline_aligned;
这一组操作,感觉比较熟悉了,就是我们对文件目录的删除,创建等,因为只有删除,创建才会影响到i节点,不会影响到文件中的内容。
这一节,先介绍这么多了,下一节再把后面的补上。