virtual-filesystem-switch

vfs-arch

版本

引用代码版本:linux-3.10.104

功能

  • 提供一个通用接口
  • 提供各种cache以提高文件系统性能

vfs

VFS所处理的系统调用

系统调用名 说明
mount umount umount2 安装/卸载文件系统
sysfs 获取文件系统信息
ustat statfs fstatfs statfs64 fstatfs64 获取文件系统统计信息
chroot pivot_root 更改根目录
chdir fchdir getcwd 对当前目录进行操作
mkdir rmdir 创建、删除目录
getdents getdents64 readdir link unlink rename lookup_dcookie 对目录项进行操作
readlink symlink 对软连接进行操作
chown fchown lchown chown16 fchown16 lchown16 更改文件所有者
chmod fchmod utime 更改文件属性
stat fstat lstat access oldstat oldfstat oldlstat 读取文件状态
stat64 lstat64 fstat64
open close creat umask 打开、关闭、创建文件
dup dup2 fcntl fcntl64 对文件描述符进行操作
select poll 等待一组文件描述符上发生的事件
truncate ftruncate truncate64 ftruncate64 更改文件长度
lseek 更改文件指针
read write readv writev sendfile sendfile64 readahead 进行文件I/O操作
io_setup io_submit io_getevents io_cancel io_destroy 异步I/O
pread64 pwrite64 搜索并访问文件
mmap mmap2 munmap madvise mincore remap_file_pages 处理文件内存映射
fdatasync fsync sync msync 同步文件数据
flock 处理文件锁
setxattr lsetxattr fsetxattr getxattr lgetxattr 处理文件扩展属性
fgetxattr listxattr llistxattr flistxattr removexattr
lremovexattr fremovexattr

VFS数据结构

  • 超级块对象和inode对象分别对应有物理数据,在磁盘上有静态信息。
  • 目录项对象和文件对象描述的是一种关系,前者描述的文件与文件名的关系,后者描述的是进程与文件的关系,所以没有对应物理数据
  • 进程每打开一个文件,就会有一个file结构与之对应。同一个进程可以多次打开同一个文件而得到多个不同的file结构,file结构描述被打开文件的属性,如文件的当前偏移量等信息
  • 两个不同的file结构可以对应同一个dentry结构。进程多次打开同一个文件时,对应的只有一个dentry结构
  • 在存储介质中,每个文件对应唯一的inode结点,但是每个文件又可以有多个文件名。
  • Inode中不存储文件的名字,它只存储节点号;而dentry则保存有名字和与其对应的节点号,所以就可以通过不同的dentry访问同一个inode

vfs_3.10.104.png

Super Block

  • 超级块用来描述特定文件系统的信息。它存放在磁盘特定的扇区中 ,它在使用的时候将信息存在于内存中
  • 当内核对一个文件系统进行初始化和注册时在内存为其分配一个超级块,这就是VFS超级块。即,VFS超级块是各种具体文件系统在安装时建立的,并在这些文件系统卸载时被自动删除

super_block_1.jpg

关键成员:

  • s_list
    所有的超级块形成一个双联表,s_list.prev和s_list.next分别指向与当前超级块相邻的前一个元素和后一个元素
  • s_fs_info
    字段指向具体文件系统的超级块。
    例如:超级块对象指的是Ext2文件系统,该字段就指向ext2_sb_info数据结构
  • alloc_super()
    超级块对象是通过函数alloc_super()创建并初始化的(由sget()调用,在fs/super.c文件中)。在文件系统安装时,内核会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存中的超级块对象中

include/linux/fs.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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_magic;
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 list_head s_inodes; /* all inodes */
struct hlist_bl_head s_anon; /* 用于处理远程网络文件系统的匿名目录项的链表 */
struct list_head s_mounts; /* list of mounts; _not_ for fs use */
/* s_dentry_lru, s_nr_dentry_unused protected by dcache.c lru locks */
struct list_head s_dentry_lru; /* unused dentry lru */
int s_nr_dentry_unused; /* # of dentry on lru */
/* s_inode_lru_lock protects s_inode_lru and s_nr_inodes_unused */
spinlock_t s_inode_lru_lock ____cacheline_aligned_in_smp;
struct list_head s_inode_lru; /* unused inode lru */
int s_nr_inodes_unused; /* # of inodes on lru */
struct block_device *s_bdev; /* 指向块设备驱动程序描述符的指针 */
struct backing_dev_info *s_bdi;
struct mtd_info *s_mtd;
struct hlist_node s_instances; /* 用于给定文件系统类型的超级块对象链表的指针 */
struct quota_info s_dquot; /* 磁盘限额的描述符 */
struct sb_writers s_writers;
char s_id[32]; /* Informational name */
u8 s_uuid[16]; /* UUID */
void *s_fs_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;
};

