$ slabinfo | awk 'NR==1 || $1=="dentry" {print}'Name                   Objects Objsize    Space Slabs/Part/Cpu  O/S O %Fr %Ef Flgdentry                  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;#define f_dentry  f_path.dentry  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;#ifdef CONFIG_SECURITY void *f_security;#endif /* 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; struct list_head f_tfile_llink;#endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping;#ifdef CONFIG_DEBUG_WRITECOUNT unsigned long f_mnt_write_state;#endif};

对于一个文件结构来说,拿来表示一个早已打开的文件,而且应当晓得,当程序打开一个文件时,会领到一个文件描述符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);};

运行态相关的数据结构

内核引导程序_linux内核引导的步骤_linux 内核 引导 文件系统

里面梳理了VFS的四个结构以及相关的操作,并且这四个结构只能说提供了一套插口定义,或则说给了文件系统提供了一个对接标准,是一个静态的概念。

但要用户才能使用和感知到一个文件系统,须要mount到当前目录树中来,以及须要程序去Open文件系统中的文件。这种操作,须要一些额外的数据结构。

内核还使用了一些数据结构来管理文件系统以及相关数据,例如使用file_system_type拿来描述特定的文件系统类型:

struct file_system_type {  const char *name;  int fs_flags;#define FS_REQUIRES_DEV    1 #define FS_BINARY_MOUNTDATA  2#define FS_HAS_SUBTYPE    4#define FS_USERNS_MOUNT    8  /* Can be mounted by userns root */#define FS_USERNS_DEV_MOUNT  16 /* A userns mount does not imply MNT_NODEV */#define FS_RENAME_DOES_D_MOVE  32768  /* FS will handle d_move() during rename() internally. */  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恳求的执行流程。如右图:

linux 内核 引导 文件系统_内核引导程序_linux内核引导的步骤

最后,再通过介绍两个十分典型的文件系统,来讲讲文件系统是怎样配合VFS工作的。

linux 内核 引导 文件系统_linux内核引导的步骤_内核引导程序

传统的文件系统:ext2

ext2曾是一款优秀的文件系统,是Linux上使用最为广泛的文件系统,也是原始Linux文件系统ext的继任者。其实ext2目前早已不会使用,并且由于其设计的简单,很适宜拿来介绍文件系统是怎样工作的。

正如上面提及的,ext2文件系统由以下几部份组成。

引导块:总是作为文件系统的首块。引导块不为文件系统所用,只是包含拿来引导操作系统的信息。操作系统只需一个引导块,但所有文件系统都设有引导块,绝大多数都未使用。

超级块:紧跟引导块然后的一个独立块,包含与文件系统有关的参数信息,其中包括:

•inode表容量;•文件系统中逻辑块的大小;•以逻辑块计,文件系统的大小;

inode表:文件系统中的每位文件或目录在inode表中都对应着惟一一条记录。这条记录登记了关乎文件的各类信息linux ,例如:

•文件类型(例如,常规文件、目录、符号链接,以及字符设备等)•文件属主(又名用户ID或UID)•文件属组(俗称为组ID或GID)•3类用户的访问权限:属主、属组以及其他用户•3个时间戳:最后访问时间、最后更改时间、文件状态的最后改变时间•指向文件的硬链接数目•文件的大小,以字节为单位•实际分配给文件的块数目•指向文件数据块的表针

数据块:文件系统的大部份空间都用于储存数据,以构成留驻于文件系统之上的文件和目录。

linux内核引导的步骤_内核引导程序_linux 内核 引导 文件系统

linux内核引导的步骤_linux 内核 引导 文件系统_内核引导程序

ext2文件系统在储存文件时,数据块不一定连续,甚至不一定按次序储存。为了定位文件数据块,内核在inode内维护有一组表针。

linux 内核 引导 文件系统_linux内核引导的步骤_内核引导程序

一个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中。

内核引导程序_linux 内核 引导 文件系统_linux内核引导的步骤

最后

本文梳理了VFS核心数据结构和之间的关系,并且了解VFS有哪些用的。我觉得是两个方面的作用。

第一是理解Linux文件系统是如何工作的,这对以理解一次IO发生了哪些很有帮助。

第二是有助于理解Linux中的IO缓存linux 内核 引导 文件系统,VFS和好多缓存息息相关,包括页缓存,目录缓存,inode缓存:

linux 内核 引导 文件系统_内核引导程序_linux内核引导的步骤

不仅inode和目录项在显存的结构起到缓存作用外,例如页缓存用于缓存近来读写的文件数据块,其中file.address_space数组便是用于管理页缓存。

参考

•《Linux/UNIX系统编程指南》•《Linux内核设计与实现》•《PortingtheSGIXFSFileSystemtoLinux》

本文原创地址://q13zd.cn/wjxtzmlxywjj.html编辑:刘遄,审核员:暂无