$ slabinfo | awk 'NR==1 || $1=="dentry" {print}'
Name Objects Objsize Space Slabs/Part/Cpu O/S O %Fr %Ef Flg
dentry 608813 192 139.5M 33924/16606/142 21 0 48 83 a
目录项既然是文件系统在显存中的缓存,因而目录项的管理就和普通cache的管理十分接近。例如判定是否有效,对缓存结构的释放等。这种操作便包含在目录项的操作结构中:
struct dentry_operations {
int (*d_revalidate)(struct dentry *, unsigned int);
int (*d_weak_revalidate)(struct dentry *, unsigned int);
int (*d_hash)(const struct dentry *, struct qstr *);
int (*d_compare)(const struct dentry *, const struct dentry *,
unsigned int, const char *, const struct qstr *);
int (*d_delete)(const struct dentry *);
void (*d_release)(struct dentry *);
void (*d_prune)(struct dentry *);
void (*d_iput)(struct dentry *, struct inode *);
char *(*d_dname)(struct dentry *, char *, int);
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(struct dentry *, bool);
} ____cacheline_aligned;
文件
文件结构用于表示进程已打开的文件,是当前文件的显存的数据结构。这个结构在open系统调用时创建,在close系统调用时释放,所有对文件的操作,都是围绕这个结构展开的。
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
/*
* Protects f_ep_links, f_flags, f_pos vs i_size in lseek SEEK_CUR.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
void *f_security;
/* needed for tty driver, and maybe others */
void *private_data;
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
struct address_space *f_mapping;
unsigned long f_mnt_write_state;
};
对于一个文件结构来说,拿来表示一个早已打开的文件,而且应当晓得,当程序打开一个文件时,会领到一个文件描述符linux查看端口占用,文件描述符和文件还是有差别的,这个前面会提及。可以看见,文件结构中有一个名为f_count的引用计数的数组,当引用计数清零时,会调用文件操作结构中的release方式,这个技巧会形成哪些疗效由文件系统的实现决定。
对于文件的操作结构,也就是file_operations,操作函数名和系统调用/库函数名称基本保持一致,不做赘言。
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 (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
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 *, loff_t, loff_t, 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 *, 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 **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
};
运行态相关的数据结构
里面梳理了VFS的四个结构以及相关的操作,并且这四个结构只能说提供了一套插口定义,或则说给了文件系统提供了一个对接标准,是一个静态的概念。
但要用户才能使用和感知到一个文件系统,须要mount到当前目录树中来,以及须要程序去Open文件系统中的文件。这种操作,须要一些额外的数据结构。
内核还使用了一些数据结构来管理文件系统以及相关数据,例如使用file_system_type拿来描述特定的文件系统类型:
struct file_system_type {
const char *name;
int fs_flags;
struct dentry *(*mount) (struct file_system_type *, int,
const char *, void *);
void (*kill_sb) (struct super_block *);
struct module *owner;
struct file_system_type * next;
struct hlist_head fs_supers;
struct lock_class_key s_lock_key;
struct lock_class_key s_umount_key;
struct lock_class_key s_vfs_rename_key;
struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];
struct lock_class_key i_lock_key;
struct lock_class_key i_mutex_key;
struct lock_class_key i_mutex_dir_key;
};
每位安装到系统的文件系统,在完成挂载之前,都只是一个file_system_type对象,上面包含了超级块的挂载、卸载的方式用以实现mount。
而mount操作除了会完成挂载,就会创建一个vfsmount结构,以代表一个挂载点。vfsmount的代码在linux/mount.h中:
struct vfsmount {
struct dentry *mnt_root; /* root of the mounted tree */
struct super_block *mnt_sb; /* pointer to superblock */
int mnt_flags;
};
文件描述符
在系统中,每位进程都有自己的一组「打开的文件」,每位程序的每位文件有不同的文件描述符和读写偏斜量。为此linux 内核 引导 文件系统,还有几个与之相关的数据结构和前面提及的VFS的数据结构紧密相关。
这儿讲下和文件描述符有关的files_struct,其与上面提及的file名子比较蒙蔽,三者最大的不同在于前者是用于维护的系统所有打开的文件,而后者维护的是当前进程打开的所有文件,后者最后会指向前者。
files_struct的结构坐落linux/fdtable.h中:
/*
* Open file table structure
*/
struct files_struct {
/*
* read mostly part
*/
atomic_t count;
struct fdtable __rcu *fdt;
struct fdtable fdtab;
/*
* written part on a separate cache line in SMP
*/
spinlock_t file_lock ____cacheline_aligned_in_smp;
int next_fd;
unsigned long close_on_exec_init[1];
unsigned long open_fds_init[1];
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};
fd_array链表表针便是指向该进程打开的所有文件,进程通过该数组找到对应的file,因而找到对应的inode。
文件系统
通过梳理Linux内核中的数据结构,可以基本摸透从进程打开文件,到VFS处理的过程中数据结构直接的关系,由于这种结构采用了「围绕数据的面向对象的编程方法」,结构本身就带着「方法」,所有也基本可以梳理理解IO恳求的执行流程。如右图:
最后,再通过介绍两个十分典型的文件系统,来讲讲文件系统是怎样配合VFS工作的。
传统的文件系统:ext2
ext2曾是一款优秀的文件系统,是Linux上使用最为广泛的文件系统,也是原始Linux文件系统ext的继任者。其实ext2目前早已不会使用,并且由于其设计的简单,很适宜拿来介绍文件系统是怎样工作的。
正如上面提及的,ext2文件系统由以下几部份组成。
引导块:总是作为文件系统的首块。引导块不为文件系统所用,只是包含拿来引导操作系统的信息。操作系统只需一个引导块,但所有文件系统都设有引导块,绝大多数都未使用。
超级块:紧跟引导块然后的一个独立块,包含与文件系统有关的参数信息,其中包括:
•inode表容量;•文件系统中逻辑块的大小;•以逻辑块计,文件系统的大小;
inode表:文件系统中的每位文件或目录在inode表中都对应着惟一一条记录。这条记录登记了关乎文件的各类信息linux ,例如:
•文件类型(例如,常规文件、目录、符号链接,以及字符设备等)•文件属主(又名用户ID或UID)•文件属组(俗称为组ID或GID)•3类用户的访问权限:属主、属组以及其他用户•3个时间戳:最后访问时间、最后更改时间、文件状态的最后改变时间•指向文件的硬链接数目•文件的大小,以字节为单位•实际分配给文件的块数目•指向文件数据块的表针
数据块:文件系统的大部份空间都用于储存数据,以构成留驻于文件系统之上的文件和目录。
ext2文件系统在储存文件时,数据块不一定连续,甚至不一定按次序储存。为了定位文件数据块,内核在inode内维护有一组表针。
一个inode结构包含了15个表针(0-14),其中前11个表针拿来指向数据块,这样在小文件场景下可以直接引用,旁边的表针指向间接表针块,以指向后续的数据块。
因而也可以看出,对于大小为4096字节的块而言,理论上,单文件最大概等于1024×1024×1024×4096字节,或4TB(4096GB)。
日志文件系统:XFS
第二个反例可以看下常见的现代文件系统xfs,xfs是SiliconGraphics为她们的IRIX操作系统而开发,在大文件处理和传输性能上有不错的表现。
这篇文章重点并不是为了讲xfs的工作原理,而是从Linux视角看待xfs。xfs不像ext系专为Linux订制的,所以在文件系统处理上并没有依照VFS设计。
在VFS中,文件的操作分为了两层:file(文件读写等),inode(文件创建删掉等),而在xfs中只有一层vnode来提供所有操作。所以在移植XFS到Linux过程中,为了适配VFS引入了一个转换中间层linvfs,将对file和inode的操作映射到vnode中。
最后
本文梳理了VFS核心数据结构和之间的关系,并且了解VFS有哪些用的。我觉得是两个方面的作用。
第一是理解Linux文件系统是如何工作的,这对以理解一次IO发生了哪些很有帮助。
第二是有助于理解Linux中的IO缓存linux 内核 引导 文件系统,VFS和好多缓存息息相关,包括页缓存,目录缓存,inode缓存:
不仅inode和目录项在显存的结构起到缓存作用外,例如页缓存用于缓存近来读写的文件数据块,其中file.address_space数组便是用于管理页缓存。
参考
•《Linux/UNIX系统编程指南》•《Linux内核设计与实现》•《PortingtheSGIXFSFileSystemtoLinux》
本文原创地址://q13zd.cn/wjxtzmlxywjj.html编辑:刘遄,审核员:暂无