super_operation

include/linux/fs.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
/* 创建和初始化一个索引节点对象 */
void (*destroy_inode)(struct inode *);
/* 释放给定的索引节点 */
void (*dirty_inode) (struct inode *, int flags);
/* VFS在索引节点被修改时会调用这个函数 */
int (*write_inode) (struct inode *, struct writeback_control *wbc);
/* 将指定的inode写回磁盘 */
int (*drop_inode) (struct inode *);
/* 最后一个指向索引节点的引用被删除后,VFS会调用这个函数 */
void (*evict_inode) (struct inode *);
void (*put_super) (struct super_block *);
/* 卸载文件系统时由VFS调用,用来释放超级块 */
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 *);
/* VFS调用该函数获取文件系统状态 */
int (*remount_fs) (struct super_block *, int *, char *);
/* 定新的安装选项重新安装文件系统时,VFS会调用该函数 */
void (*umount_begin) (struct super_block *);
/* VFS调用该函数中断安装操作 */

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);
/* 限额系统使用该方法将数据写入文件中,该文件详细说明了所在文件系统的限制 */
#endif
int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
int (*nr_cached_objects)(struct super_block *);
void (*free_cached_objects)(struct super_block *, int);
};

Inode

inode_1.jpg

索引节点,索引节点与文件一一对应,并且随文件存在而存在。内存中索引节点由一个inode结构表示。

include/linux/fs.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
struct inode {
umode_t i_mode; /* 文件类型与访问权限 */
unsigned short i_opflags;
kuid_t i_uid; /* inode所属文件的拥有者的id,通过ls -n可查看拥有者id */
kgid_t i_gid; /* inode所属文件所在组的id,通过ls -n可查看组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; /* address space */

#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; /* 硬链接数目,当该inode描述一个目录时,这个值至少为2,代表.和..的数目 */
unsigned int __i_nlink;
};
dev_t i_rdev; /* 如果该inode描述的是一个设备文件,此值为设备号 */
loff_t i_size; /* 文件的字节数 */
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; /* 文件使用块的个数,通过ls -s可以查看该某个文件的块使用数目 */

#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount; /* SMP系统为i_size字段获取一致值时使用的顺序计数器 */
#endif

/* Misc */
unsigned long i_state; /* 索引节点的状态标志 */
struct mutex i_mutex;

unsigned long dirtied_when; /* jiffies of first dirtying */

struct hlist_node i_hash; /* 用于散列链表的指针 */
/* 为了提高查找inode的效率,每一个inode都会有一个hash值。该字段指向hash值相同的inode所形成的双链表该字段包含prev和next两个指针,分别指向上述链表的前一个元素和后一个元素 */
struct list_head i_wb_list; /* backing dev IO list */
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; /* 用于写进程的引用计数器 */
const struct file_operations *i_fop; /* 缺省文件操作 former ->i_op->default_file_ops */
struct file_lock *i_flock; /* 指向文件锁链表的指针 */
struct address_space i_data; /* address space */
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS]; /* 索引节点磁盘限额 */
#endif
struct list_head i_devices; /* 用于具体的字符或块设备索引节点链表的指针 */
union {
struct pipe_inode_info *i_pipe; /* 如果文件是一个管道则使用它 */
struct block_device *i_bdev; /* 指向块设备驱动程序的指针 */
struct cdev *i_cdev; /* 指向字符设备驱动程序的指针 */
};

__u32 i_generation; /* 索引节点版本号(由某些文件系统使用) */

#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_marks;
#endif

#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
void *i_private; /* fs or device private pointer */
};

i_state 成员

  • I_DIRTY_SYNC
    已被改变但不需要同步
  • I_DIRTY_DATASYNC
    数据部分已被改变
  • I_DIRTY_PAGES
    有脏的数据页
  • I_NEW
    索引节点对象已经分配,但还没有用从磁盘索引节点读取来的数据填充
  • I_WILL_FREE
    即将被释放
  • I_FREEING
    索引节点对象正在被释放
  • I_CLEAR
    索引节点对象的内容不再有意义
  • I_SYNC
    正在同步过程中
  • I_REFERENCED
    在LRU list中标记inode为最近被引用过
  • I_DIO_WAKEUP
    Never Set
  • I_DIRTY
    I_DIRTY_SYNC | I_DIRTY_DATASYNC | I_DIRTY_PAGES

