Linux内核设计与实现 总结笔记(第十三章)虚拟文件系统

一、通用文件系统接口

Linux通过虚拟文件系统,使得用户可以直接使用open()、read()、write()访问文件系统,这种协作性和泛型存取成为可能。

不管文件系统是什么,也不管文件系统位于何种介质,采用策略是统一的。

二、文件系统抽象层

为了支持多文件系统,VFS提供了一个通用文件系统模型, 囊括了任何文件系统的常用功能集和行为。

用户空间的write()将调用VFS的sys_write(),然后调用文件系统中文件系统的写方法,最后写进物理介质中去。

三、Unix文件系统

Unix使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点、安装点。

从本质上讲文件系统是特殊的数据分层存储结构,它包括文件、目录和相关的控制信息。

文件系统的通用操作包含创建、删除和安装等。多有已安装的文件系统都作为根文件系统树的枝叶出现在系统中。

文件其实可以做一个有序字节串,字节串抵押给字节是文件的头,最后一个字节是文件的尾。

文件通过目录组织起来,文件目录好比一个文件夹,用来容纳相关文件。目录可以包含子目录,层层嵌套,形成文件路径。

Unix系统将文件的相关信息和文件本身这两个概念加以区分,例如访问控制权限、大小、拥有者、创建按时间等信息。

而文件相关的信息被存储在单独的结构中,被称作索引节点。

四、VFS对象及其数据结构

VFS有四个主要对象类型,分别是:

  • 超级块对象:代表一个具体的已安装文件系统。
  • 索引节点对象:代表一个具体文件
  • 目录项对象:代表一个目录项,是路径的要给组成部分
  • 文件对象:代表由进程打开的文件

每个对象都有对应的操作对象:

super_operations对象:其中包括内核针对特定文件系统所能调用的方法

inode_operations对象:其中包括内核针对特定文件所能调用的方法

dentry_operations对象:其中包括内核针对特定目录所能调用的方法

file_operations对象:其中包括进程对已打开文件所能调用的方法

如果通用函数提供的基本功能无法满足需求,那么就必须使用实际文件系统的独有方法填充这些函数指针,使其指向文件系统实例。这里的对象是结构体指针,不是类。

五、超级块对象

定义在文件<linux/fs.h>中,创建、管理撤销超级块对象的代码位于文件fs/super.c中。

struct super_block {
struct list_head s_list; /* 指向所有超级块的链表 */
dev_t s_dev; /* 设备标识符 */
unsigned long s_blocksize; /* 以字节为单位的块大小 */
unsigned char s_blocksize_bits; /* 以位为单位的块大小 */
unsigned char s_dirt; /* 修改(脏)标志 */
unsigned long long s_maxbytes; /* 文件大小上限 */
struct file_system_type s_type; /* 文件按系统类型 */
struct super_operations s_op; /* 超级块方法 */
struct quotactl_operations *dp_op; /* 磁盘限额方法 */
struct export_operations *s_export_op; /* 导出方法 */
unsigned long s_flags; /* 挂载文件标志 */
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 hlist_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[]; /* 文本名字 */
void *s_fs_info; /* 文件系统特殊信息 */
fmode_t s_mode; /* 安装权限 */
struct semaphore s_vfs_rename_sem; /* 重命名信号量 */
u32 s_time_gran; /* 时间戳粒度 */
char *s_subtype; /* 子类型名称 */
char *s_options; /* 已存安装选项 */
};

super_block结构

六、超级块操作

超级块最重要的一个域是s_op,指向超级块的操作函数表。super_operations结构体表示,定义文件<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);
};

super_operations

当文件需要对其超级块执行操作时,首先要寻找需要的操作方法:

struct inode *alloc_inode(struct super_block *sb)
在给定超级块下创建和初始化一个新的索引节点 void destroy_inode(struct inode *inode)
VFS在索引节点脏时会调用此函数。日志文件系统执行该函数更新日志 void write_inode(struct inode *inode, int wait)
用于将给定的索引节点写入磁盘,wait参数指明写操作是否需要同步 void drop_inode(struct inode *inode)
在最后一个指向索引节点的引用被释放后,VFS会调用该函数。 void delete_inode(struct inode *inode)
用于从磁盘上删除给定的索引节点 void put_super(struct super_block *sb)
在卸载文件系统时由VFS调用,用来释放超级块。必须一直持有s_lock锁 void write_super(struct super_block *sb)
用给定的超级块更新磁盘上的超级块。 int sync_fs(struct super_block *sb, int wait)
使文件系统的数据元与磁盘上的文件系统同步。 void write_super_lockfs(struct super_block *sb)
首先禁止对文件系统做该表,再使用给定的超级块更新磁盘上的超级块 void unlockfs(struct super_block *sb)
对文件系统解除锁定,write_super_lockfs()逆操作 int statfs(struct super_block *sb, struct statfs *statfs)
VFS通过调用该函数获取文件系统状态。 int remount_fs(struct super_block *sb, int *flags, char *data)
当指定新的安装选项重新安装我呢见系统时,VFS会调用该函数。 void clear_inode(struct inode *inode)
VFS调用该函数释放索引节点,并清空包含相关数据的所有页面 void umount_begin(struct super_block *sb)
VFS调用该函数中断安装操作。

