Linux2.6.33虚拟文件系统
- 虚拟文件系统(虚拟文件交换VFS)作为内核子系统,为用户空间程序提供了文件和文件系统相关的接口
- 系统中所有文件系统不但依赖VFS共存,而且依靠VFS系统协同工作
- VFS把目录当作文件对待,因此可以对目录执行与文件相同的操作
- 文件相关信息(文件的元数据),被存储在一个单独的数据结构中,该结构被称为索引节点inode
- 文件系统的控制信息存储在超级块中,超级块是一种包含文件系统信息的数据结构
VFS对象及其数据结构
- VFS中有四个主要的对象类型
- 超级块对象,代表一个具体的已安装文件系统
- 索引节点对象,代表一个具体文件
- 目录项对象,代表一个目录项,是路径的一个组成部分
- 文件对象,代表有进程打开的文件
- 每个主要对象中都包含一个操作对象,这些操作对象描述了内核针对主要对象可以使用的方法
- super_operations对象,其中包含内核针对特定文件系统所能调用的方法,如write_inode()和sync_fs()等方法
- inode_operations对象,其中包含内核针对特定文件所能调用的方法,如create()和link()等方法
- dentry_operations对象,其中包含内核针对特定目录所能调用的方法,如d_compare()和d_delete()等方法
- file_operations对象,其中包含进程针对已打开文件所能调用的方法,如read()和write()等方法
- 操作对象作为一个结构体指针来实现,此结构体包含指向操作其父对象的函数指针
- VFS使用了大量结构体对象,如每个注册的文件系统都由file_system_type结构体来表示,描述文件系统及其性能;每一个安装点都用vfsmount结构体表示,包含安装点的相关信息,如位置和安装标志等
超级块对象
- 各种文件系统都必须实现超级块对象,该对象用于控制存储特定文件系统的信息,通常对应于存放在磁盘特定扇区中的文件系统超级块或文件系统控制块
- 对于并非基于磁盘的文件系统(如基于内存的文件系统sysfs),会在使用现场创建超级块并将其保存在内存中
- 创建、管理和撤销超级块对象的代码位于fs/super.c中,超级块对象通过alloc_super()函数创建并初始化
- 在文件系统安装时,文件系统会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存中的超级块对象中
#include <linux/fs.h>
struct super_block {
struct list_head s_list;//指向所有超级块的连接
dev_t s_dev;//设备标识符
unsigned long s_blocksize;//以字节为单位的块大小
unsigned char s_blocksize_bits;//以位为单位的块大小
unsigned char s_dir;//修改(脏)标志
unsigned long long s_maxbytes;//文件大小上限
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_need_sync;//尚未同步标志
atomic_t s_active;//活动引用计数
void *s_security;//安全模块
struct xattr_handler **s_xattr;//扩展的属性操作
struct list_head s_inodes;//inodes链表
struct list_head s_dirty;//脏数据链表
struct list_head s_io;//回写链表
struct list_head s_more_io;//更多回写链表
struct list_head s_anon;//匿名目录项
struct list_head s_files;//被分配文件链表
struct list_head s_dentry_lru;//未被使用目录项链表
int s_nr_dentry_unused;//链表总目录项的数目
struct block_device *s_bdev;//相关的块设备
struct mtd_info *s_mtd;//存储磁盘信息
struct list_head s_instances;//该类型文件系统
struct quota_info s_dquot;//限额相关选项
int s_frozen;//frozen标志位
wait_queue_head_t s_wait_unfrozen;//冻结的等待队列
char s_id[32];//文本名字
void *s_fs_info;//文件系统特殊信息
fmode_t s_mode;//安装权限
struct semaphore s_vfs_rename_sem;//重命名信号量
u32 s_time_gran;//时间戳粒度
char *s_subtype;//子类型名称
char *s_options;//已存安装选项
};
超级块操作
- 超级块对象有一个成员s_op,指向超级块的操作函数表
- 操作函数表的每一项都是一个指向超级块操作函数的指针,执行文件系统和索引节点的底层操作
- 当文件系统需要对其超级块执行操作时,首先要在超级块对象中寻找需要操作方法
#include <linux/fs.h>
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode*);
void (*dirty_inode)(struct inode*);
int (*write_inode)(struct inode*, int);
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 *sb, int wait);
int (*freeze_fs)(struct super_block*);
int (*unfreeze_fs)(struct super_block);
int (*statfs)(struct dentry*, struct kstatfs*);
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*);
int (*show_stats)(struct seq_file*, struct vfsmount*);
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);
int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
}
//如果一个文件系统要写自己的超级块,则调用(其中sb指向文件系统超级块指针)
sb->s_op->write_super(sb);
//在给定的超级块下创建和初始化一个新的索引节点对象
struct inode *alloc_inode(struct super_block *sb);
//释放给定的索引节点
void destroy_inode(struct inode *inode);
//VFS在索引节点脏(被修改)时调用此函数,日志文件系统(如ext3和ext4)执行该函数更新日志
void dirty_inode(struct inode *inode);
//将给定的索引节点写入磁盘,wait参数指明操作是否需要同步
void write_inode(struct inode *inode, int wait);
//在最后一个指向索引节点的引用被释放后,VFS调用该函数删除这个索引节点,普通Unix文件系统不会定义该函数
void drop_inode(struct inode *inode);
//从磁盘上删除给定的索引节点
void delete_inode(struct inode *inode);
//在卸载文件系统时由VFS调用,释放超级块,调用者必须一直持有s_lock锁
void put_super(struct super_block *sb);
//给定的超级块更新磁盘上的超级块,VFS通过该函数对内存中的超级块和磁盘中的超级块进行同步,调用者必须一直持有s_lock锁
void write_super(struct super_block *sb);
//使文件系统的数据元与磁盘上的文件系统同步,wait参数指定操作是否同步
int sync_fs(struct super_block *sb, int wait);
//首先禁止对文件系统做改变,再使用给定的超级块更新磁盘上的超级快,目前LVM(逻辑卷标管理)调用该函数
void write_super_lockfs(struct super_block *sb);
//对文件系统解除锁定,是write_super_lockfs()的逆操作
void unlockfs(struct super_block *sb);
//VFS通过调用该函数获取文件系统状态,指定文件系统相关的统计信息将放置在statfs中
int statfs(struct super_block *sb, struct statfs *statfs);
//当指定新的安装选项重新安装文件系统时,VFS调用该函数,调用者必须一直持有lock锁
int remount_fs(struct super_block *sb, int *flags, char *data);
//VFS调用该函数释放索引节点,并清空包含相关数据的所有页面
void clear_inode(struct inode *inode);
//VFS调用该函数中断安装操作,该函数被网络文件系统使用,如NFS
void umount_begin(struct super_block *sb);
- 以上函数都是由VFS在进程上下文中调用
- 除了dirty_inode(),其他函数在必要时都可以阻塞
索引节点对象
- 索引节点对象包含了内核在操作文件或目录时需要的全部信息,对于Unix风格的文件系统,索引节点信息可以从磁盘索引节点之间读入
- 如果一个文件系统没有索引节点,那么不管相关信息如何在磁盘存放,文件系统都必须从中提取信息,没有索引节点的文件系统通常将文件的描述信息作为文件的一部分存放
- 索引节点必须在内存中创建,以便文件系统使用
#include <linux/fs.h>
struct inode {
struct hlist_node i_hash;//散列表
struct list_head i_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;//使用者的id
gid_t i_gid://使用组的id
kdev_t i_rdev;//实际设备标识符
u64 i_version;//版本号
loff_t i_size;//以字节为单位的文件大小
seqcount_t i_size_seqcount;//对i_size进行串行计数
struct timespec i_atime;//最后访问时间
struct timespec i_mtime;//最后修改时间
struct timespec i_ctime;//最后改变时间
unsigned int i_blkbits;//以位为单位的块大小
blkcnt_t i_blocks;//文件的块数
unsigned short i_bytes;//使用的字节数
umode_t i_mode;//访问权限
spinlock_t i_lock;//自旋锁
struct rw_semaphore i_alloc_sem;//嵌入i_sem内部
struct semaphore i_sem;//索引节点信号量
struct inode_operations *i_op;//索引节点操作表
struct file_operations *i_fop;//缺省的索引节点操作
struct super_block *i_sb;//相关的超级块
struct file_lock *i_flock;//文件锁链表
struct address_space *i_mapping;//相关的地址映射
struct address_apace i_data;//设备地址映射
struct dquot *i_dquot[MAXQUOTAS];//索引节点的磁盘限额
struct list_head i_devices;//块设备链表
union {
struct pipe_inode_info *i_pipe;//管道信息
struct block_device *i_bdev;//块设备驱动
struct cdev *i_cdev//字符设备驱动
};
unsigned long i_dnotify_mask;//目录通知掩码
struct dnotify_struct *i_dnotify;//目录通知
struct list_head inotify_watches;//索引节点通知监测链表
struct mutex inotify_mutex;//保护inotify_watches
unsigned long i_state;//状态标志
unsigned long dirtied_when;//第一次弄脏数据的时间
unsigned int i_flags;//文件系统标志
atomic_t i_writecount;//写者技术
void *i_security;//安全模块
void *i_private;//fs私有指针
}
- 一个索引节点代表文件系统中(索引节点仅当文件被访问时,才在内存中创建)的一个文件,可以是设备或管道等特殊文件,因此索引节点结构体中与特殊文件相关的项,如i_pipe指向一个代表有名管道的数据结构,i_bdev指向块设备结构体,i_cdev指向字符设备结构体(一个给定的索引节点每次只能表示三者之一,或三者均不)
索引节点操作
#include <linux/fs.h>
struct inode_operations {
int (*create)(struct inode*, struct dentry*, int, struct nameidata*);
struct dentry *(*lookup)(struct inode*, struct dentry*, struct nameidata*);
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 dengtry*);
int (*readlink)(struct dentry*, char __user*, int);
void *(*follow_link)(struct dentry*, struct nameidata*);
void (*put_link)(struct dentry*, struct nameidata*, void*);
void (*truncate)(struct inode*);
int (*permission)(struct inode*, 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);
ssize_t (*listxattr)(struct dentry*, char*, size_t);
int (*removexattr)(struct dentry*, const char*);
void (*truncate_range)(struct inode*, loff_t, loff_t);
long (*fallocate)(struct inode*, int, loff_t, loff_t);
int (*filemap)(struct inode*, struct filemap_extent_info*, u64, u64);
}
//与超级块类型,对索引节点的操作(i指向给定的索引节点)
i->i_op->truncate(i);
//VFS通过系统调用create()和open()调用该函数,从而为dentry对象创建一个新的索引节点,在创建时使用mode指定的初始模式
int create(struct inode *dir, struct dentry *dentry, int mode);
//该函数在特定目录中寻找索引节点,该索引节点要对应于dentry给出的文件名
struct dentry *lookup(struct inode *dir, struct dentry *dentry);
//该函数被系统调用link()调用,用来创建硬链接,硬链接名称由dentry参数指定,链接对象是dir目录中的old_dentry目录项所代表的文件
int link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry);
//该函数被系统调用unlink()调用,从目录dir中删除由目录项dentry指定的索引节点对象
int unlink(struct inode *dir, struct dentry *dentry);
//该函数被系统调用symlik()调用,创建符号链接,该符号链接名称由symname指定,链接对象是dir目录中的dentry目录项
int symlink(struct inode *dir, struct dentry *dentry, const char *symname);
//该函数被系统调用mkdir()调用,创建一个新目录,创建是使用mode指定的初始模式
int mkdir(struct inode *dir, struct dentry *dentry, int mode);
//该函数被系统调用rmdir()调用,删除dir目录中的dengtry目录项代表的文件
int rmdir(struct inode *dir, struct dentry *dentry);
//该函数被系统调用mknod()调用,创建特殊文件(设备文件、命名管道或套接字),要创建的文件放在dir目录中,其目录项为dentry,关联的设备为rdev,初始权限由mode指定
int mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t rdev);
//VFS调用该函数来移动文件,文件源路径在old_dir目录中,源文件由old_dentry目录项指定,目标路径在new_dir目录中,目标文件由new_dentry指定
int rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new)dentry);
//该函数被系统调用readlink()调用,拷贝数据到特定的缓冲buffer中,拷贝的数据来自dentry指定的符号连接,拷贝大小最大可达buflen字节
int readlink(struct dentry *dentry, char *buffer, int buflen);
//该函数由VFS调用,从一个符号链接查找指向的索引节点,由dentry指向的链接被解析,其结果存放在由nd指向的nameidata结构体中
int follow_link(struct dentry *dentry, struct nameidata *nd);
//在follow_link()调用之后,该函数由VFS调用进行清除工作
int put_link(struct dentry *dentry, struct nameidata *nd);
//该函数由VFS调用,修改文件的大小,在调用前,索引节点的i_size项必须设置为预期的大小
void truncate(struct inode *inode);
//该函数用来检查给定的inode所代表的文件是否允许特定的访问模式,如果允许返回0,否则返回负值的错误码
//多数文件系统都将此区域设置为NULL,使用VFS提供的通用方法进行检查,这种检查仅仅比较索引节点对象中的访问模式位是否和给定的mask一致
int permission(struct inode *inode, int mask);
//该函数被notify_change()调用,在修改索引节点后,通知发生“改变事件”
int setattr(struct dentry *dentry, struct iattr *attr);
//在通知索引节点需要从磁盘更新时,VFS会调用该函数
int getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat);
//该函数由VFS调用,给dentry指定的文件设置扩展属性,属性名为name,值为value
int setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags);
//该函数由VFS调用,向value中拷贝指定文件的扩展属性name对应的数值
ssize_t getxattr(struct dentry *dentry, const char *name, void *value, size_t size);
//该函数将特定文件的所有属性列表拷贝到一个缓冲列表中
ssize_t listxattr(struct dentry *dentry, char *list, size_t size);
//该函数从给定文件中删除指定的属性
int removexattr(struct dentry *dentry, const char *name);
- 在给定的节点上,可能由VFS执行,也有可能由具体的文件系统执行
目录项对象
- VFS把目录当作文件对待,在路径/bin/vi中,bin和vi都属于文件,路径中的每个组成部分都由一个索引节点对象表示
- 每个dentry代表路径中的一个特定部分,在路径中(包括普通文件在内),每一个部分都是目录项对象
- VFS在执行目录操作时(如果需要的话)会现场创建目录项对象
- 目录项对象没有对应的磁盘数据结构,VFS根据字符串形式的路径名创建,而且由于目录项对象并非真正保存在磁盘上,所以目录项结构体没有是否被修改的标志
#include <linux/dcache.h>
struct dentry {
atomic_t d_count;//使用计数
unsigned int d_flags;//目录项标识
spinlock_t d_lock;//单目录项锁
int d_mounted;//是登陆点的目录项吗?
struct inode *d_inode;//相关联的索引节点
struct hlist_node d_hash;//散列表
struct dentry *d_parent;//父目录的目录项对象
struct qstr d_name;//目录项名称
struct list_head d_lru;//未使用的链表
union {
struct list_head d_child;//目录项内部形成的链表
struct rcu_head d_rcu;//RCU加锁
} d_u;
struct list_head d_subdirs;//子目录链表
struct list_head d_alias;//索引节点别名链表
unsigned long d_time;//重置时间
struct dentry_operations *d_op;//目录项操作指针
struct super_block *d_sb;//文件的超级块
void *d_fsdata;//文件系统特有数据
unsigned char d_iname[DNAME_INLINE_LEN_MIN];//短文件名
}
目录项状态
- 目录项对象有三种有效状态:被使用,未被使用和负状态
- 一个被使用的目录项对应一个有效的索引节点(即d_inode指向相应的索引节点)并且表明该对象存在一个或多个使用者(即d_count为正值),一个目录项处于被使用状态,意味着被VFS使用并且指向有效的数据,因此不能丢弃
- 一个未被使用的目录项对应一个有效的索引节点(d_inode指向一个索引节点),但是应指明VFS当前并未使用(d_count为0),该目录项对象仍然指向一个有效对象,而且保留在缓存中以便需要时再次使用。由于该目录项不会过早被撤销,所以再次使用时无需重新创建,与未缓存的目录项相比,路径查找更迅速。但如果要回收内存,可以撤销未使用的目录项
- 一个负状态的目录项没有对应的有效索引节点(d_inode为NULL),因为索引节点已被删除,或路径不再正确,但是目录项仍然保留,以便快速解析以后的路径查询。比如,一个守护程序不断地试图打开并读取一个不存在的配置文件,open()系统调用不断返回ENOENT,直到内核构建这个路径、遍历磁盘上的目录结构体并检查这个文件的确不存在为止。即便失败的查询浪费资源,但是将负状态缓存是值得的,但是如果需要,可以撤销目录项对象
- 目录项对象被释放后也可以保存到slab对象缓冲中,此时,任何VFS或文件系统代码都没有指向该目录项对象的有效引用
目录项缓存
- 内核将目录项对象缓存在目录项缓存(dcache)中
- 目录项缓存包括三大主要部分
- “被使用的”目录项链表,该链表通过索引节点对象中的i_dentry项连接相关的索引节点,因为一个给定的索引节点可能有多个链接,所以可能有多个目录项对象,因此使用一个链表来连接它们
- “最近被使用的”双向链表,该链表含有未被使用的和负状态的目录项对象,由于该链表总是在头部插入目录项,所以链头节点的数据总比链尾的数据更新,因此尾部的节点最旧,近期被使用的可能性最小,当内核必须通过删除节点项回收内存时,从链尾删除节点项
- 散列表和相应的散列函数用来快速将给定路径解析为相关目录项对象
- 散列表有数组dentry_hashtable表示,其中每一个元素都是一个指向具有相同键值的目录项对象链表的指针,数组的大小取决于系统中物理内存的大小
- 实际的散列值由d_hash()函数计算,是内核提供给文件系统的唯一的一个散列函数
- 查找散列表要通过d_lookup()函数,如果该函数在的cache中发现与其匹配的目录项对象,则匹配的对象被返回,否则,返回NULL指针
目录项操作
- dentry_operation结构体指明了VFS操作目录项的所有方法
#include <linux/dcache.h>
struct dentry_operations {
int (*d_revalidate)(struct dentry*, struct nameidata*);
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*);
char *(*d_name)(struct dentry*, char*, int);
}
//该函数判断目录对象是否有效,VFS准备从dcache中使用一个目录项时,会调用该函数
//大部分文件系统将该方法置NULL,因为认为dcache中的目录项对象总是有效的
int d_revalidate(struct dentry *dentry, struct nameidata *);
//该函数为目录项生成散列值,当目录项需要加入到散列表中时,VFS调用该函数
int d_hash(struct dentry *dentry, struct qstr *name);
//VFS调用该函数比较name1和name2两个文件名,多数文件系统都使用VFS默认的操作,仅仅做字符串比较
//对有些文件系统,如FAT,简单的字符串比较不能满足要求,因为FAT文件系统不区分大小写,所以需要实现不区分大小写的字符串比较函数
//使用该函数需要加dcache_lock锁
int d_compare(struct dentry *dentry);
//当目录项对象的d_count计数值等于0时,VFS调用该函数,使用该函数需要加dcache_lock锁和目录项的d_lock锁
int d_delete(struct dentry *dentry);
//当目录项对象将要释放时,VFS调用该函数,默认情况下,啥也不做
void d_release(struct dentry *dentry);
//当一个目录项对象丢失其相关的索引节点时(也就是说磁盘索引节点被删除),VFS调用该函数
//默认情况下VFS会调用iput()函数释放索引节点
//如果文件系统重载了该函数,那么除了执行此文件系统特殊的工作外,还必须调用iput()函数
void d_iput(struct dentry *dentry, struct inode *inode);
文件对象
- 文件对象表示进程已打开的文件
- 因为多个进程可以同时打开和操作同一个文件,所以同一个文件也可能存在多个对应的文件对象
- 类似目录项对象,文件对象实际上没有对应的磁盘数据,所以结构体中没有代表其对象是否为脏、是否需要回写磁盘的标志
- 文件对象通过f_dentry指针指向相关的目录项对象,目录项指向相关的索引节点,索引节点会记录文件是否是脏的
#include <linux/fs.h>
struct file {
union {
struct list_head fu_list;//文件对象链表
struct rcu_head fu_rcuhead;//释放之后的RCU链表
} f_u;
struct path f_path;//包含目录项
struct file_operations *f_op;//文件操作表
spinlock_t f_lock;//当个文件结构锁
atomic_t f_count;//文件对象的使用计数
unsigned int f_flags;//当打开文件时所指定的标志
mode_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;//版本号
void *f_security;//安全模块
void *private_data;//tty设备驱动的钩子
struct list_head f_ep_links;//事件池链表
spinlock_t f_ep_lock;//事件池锁
struct address_space *f_mapping;//页缓存映射
unsigned long f_mnt_write_state;//调试状态
};
文件操作
#include <linux/fs.h>
struct file_operations {
struct module *owner;
loff_t (*llseek)(struct file*, loff_t, int);
ssize_t (*read)(struct file*, char __user*, size_t, loff_t*)
ssize_t (*write)(struct file*, const char __user*, size_t, loff_t*);
ssize_t (*aio_read)(struct kiocb*, const struct iovec*, unsigned long, loff_t);
ssize_t (*aio_write)(struct kiocb*, const struct iovec*, unsigned long, 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);
long (*unlocked_ioctl)(struct file*, unsigned int, unsigned long);
long (*compat_ioctl)(struct file*, unsigned int, unsigned long);
int (*mmap)(struct file*, struct vm_area_struct*);
int (*open)(struct inode*,struct file*);
int (*flush)(struct file*, fl_owner_t id);
int (*release)(struct inode*, struct file*);
int (*fsync)(struct file*, struct dentry*, int datasync);
int (*aio_sync)(struct kiocb*, int datasync);
int (*fasync)(int ,struct file*, int);
int (*lock)(struct file*, int, struct file_lock*);
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, unsigned long);
int (*check_flags)(int);
int (*flock)(struct file*, int, struct file_lock*);
ssize_t (*splice_write)(struct pipe_inode_info*, struct file*, loff_t*, size_t, unsigned int);
ssize_t (*splice_read)(struct file*, loff_t*, struct pipe_inode_info*, size_t, unsigned int);
int (*setlease)(struct file*, long, struct file_lock**);
}
//该函数用于更新偏移量指针,由系统调用llseek()调用
loff_t llseek(struct file *file, loff_t offset, int origin);
//该函数从给定文件的offset偏移处读取count字节的数据到buf中,同时更新文件指针,由系统调用read()调用
ssize_t read(struct file *file, char *buf, size_t count, loff_t offset);
//该函数从iocb描述的文件里,以同步方式读取count字节的数据到buf中,由系统调用aio_read()调用
ssize_t aio_read(struct kiocb *iocb, char *buf, size_t count, loff_t offset);
//该函数从给定的buf中取出count字节的数据,写入给定文件的offset偏移处,同时更新文件指针,由系统调用write()调用
ssize_t write(struct file *file, const char *buf, size_t count, loff_t *offset);
//该函数以同步方式从给定的buf中取出count字节的数据,写入由iocb描述的文件中,由系统调用aio_write()调用
ssize_t aio_write(struct kiocb *iocb, const char *buf, size_t count, loff_t offset);
//该函数返回目录列表中的下一个目录,由系统调用readdir()调用
int readdir(struct file *file, void *dirent, filldir_t filldir);
//该函数睡眠等待给定文件活动,由系统调用poll()调用
unsigned int poll(struct file *file, struct poll_table_struct *poll_table);
//该函数用来给设备发送命令参数对,只不过是一个被打开的设备节点时,可以通过它进行设置操作
//由系统调用ioctl()调用
int iotcl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg);
//与ioctl()类似功能,只不过不需要调用者持有BKL
//如果用户空间调用ioctl()系统调用,VFS便可以调用unlocked_ioctl()
//因此文件系统只需要实现其中一个,一般优先实现unlocked_ioctl()
int unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
//该函数时ioctl()函数的可移植变种,被32位应用程序用在64位系统上
//这个函数被设计成即使在得到4位的体系结构上对32位也是安全的,可以进行必要的字大小转换
//新的驱动程序应该设计ioctl命令以便所有的驱动程序都是可移植的,从而使得compat_ioctl()和unlocked_ioctl()指向同一个函数
int compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
//该函数将给定的文件映射到指定的地址空间上,由系统调用mmap()调用
int mmap(struct file *file, struct vm_area_struct *vm);
//该函数创建一个新的文件对象,并将它和相应的索引节点对象关联起来,由系统调用open()调用
int open(struct inode *inode, struct file *file);
//当打开文件的引用计数减少时,该函数被VFS调用,作用依据文件系统而定
int flush(struct file *file);
//当文件的最后一个引用被注销时(比如当最后一个共享文件描述符的进程调用了close()或退出时),该函数会被VFS调用,作用根据具体文件系统而定
int release()struct inode *inode, struct file *file);
//将给定文件的所有被缓存数据写回磁盘,由系统调用fsync()调用
int fsync(struct file *file, struct dentry *dentry, int datasync);
//将iocb描述的文件所有被缓存数据写回到磁盘,由系统调用aio_fsync()调用
int aio_fsync(struct kiocb *iocb, int datasync);
//该函数用于打开或关闭异步I/O的通告信号
int fasync(int fd, struct file *file, int no);
//该函数用于给定文件上锁
int lock(struct file *file, int cmd, struct file_lock *lock);
//该函数从给定文件中读取数据,并将其写入由vector描述的count个缓冲中去,同时增加文件的偏移量,由系统调用readv()调用
ssize_t readv(struct file *file, const struct iovec *vector, unsigned long count, loff_t *offset);
//该函数将由vector描述的count个缓冲中的数据写入由file指定的文件中去,同时减少文件的偏移量,由系统调用writev()调用
ssize_t writev(struct file *file, const struct iovec *vector, unsigned long count, loff_t *offset);
//该函数用于从一个文件拷贝数据到另一个文件中,执行的拷贝操作完全在内核中完成,避免向用户空间进行不必要的拷贝,由系统调用sendfile()调用
ssize_t sendfile(struct file *file, loff_t *offset, size_t size, read_actor_t actor, void *target);
//该函数用来从一个文件向另一个文件发送数据
ssize_t sendpage(struct file *file, struct page *page, int offset, size_t size, loff_t *pos, int more);
//该函数用于获取未使用的地址空间来映射给定的文件
unsigned long get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, unsigned long offset, unsigned long flags);
//当给出SETFL命令时,这个函数用来检查传递给fcntl()系统调用的flags的有效性
//与大多数VFS操作一样,文件系统不必实现check_flags(),目前只有NFS文件系统上实现
//这个函数能使文件系统限制无效的SETFL标志,不进行限制,普通的fcntl()函数能使标志生效
//在NFS文件系统中,不允许把O_APPEND和O_DIRECT相结合
int check_flags(int flags);
//该函数用来实现flock()系统调用,该调用提供忠告锁
int flock(struct file *file, int cmd, struct file_lock *fl);
和文件系统相关的数据结构
- file_system_type用来描述各种特定文件系统类型,如ext3、ext4或UDF
- vfsmount用来描述一个安装文件系统的实例
#include <linux/fs.h>
struct file_system_type {
const char *name;//文件系统的名字
int fs_flags;//文件系统类型标志
/*该函数用来从磁盘中读取超级块*/
struct super_block *(*get_sb)(struct file_system_type*, int, char*, void*);
/*该函数用来终止访问超级块*/
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;
}
- get_sb()函数从磁盘读取超级块,并且在文件系统被安装时,在内存中组装超级块对象,剩余的成员描述文件系统的属性
- 每种文件系统,不管多数实例安装到系统中,还是没有安装到系统中,都只有一个file_system_type结构
#inlude <linux/mount.h>
struct vfsmount {
struct list_head mnt_hash;//散列表
struct vfsmount *mnt_parent;//父文件系统
struct dentry *mnt_mountpoint;//安装点的目录项
struct dentry *mnt_root;//该文件系统的根目录项
struct super_block *mnt_sb;//该文件系统的超级块
struct list_head mnt_mounts;//子文件系统链表
struct list_head mnt_child;//子文件系统链表
int mnt_flags;//安装标志
char *mnt_devname;//设备文件名
struct list_head mnt_list;//描述符链表
struct list_head mnt_expire;//到期链表的入口
struct list_head mnt_share;//共享安装链表的入口
struct list_head mnt_slave_list;//从安装链表
struct list_head mnt_slave;//从安装链表的入口
struct vfsmount *mnt_master;//相关的命名空间
int mnt_id;//安装标识符
int mnt_group_id;//组标识符
atomic_t mnt_count;//使用计数
int mnt_expiry_mark;//如果标记到期,则值为真
int mnt_pinned;//”钉住“进程计数
int mnt_ghosts;//“镜像”引用计数
atomic_t __mnt_writers;//写者引用计数
}
- 当文件系统被实际安装时,将有一个vfsmount结构体在安装点被创建,该结构体代表文件系统的实例,也就是说代表一个安装点
- vfsmount结构保存在安装时指定的标志信息,该信息存储在mnt_flags中
标准安装标准列表
| 标准 | 描述 |
|---|---|
| MNT_NOSUID | 禁止该文件系统的可执行文件设置setuid和setgid标志 |
| MNT_MODEV | 禁止访问该文件系统上的设备文件 |
| MNT_NOEXEC | 禁止执行该文件系统上的可执行文件 |
和进程相关的数据结构
- 系统中的每一个进程都有自己的一组打开的文件,像根文件系统、当前工作目录、安装点等
- 有三个数据结构将VFS层和系统的进程紧密联系,分别是:file_struct、fs_struct和namespace结构体
- file_struct该结构体由进程描述符中的files目录项指向,所有与单个进程(per_process)相关信息(如打开的文件及文件描述符)都包含在其中
#include <linux/fdtable.h>
struct files_strcut {
atomic_t count;//结构的使用计数
struct fdtable *fdt;//指向其他fa表的指针
struct fdtable fdtab;//fd表
spinlock_t file_lock;//单个文件的锁
int next_fd;//缓存下一个可用的fd
struct embedded_fd_set close_on_exec_init;//exec()时关闭的文件描述符链表
struct embedded_fd_set open_fds_init;//打开的文件描述符链表
struct file *fd_array[NR_OPEN_DEFAULT];//缺省的文件对象数组
}
- fd_array数组指针指向已打开的文件对象,因为NR_OPEN_DEFAULT等于BITS_PER_LONG,在64位机器体系结构中这个宏的值为64,所以该数组可以容纳64个文件对象
- 如果一个进程所打开的文件对象超过64个,内核将分配一个新数组,并且将fdt指针指向新数组
- fs_struct该结构由进程描述符fs成员指向,包含文件系统和进程相关的信息
#include <linux/fs_struct.h>
struct fs_struct {
int users;//用户数目
rwlock_t lock;//包含该结构体的锁
int umask;//掩码
int in_exec;//当前正在执行的文件
struct path root;//根目录路径
struct path pwd;//当前工作目录路径
}
- namespace,由进程描述符中的mmt_namespace成员指向,2.4版内核以后,单进程命名空间被加入内核,使得每一个进程在系统中都看到唯一的安装文件系统
#include <linux/mmt_namespace.h>
struct mmt_namespace {
atomic_t count;//结构的使用计数
struct vfsmount *root;//根目录的安装点对象
struct list_head list;//安装点链表
wait_queue_head_t poll;//轮询的等待队列
int event;//事件计数
}
- list成员时连接已安装文件系统的双向链表,包含的元素组成了全体命名空间
- 三个主要数据结构都是通过进程描述符连接起来,对多数进程来说,进程描述符都指向唯一的files_struct和fs_struct结构体,但是对于哪些使用克隆标志CLONE_FILES或CLONE_FS创建的进程,会共享这两个结构体,所以多个进程描述符可能指向同一个files_struct和fs_struct结构体,每个结构体都维护一个count成员作为引用计数,防止进程正在使用该结构时,该结构被撤销
- namespace结构体在默认情况下,所有进程共享同样的命名空间(也就是说,都从相同的挂载表中看到同一个文件系统层次结构),只有在进行clone()操作时使用CLONE_NEWS标志,才会给进程一个唯一的命名空间结构体的拷贝,因为大多数进程不提供该标志,所有进程都继承其父进程的命名空间