inode_operations

include/linux/fs.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
struct inode_operations {
struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
void * (*follow_link) (struct dentry *, struct nameidata *);
int (*permission) (struct inode *, int);
struct posix_acl * (*get_acl)(struct inode *, int);

int (*readlink) (struct dentry *, char __user *,int);
void (*put_link) (struct dentry *, struct nameidata *, void *);

int (*create) (struct inode *,struct dentry *, umode_t, bool);
/* 如果该inode描述一个目录文件,那么当在该目录下创建或打开一个文件时,内核必须为这个文件创建一个inode。VFS通过调用该inode的i_op->create()函数来完成上述新inode的创建。
该函数的第一个参数为该目录的 inode,第二个参数为要打开新文件的dentry,第三个参数是对该文件的访问权限。如果该inode描述的是一个普通文件,那么该inode永远都不会调用这个create函数 */

int (*link) (struct dentry *,struct inode *,struct dentry *);
/* 用于在指定目录下创建一个硬链接。这个link函数最终会被系统调用link()调用。
该函数的第一个参数是原始文件的dentry,第二个参数即为上述指定目录的inode,第三个参数是链接文件的dentry。 */

int (*unlink) (struct inode *,struct dentry *);
/* 在某个目录下删除dentry指定的索引节点对象。这个unlink函数最终会被系统调用unlink()调用。 第一个参数即为上述硬链接所在目录的inode,第二个参数为要删除文件的dentry */

int (*symlink) (struct inode *,struct dentry *,const char *);
/* 在某个目录下新建软连接(符号链接),第一个参数是原始文件所在目录的inode,第二个参数是原始文件的dentry,第三个参数是符号链接的名字(const char *) */

int (*mkdir) (struct inode *,struct dentry *,umode_t);
/* 在指定的目录下创建一个子目录,当前目录的inode会调用i_op->mkdir()。该函数会被系统调用mkdir()调用。
第一个参数即为指定目录的inode,第二个参数为子目录的dentry,第三个参数为子目录权限;
(目录与子目录是通过目录inode中的dentry链相连的,而子目录的dentry又指向子目录自身的inode) */

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 (*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 *);
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);
} ____cacheline_aligned;

File

  • 文件对象描述进程怎样与一个打开文件进行交互。
  • 几个进程可以同时访问一个文件,因此文件指针必须放在文件对象而不是索引节点对象中

include/linux/fs.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path; /* 文件对象通过f_path.dentry指针指向相关的目录项对象 */
/* 目录项会指向相关的索引节点,索引节点会记录文件是否是脏的 */
#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
};

file_operations

include/linux/fs.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
struct file_operations {
struct module *owner; /* 用于指定拥有这个文件操作结构体的模块,通常取THIS_MODULE */
loff_t (*llseek) (struct file *, loff_t, int);
/* 用于设置文件的偏移量。第一个参数指明要操作的文件,第二个参数为偏移量,第三个参数为开始偏移的位置(可取SEEK_SET,SEEK_CUR和SEEK_END之一)*/

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
/* 从文件中读数据。第一个参数为源文件,第二个参数为目的字符串,第三个参数指明欲读数据的总字节数,第四个参数指明从源文件的某个偏移量处开始读数据。由系统调用read()调用 */

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
/* 往文件里写数据。第一个参数为目的文件,第二个参数源字符串,第三个参数指明欲写数据的总字节数,第四个参数指明从目的文件的某个偏移量出开始写数据。由系统调用write()调用 */

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);
/* 启动一个异步I/O操作,引入它是为了支持io_submit()系统调用 */

int (*readdir) (struct file *, void *, filldir_t);
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 *);
/* 将指定文件映射到指定的地址空间上。由系统调用mmap()调用 */

int (*open) (struct inode *, struct file *);
/* 打开指定文件,并且将这个文件和指定的索引结点关联起来。由系统调用open()调用 */

int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
/* 释放以打开的文件,当打开文件的引用计数(f_count)为0时,该函数被调用 */

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);
};

Dentry

VFS把每一个目录看作由若干个目录和文件组成的一个普通文件。