超级块方法

七、索引节点对象

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; /* 文件的块数 */
unsgined 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_space 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_watched; /* 索引节点通知监测链表*/
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私有指针 */
};

struct inode

一个索引节点代表文件系统中的一个文件,它也可以是设备或管道这样的特殊文件。

i_pipe指向有名管道的数据结构,i_bdev指向块设备结构体,i_cdev指向字符设备结构体

八、索引节点操作

索引节点inode_operations描述了VFS操作索引节点对象的所有方法。i->i_op->truncate(i)

inode_operations结构定义在文件<linux/fs.h>中

struct inode_operations {
int (*create)(struct inode *, struct dentry *, int , struct nameidata *);
struct dentry *(*loopup)(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 dentry *);
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 (*setxattr)(struct dentry *, const char *, const void *, size_t, int);
ssize_t (*getxattr)(struct dentry *, const char *, void *, size_t, int);
ssize_t (*listxattr)(struct dentry *, const char *, void *, size_t);
int (*removexattr)(struct dentry *, const char *);
void (*truncate_range)(struct inode *, loff_t, loff_t);
long (*fallocate)(struct inode *inode, int mode, loff_t offset, loff_t len);
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start, u64 len);
};

struct inode_operations

九、目录项对象

VFS把目录当作文件 ,目录项对象由dentry结构体表示,定义在文件<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]; /* 短文件名 */
};

struct dentry

9.1 目录项状态

目录项对象有三种有效状态:被使用、未被使用和负状态。

d_count为正值:一个被使用的目录项对应一个有效的索引节点,并且表明该对象存在一个或多个使用者。

d_count=0:一个未被使用的目录项u敌营一个有效的索引节点,但是应指明当前VFS未使用它。指向一个有效的对象,需要时再使用它。

d_count=NULL:一个负状态的目录项没有对应的有效索引节点,因为系欸但那已被删除了。

9.2 目录项缓存

如果每次都遍历目录项对象,会费时费力。内核把目录项对象缓存在目录项缓存中。(dcache)

目录项缓存包括三个主要部分:

“被使用的”目录项链表。该链表通过索引节点对象中的i_dentry项连接相关的索引节点。因为一个给定的索引节点可能有多个连接

”最近被使用的“双向链表。该链表含有未被使用的和负状态的目录项对象。

散列表和相应的散列函数用来快速地将给定路径解析为相关目录项对象。

十、目录项操作

dentry_operation结构体指明了VFS操作目录项的所有方法。在文件<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);
};

struct dentry_operations

具体用法:

十一、文件对象

VFS的最后一个主要对象是文件对象。文件对象是进程已经打开的文件,文件对象在内存中表示。

文件对象由file结构体表示,定义在<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; /* 调式状态 */
};

struct file

十二、文件操作

文件对象的操作由file_operations结构体i表示,定义在文件<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 *, 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 *, usngined 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 (*fsync)(struct file *, struct dentry *, int datasync);
int (*aio_fsync)(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);
int (*check_flags)(int);
int (*flock)(struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, ssize_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 **);
};

struct file_operations

十三、文件系统相关的数据结构

Linux支持众多不同的文件系统,所以内核必须由一个特殊的结构来描述每种文件系统的功能和行为。file_system_type结构体被定义在<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;
};

struct file_system_type

get_sb()函数从磁盘上读取超级块,并且在文件系统被安装时,在内存中组装超级块对象。

当文件系统被实际安装时,将有一个vfsmount结构体在安装点被创建。vfsmount结构被定义在<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; /* 从安装链表的主人 */
struct mnt_namespace *mnt_namespace; /* 相关的命名空间 */
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; /* 写着引用计数 */
};

struct vfsmount

十四、和进程相关的数据结构

系统中每一个进程都有自己的一组打开的文件,想根文件系统、当前工作目录、安装点等。

分别是file_struct、fs_struct和namespace结构体,file_struct定义在文件<linux/fdtable.h>中。

struct files_struct {
atomic_t count; /* 结构体的使用计数 */
struct fdtable *fdt; /* 指向其他fd表的指针 */
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]; /* 缺省的文件对象数组 */
};

struct files_struct

fd_array数组指针指向已打开的文件对象。和进程相关的第二个结构体是fs_struct。定义在文件<linux/fs_struct.h>中:

struct fs_struct {
int users; /* 用户数目 */
rwlock_t lock; /* 保护该结构体的锁 */
int umask; /* 掩码 */
int in_exec; /* 当前正在执行的文件 */
struct path root; /* 根目录路径 */
struct path pwd; /* 当前工作目录的路径 */
};

struct fs_struct

该结构包含了当前进程的当前工作目录(pwd)和根目录

第三个结构体ishinamespace结构体,在文件<linux/mmt_namespace.h>中。

struct mmt_namespace {
atomic_t count; /* 结构的使用计数 */
struct vfsmount *root; /* 根目录的安装点对象 */
struct list_head list; /* 安装点链表 */
wait_queue_head_t poll; /* 轮询的等待队列 */
int event; /* 事件计数 */
};

struct mmt_namespace

上一篇:初入android驱动开发之字符设备(四-中断)


下一篇:bzoj 3473 后缀自动机多字符串的子串处理方法