本来inode中应该包括“目录节点”的名称,但由于硬链接的存在,导致一个物理文件可能有多个文件名,因此把和“目录节点”名称相关的部分从 inode 中分开,放在一个专门的 dentry 结构(目录项)中
描述一个文件和一个名字的对应关系,或者说dentry就是一个“文件名”

dentry_1.jpg

  • 在内存中, 每个文件都至少有一个dentry(目录项)和inode(索引节点)结构
  • dentry记录着文件名,上级目录等信息,正是它形成了我们所看到的树状结构
  • 有关该文件的组织和管理的信息主要存放inode里面,它记录着文件在存储介质上的位置与分布

dentry_2.png

include/linux/fs.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct dentry {
/* RCU lookup touched fields */
unsigned int d_flags; /* protected by d_lock */
seqcount_t d_seq; /* per dentry seqlock */
struct hlist_bl_node d_hash; /* lookup hash list */
struct dentry *d_parent; /* parent directory */
struct qstr d_name;
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative */
unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */

/* Ref lookup also touches following */
unsigned int d_count; /* protected by d_lock */

spinlock_t d_lock; /* per dentry lock */
const struct dentry_operations *d_op;
struct super_block *d_sb; /* The root of the dentry tree */
unsigned long d_time; /* used by d_revalidate */
void *d_fsdata; /* fs-specific data */

struct list_head d_lru; /* LRU list */
struct list_head d_child; /* child of parent list */
/* 如果当前目录项是一个目录,那么该目录项通过这个字段加入到父目录的d_subdirs链表当中。这个字段中的next和prev指针分别指向父目录中的另外两个子目录 */
struct list_head d_subdirs; /* our children */
/* 如果当前目录项是一个目录,那么该目录下所有的子目录(一级子目录)形成一个链表。该字段是这个链表的表头 */
/*
* d_alias and d_rcu can share memory
*/
union {
struct hlist_node d_alias; /* inode alias list */
/* 索引节点中的i_dentry指向了它目录项,目录项中的d_alias,d_inode又指会了索引节点对象
一个inode可能对应多个目录项,所有的目录项形成一个链表。inode结构中的i_dentry即为这个链表的头结点。当前目录项以这个字段处于i_dentry链表中。该字段中的prev和next指针分别指向与该目录项同inode的其他两个(如果有的话)目录项 */
struct rcu_head d_rcu;
} d_u;
};

dentry的四种状态

  • 空闲状态(Free)
    该状态的目录对象不包括有效的信息,没有被vfs使用。
  • 未使用状态(unused)
    该状态的目录对象还没有被内核使用。引用计数器d_count为0,d_inode字段仍然指向关联的索引节点,目录对象包含有效的信息。为了在必要时回收内存,它的内容可能被丢弃。
  • 正在使用状态(in use)
    该状态的目录对象正在被内核使用。引用计数器d_count大于0,d_inode字段指向关联的索引节点,目录对象包含有效的信息,不能被丢弃。
  • 负状态(negative)
    与目录对象关联的索引节点不存在,相应的磁盘索引节点已被删除。d_inode被置为NULL,该对象仍被保存在目录项高速缓存中,以便同一文件目录名的查找能够快速完成。

dentry_oprations

include/linux/dcache.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct dentry_operations {
int (*d_revalidate)(struct dentry *, unsigned int);
/* 该函数判断目录对象是否有效。VFS准备从dcache中使用一个目录项时,会调用该函数 */

int (*d_weak_revalidate)(struct dentry *, unsigned int);
int (*d_hash)(const struct dentry *, const struct inode *, struct qstr *);
/* 该目录生成散列值,当目录项要加入到散列表时,VFS要调用此函数 */

int (*d_compare)(const struct dentry *, const struct inode *, const struct dentry *, const struct inode *, unsigned int, const char *, const struct qstr *);
int (*d_delete)(const struct dentry *);
/* 当d_count=0时,VFS调用次函数。使用该函数要加dcache_lock锁 */

void (*d_release)(struct dentry *);
/* 当该目录对象将要被释放时,VFS调用该函数 */

void (*d_prune)(struct dentry *);

void (*d_iput)(struct dentry *, struct inode *);
/* 当一个目录项丢失了其索引节点时,VFS就掉用该函数 */

char *(*d_dname)(struct dentry *, char *, int);
struct vfsmount *(*d_automount)(struct path *);
int (*d_manage)(struct dentry *, bool);
} ____cacheline_aligned;

Dentry定位文件

首先,通过dir对应的dentry0找到inode0节点,有了inode节点就可以读取目录中的信息。其中包含了该目录包含的下一级目录与文件文件列表,包括name与inode号。

1
2
$  ls -i
975248 subdir0 975247 subdir1 975251 file0

然后,根据通过根据subdir0对应的inode号重建inode2,并通过文件数据(目录也是文件)与inode2重建subdir0的dentry节点:dentry1。

1
2
$  ls -i
975311 file1 975312 file2

接着,根据file1对应的inode号重建inode4,并通过文件数据与inode4重建file1的dentry节点。最后,就可以通过inode4节点访问文件了。

dentry_3.png

Dentry Cache

由于从硬盘读入一个目录项并构造相应的目录项对象需要花费大量的时间,也为了最大限度提高目处理这些录项对象的效率,VFS采用了dentry cache的设计。当有用户用ls命令查看某一个目录或用open命令打开一个文件时,VFS会为这里用的每个目录项与文件建立dentry项与inode,即“按需创建”。然后维护一个LRU(Least Recently Used)列表,当Linux认为VFS占用太多资源时,VFS会释放掉长时间没有被使用的dentry项与inode项。

这里的建立于释放是从内存占用的角度看。从Linux角度看,dentry与inode是VFS中固有的东西。所不同的只是VFS是否把dentry与inode读到了内存中。对于Ext2/3文件系统,构建dentry与inode的过程非常简单,但对于其他文件系统,则会慢得多。

Process & File

file_1.png

task_struct (include/linux/sched.h)

1
2
3
4
5
6
7
8
9
10
11
12
struct task_struct {
...
/* CPU-specific state of this task */
struct thread_struct thread;
/* filesystem information */
struct fs_struct *fs;
/* open file information */
struct files_struct *files;
/* namespaces */
struct nsproxy *nsproxy;
...
};

进程控制块task_struct(include/linux/sched.h)中有两个变量与文件有关:fs(struct fs_struct)与files(struct files_struct)。

fs_struct (include/linux/fs_struct.h)

1
2
3
4
5
6
7
8
struct fs_struct {
int users;
spinlock_t lock;
seqcount_t seq;
int umask;
int in_exec;
struct path root, pwd;
};

path (include/liinux/path.h)

1
2
3
4
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
};

fs中存储着rootpwd两个指向dentry项的指针。用户定路径时,绝对路径会通过root进行定位;相对路径会通过pwd进行定位。进程的root不一定是文件系统的根目录。如ftp进程的根目录不是文件系统的根目录,这样才能保证用户只能访问ftp目录下的内容。
files是一个file object列表,其中每一个节点对应着一个被打开了的文件。

files_struct (include/linux/fdtable.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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];
};

当进程定位到文件时,会构造一个file object,并通过f_inode关联到inode节点。文件关闭时(close),进程会释放对应对应file object。

fdtable (include/linux/fdtable.h)

1
2
3
4
5
6
7
struct fdtable {
unsigned int max_fds;
struct file __rcu **fd; /* current fd array */
unsigned long *close_on_exec;
unsigned long *open_fds;
struct rcu_head rcu;
};

file_2.jpg

fd数组第一个元素[0]是进程的标准输入文件;第二个元素[1]是进程的标准输出文件;第三个元素[2]是进程的标准错误文件。

文件系统类型

特殊文件系统

文件系统 安装点 说明
bdev 块设备
binfmt_misc 任意 其他可执行格式
devpts /dev/pts 伪终端支持
eventpollfs 由事件轮询机制使用
futexfs 由futex(快速用户空间加锁)机制使用
pipefs 管道
proc /proc 对内核数据结构的常规访问点
rootfs 为启动阶段提供一个空的根目录
shm IPC共享线性区
mqueue 任意 实现POSIX消息队列时使用
sockfs 套接字
sysfs /sys 对系统数据的常规访问点
tmpfs 任意 临时文件(若不被交换出去,则常驻内存中)
usbfs /proc/bus/usb USB设备

内核给每个安装的特殊文件系统分配一个虚拟的块设备,让其主设备号为0,次设备号有任意值(每个特殊的文件系统有不同的值)

文件系统类型注册

文件系统的源码要么包含在内核映像中,要么作为一个模块被动态装入。每个注册的文件系统都用一个类型为file_system_type的对象来表示。
file_system_type (include/linux/fs.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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_systems指向链表的第一个元素,file_systems->next指向链表的下一个元素。

int register_filesystem(struct file_system_type * fs)[fs/filesystems.c]
注册一个新的文件系统,传入要注册文件系统的结构体,在file_systems列表中查找受否与传入fs->name同名的文件系统,如果存在则返回找到的fs,若不存在将传入文件系统fs加入file_systems列表

对比一下ramfs_fs_typeroot_fs_type

1
2
3
4
5
6
7
8
9
10
11
static struct file_system_type ramfs_fs_type = {
.name = "ramfs",
.mount = ramfs_mount,
.kill_sb = ramfs_kill_sb,
.fs_flags = FS_USERNS_MOUNT,
};
static struct file_system_type rootfs_fs_type = {
.name = "rootfs",
.mount = rootfs_mount,
.kill_sb = kill_litter_super,
};

发现fs_flags = FS_USERNS_MOUNT,这个flag似乎想告诉我们,ramfs是挂载到用户命名空间的,言外之意rootfs不是挂载到用户空间的,那便是内核空间喽。

文件系统处理

  • 每个文件系统都有自己的根目录
  • 已安装文件系统的根目录隐藏了父文件系统的安装点目录原来的内容

命名空间

通常进程共享一个命名空间,位于系统的根文件系统且被init进程使用的已安装文件系统树。如果clone()系统调用以CLONE_NEWNS标志创建一个新进程,那么新进程将获取一个新的命名空间,新进程再创建的新新进程将继承新命名空间。

  • fork
    fork创造的子进程是父进程的完整副本,复制了父亲进程的资源,包括内存的内容task_struct内容
  • vfork
    vfork创建的子进程与父进程共享数据段,而且由vfork()创建的子进程将先于父进程运行
  • clone
    有选择性的继承父进程的资源,可以选择像vfork一样和父进程共享一个虚存空间,可以不和父进程共享,甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系。灵活性更强。

KeyPoint

启动流程

  1. start_kernel[init/main.c]
  2. vfs_caches_init(totalram_pages)[fs/dcache.c]
  3. dcache_init()[fs/dcache.c]
    初始化dentry_cachedentry_hashtable
  4. inode_init()[fs/inode.c]
    初始化inode_cachepinode_hashtable
  5. files_init()[fs/file_table.c]
    初始化file_cachepfiles_stat.max_files
  6. mnt_init()[fs/namespace.c]
    初始化mnt_cache,mount_hashtable,mountpoint_hashtable
    1. sysfs_init()[fs/sysfs/mount.c]
      初始化sysfs_dir_cachep
      sysfs_inode_init()[s/sysfs/inode.c] -> bdi_init(&sysfs_backing_dev_info)
      register_filesystem(&sysfs_fs_type)
      sysfs_mnt = kern_mount(&sysfs_fs_type)
    2. init_root()[fs/ramfs/inode.c]
      bdi_init(&ramfs_backing_dev_info)
      register_filesystem(&rootfs_fs_type)
    3. init_mount_tree()[fs/namespace.c]
      挂载rootfs到/ mnt = vfs_kern_mount(type, 0, "rootfs", NULL)
      创建namespace ns = create_mnt_ns(mnt)
      设置init_task(PID=0)命名空间 init_task.nsproxy->mnt_ns = ns
      设置当前进程的pwdroot set_fs_pwd(current->fs, &root); set_fs_root(current->fs, &root);
  7. bdev_cache_init()[fs/block_dev.c]
    初始化bdev_cachep
    1. mount bdev
  8. chrdev_init()[fs/char_dev.c]
    初始化cdev_map

Mount文件系统

mount_1.png

mount时,linux先找到磁盘分区的super block,然后通过解析磁盘的inode table与file data,构建出自己的dentry列表与inode列表。需要注意的是,VFS实际上是按照Ext的方式进行构建的,所以两者非常相似(毕竟Ext是Linux的原生文件系统)。比如inode节点,Ext与VFS中都把文件管理结构称为inode,但实际上它们是不一样的。Ext的inode节点在磁盘上;VFS的inode节点在内存里。Ext-inode中的一些成员变量其实是没有用的,如引用计数等。保留它们的目的是为了与vfs-node保持一致。这样在用ext-inode节点构造vfs-inode节点时,就不需要一个一个赋值,只需一次内存拷贝即可。

参考&鸣谢