Linux 内核编程之文件系统(二)

VFS中的目录项对象和文件对象

.VFS 中的目录项对象

1.为了方便查找,VFS引入了 目录 项,每个dentry代表路径中的一个特定部分。目录项也可包括安装点。
2.目录项对象由dentry结构体表示 ,定义在文件linux/dcache.h 头文件中。
89struct dentry {
90 atomic_t d_count; //使用计数
91 unsigned int d_flags; //目录项标时
92 spinlock_t d_lock; //单目录锁
93 int d_mounted; //目录项的安装点
94 struct inode *d_inode; //与该目录项相关联的索引节点
95
96 /*
97 * The next three fields are touched by __d_lookup. Place them here
98 * so they all fit in a cache line.
99 */
100 struct hlist_node d_hash; //散列表
101 struct dentry *d_parent; //父目录项
102 struct qstr d_name; //目录项名可快速查找
103
104 struct list_head d_lru; // 未使用目录以LRU 算法链接的链表
105 /*
106 * d_child and d_rcu can share memory
107 */
108 union {
109 struct list_head d_child; /* child of parent list */
110 struct rcu_head d_rcu;
111 } d_u;
112 struct list_head d_subdirs; //该目录项子目录项所形成的链表
113 struct list_head d_alias; //索引节点别名链表
114 unsigned long d_time; //重新生效时间
115 const struct dentry_operations *d_op; // 操作目录项的函数
116 struct super_block *d_sb; //目录项树的根
117 void *d_fsdata; //具体文件系统的数据
118
119 unsigned char d_iname[DNAME_INLINE_LEN_MIN]; //短文件名
120};
1>索引节点中的i_dentry指向了它目录项,目录项中的d_alias,d_inode又指会了索引节点对象,目录项中的d_sb又指回了超级块对象。
2>我们可以看到不同于VFS 中的索引节点对象和超级块对象,目录项对象中没有对应磁盘的数据结构,所以说明目录项对象并没有真正标存在磁盘上,那么它也就没有脏标志位。
3>目录项的状态(被使用,未被使用和负状态)
a.它们是靠d_count的值来进行区分的,当d_count为正值说明目录项处于被使用状态。当d_count=0时表示该目录项是一个未被使用的目录项, 但其d_inode指针仍然指向相关的的索引节点。该目录项仍然包含有效的信息,只是当前没有人引用他。d_count=NULL表示负(negative)状态,与目录项相关的inode对象不复存在(相应的磁盘索引节点可能已经被删除),dentry对象的d_inode 指针为NULL。但这种dentry对象仍然保存在dcache中,以便后续对同一文件名的查找能够快速完成。这种dentry对象在回收内存时将首先被释放。
4> d_subdirs:如果当前目录项是一个目录,那么该目录下所有的子目录形成一个链表。该字段是这个链表的表头;
d_child:如果当前目录项是一个目录,那么该目录项通过这个字段加入到父目录的d_subdirs链表当中。这个字段中的next和prev指针分别 指向父目录中的另外两个子目录;
d_alias:一个inode可能对应多个目录项,所有的目录项形成一个链表。inode结构中的i_dentry即为这个链表的头结点。当前目录项以这个字段处于i_dentry链表中。该字段中的prev和next指针分别指向与该目录项同inode的其他两个(如果有的话)目录项
3.dentry和inode的区别:
 inode(可理解为ext2 inode)对应于物理磁盘上的具体对象,dentry是一个内存实体,其中的d_inode成员指向对应的inode。也就是说,一个inode可以在运行的时候链接多个dentry,而d_count记录了这个链接的数量。
4.dentry与dentry_cache
dentry_cache简称dcache,中文名称是目录项高速缓存,是Linux为了提高目录项对象的处理效率而设计的。它主要由两个数据结构组成:
1>哈希链表dentry_hashtable:dcache中的所有dentry对象都通过d_hash指针域链到相应的dentry哈希链表中。
2>未使用的dentry对象链表dentry_unused:dcache中所有处于unused状态和negative状态的dentry对象都通过其d_lru指针域链入dentry_unused链表中。该链表也称为LRU链表。
目录项高速缓存dcache是索引节点缓存icache的主控器(master),也即 dcache中的dentry对象控制着icache中的inode对象的生命期转换。无论何时,只要一个目录项对象存在于dcache中(非 negative状态),则相应的inode就将总是存在,因为 inode的引用计数i_count总是大于0。当dcache中的一个dentry被释放时,针对相应inode对象的iput()方法就会被调用。
5对目录项进行操作的一组函数叫目录项操作表,由dentry_operation结构描述。它可以在 include/linux/dcache.h 中查到
134struct dentry_operations {
135 int (*d_revalidate)(struct dentry *, struct nameidata *);
136 int (*d_hash) (struct dentry *, struct qstr *);
137 int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
138 int (*d_delete)(struct dentry *);
139 void (*d_release)(struct dentry *);
140 void (*d_iput)(struct dentry *, struct inode *);
141 char *(*d_dname)(struct dentry *, char *, int);
142};
a.int d_reavlidate(struct dentry *dentry ,int flags) 该函数判断目录对象是否有效。VFS准备从dcache中使用一个目录项时,会调用该函数.
b.int d_hash(struct dentry *dentry ,struct qstr *name):该目录生成散列值,当目录项要加入到散列表时,VFS要调用此函数。
c.int d_compare( struct dentry *dentry, struct qstr *name1, struct qstr *name2) 该函数来比较name1和name2这两个文件名。使用该函数要加dcache_lock锁。
d.int d_delete(struct dentry *dentry):当d_count=0时,VFS调用次函数。使用该函数要叫 dcache_lock锁。
e.void d_release(struct dentry *dentry):当该目录对象将要被释放时,VFS调用该函数。
f.void d_iput(struct dentry *dentry,struct inode *inode)当一个目录项丢失了其索引节点时,VFS就掉用该函数。
二.VFS中的文件对象
1.文件对象表示进程已经打开的文件 在内存中的表示,该对象不是物理上的文件。它是由相应的open()系统调用创建,由close()系统调用销毁。多个进程可以打开和操作同一个文件,所以同一个文件也可能存在多个对应的文件对象。
2一个文件对应的文件对象不是唯一的,但对应的索引节点和超级块对象是唯一的。
3.file结构中保存了文件位置,此外,还把指向该文件索引节点的指针也放在其中。file结构形成一个双链表,称为系统打开文件表 。它的定义在 include/linux/fs.h 中可以看到
909struct file {
910 /*
911 * fu_list becomes invalid after file_free is called and queued via
912 * fu_rcuhead for RCU freeing
913 */
914 union {
915 struct list_head fu_list; //每个文件系统中被打开的文件都会形成一个双链表
916 struct rcu_head fu_rcuhead;
917 } f_u;
918 struct path f_path;
919#define f_dentry f_path.dentry // 与该文件对应的dentry
920#define f_vfsmnt f_path.mnt
921 const struct file_operations *f_op; //指向文件操作表的指针
922 spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */
923#ifdef CONFIG_SMP
924 int f_sb_list_cpu;
925#endif
926 atomic_long_t f_count; //文件对象的使用计数
927 unsigned int f_flags; //打开文件时所指定的标志
928 fmode_t f_mode; //文件的访问模式
929 loff_t f_pos; //文件当前的位移量
930 struct fown_struct f_owner
931 const struct cred *f_cred;
932 struct file_ra_state f_ra; //预读状态
933
934 u64 f_version; //版本号
935#ifdef CONFIG_SECURITY
936 void *f_security; //安全模块
937#endif
938 /* needed for tty driver, and maybe others */
939 void *private_data; //tty设备hook
940
941#ifdef CONFIG_EPOLL
942 /* Used by fs/eventpoll.c to link all the hooks to this file */
943 struct list_head f_ep_links;
944#endif /* #ifdef CONFIG_EPOLL */
945 struct address_space *f_mapping; //页缓存映射
946#ifdef CONFIG_DEBUG_WRITECOUNT
947 unsigned long f_mnt_write_state;
948#endif
949};
1>文件对象实际上没有对应的磁盘数据,所以在结构体中没有代表其对象是否为脏,是否需要写回磁盘的标志。文件对象 通过f_path.dentry指针指向相关的目录项对象。目录项会指向相关的索引节点,索引节点会记录文件是否是脏的。
2>fu_list:每个文件系统中以被打开的文件都会形成一个双联表,这个双联表的头结点存放在超级块的s_files字段中。该字段的prev和next指针分别指向在链表中与当前文件结构体相邻的前后两个元素.

Tiger-John说明:

file结构中主要保存了文件位置,此外还把指向该文件索引节点的指针也放在其中。---》有人就问了,问什么不直接把文件位置存放在索引节点中呢?

因为:Linux中的文件是能够共享的,假如把文件位置存放在索引节点中,当有两个或更多个进程同时打开一个文件时,它们将去访问同一个索引节点,那么一个进程的lseek操作将影响到另一个进程的读操作,这显然是致命的错误。


4>对文件进行操作的一组函数叫文件操作表,由file_operations结构定义:可以在include/linux/fs.h 中查看
1488struct file_operations {
1489 struct module *owner;
1490 loff_t (*llseek) (struct file *, loff_t, int);
1491 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
1492 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
1493 ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1494 ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1495 int (*readdir) (struct file *, void *, filldir_t);
1496 unsigned int (*poll) (struct file *, struct poll_table_struct *);
1497 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
1498 long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
1499 int (*mmap) (struct file *, struct vm_area_struct *);
1500 int (*open) (struct inode *, struct file *);
1501 int (*flush) (struct file *, fl_owner_t id);
1502 int (*release) (struct inode *, struct file *);
1503 int (*fsync) (struct file *, int datasync);
1504 int (*aio_fsync) (struct kiocb *, int datasync);
1505 int (*fasync) (int, struct file *, int);
1506 int (*lock) (struct file *, int, struct file_lock *);
1507 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
1508 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
1509 int (*check_flags)(int);
1510 int (*flock) (struct file *, int, struct file_lock *);
1511 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
1512 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
1513 int (*setlease)(struct file *, long, struct file_lock **);
1514};
owner:用于指定拥有这个文件操作结构体的模块,通常取THIS_MODULE;
llseek:用于设置文件的偏移量。第一个参数指明要操作的文件,第二个参数为偏移量,第三个参数为开始偏移的位置(可取SEEK_SET,SEEK_CUR和SEEK_END之一)。
read:从文件中读数据。第一个参数为源文件,第二个参数为目的字符串,第三个参数指明欲读数据的总字节数,第四个参数指明从源文件的某个偏移量处开始读数据。由系统调用read()调用;
write:往文件里写数据。第一个参数为目的文件,第二个参数源字符串,第三个参数指明欲写数据的总字节数,第四个参数指明从目的文件的某个偏移量出开始写数据。由系统调用write()调用;
mmap:将指定文件映射到指定的地址空间上。由系统调用mmap()调用;
open:打开指定文件,并且将这个文件和指定的索引结点关联起来。由系统调用open()调用;
release:释放以打开的文件,当打开文件的引用计数(f_count)为0时,该函数被调用;
fsync():文件在缓冲的数据写回磁盘
3.用户打开文件表
系统中的每一个进程都有自己的一组打开的文件 ,像根文件系统,当前目工作目录,安装点等。有四个数据结构将VFS层和系统的进程紧密联系在一起,它们分别是:files_struct,fs_struct,
file_system_type 和namespace结构体。

我们先看两个图:

Linux 内核编程之文件系统(二)

Linux 内核编程之文件系统(二)

此图来自陈莉君老师 博客,版权归属于陈莉君老师。
1.>文 件描述符是用来描述打开的文件的。每个进程用一个files_struct结构来记录文件描述符的使用情况,这个结构称为用户打开文件表。它是进程的私有数据。
a.files_struct 结构体定义在文件 include/linux/fdtable.h该结构体由进程描述符中的files 域指向。所有与每个进程(per-process)相关的信息如打开的文件及文件描述符都包含在其中。
44struct files_struct {
45 /*
46 * read mostly part
47 */
48 atomic_t count; //共享该表的进程数
49 struct fdtable *fdt;
50 struct fdtable fdtab; //定义了文件的一些属性
51 /*
52 * written part on a separate cache line in SMP
53 */
54 spinlock_t file_lock ____cacheline_aligned_in_smp;
55 int next_fd; //下一个文件描述符
56 struct embedded_fd_set close_on_exec_init; //*exec()关闭的文件描述符
57 struct embedded_fd_set open_fds_init; //文件描述符的初始集合
58 struct file * fd_array[NR_OPEN_DEFAULT]; //默认的文件对象数组
59};
a.看看 struct fdtable结构:
32struct fdtable {
33 unsigned int max_fds; //文件对象的上限
34 struct file ** fd; //全部文件对象数组
35 fd_set *close_on_exec; //*exec()关闭的文件描述符
36 fd_set *open_fds; //指向打开文件的描述符
37 struct rcu_head rcu;
38 struct fdtable *next; //指向该链表的下一个对象
39};
40
b.fd数组指针指向已打开的文件对象链表,默认情况下,指向fd_arry数组。因为NR_OPEN_DEFAULT等于32,所以该数组可以容纳32个文件对象。如果一个进程所打开的文件对象超过32个。内核将分配一个新数组,并且将fd指向它。

Linux 内核编程之文件系统(二)
c.对于在fd数组中有入口地址的每个文件来说,数组的索引就是文件描述符。通常,数组的第一个元素(索引为0)表示进程的标准输入文件,数组的第二个元素(索引为1)是进程的标准输出文件,数组的第三个元素(索引为2)是进程的标准错误文件)
2.>fs_struct 结构
a.fs_struct 结构描述进程与文件系统的关系
b.我们来深入分析其代码,它的定义在 include/linux/fs_struct.h,
6struct fs_struct {
7 int users;
8 spinlock_t lock; //保护该结构体的锁
9 int umask; //默认的文件访问权限
10 int in_exec;
11 struct path root, pwd;
12};
看一下struct path 结构的定义 :
7struct path {
8 struct vfsmount *mnt;
9 struct dentry *dentry;
10}
可以看到struct path 封装了vfsmount 和dentry;,所以struct path root,pwd包含了当前进程的当前工作目录和根目录以及根目录安装点对象和pwd安装点对象。

3>file_system_type结构体

在Linux中,用file_system_type来描述各种特定文件系统类型,比如ext3。也就是说Linux支持的所有文件系统类型都分别唯一的对应一个file_system_type结构

a.它的定义在 include/linux/fs.h中。

1736struct file_system_type {
1737 const char *name; //文件系统的类型名
1738 int fs_flags; //文件系统类型标志
1739 int (*get_sb) (struct file_system_type *, int, //文件系统读入其超级块的函数指针
1740 const char *, void *, struct vfsmount *);
1741 void (*kill_sb) (struct super_block *); //该函数用来终止访问超级块
1742 struct module *owner; //通常设置为宏THIS_MODLUE,用以确定是否把文件系统作为模块安装
1743 struct file_system_type * next;
1744 struct list_head fs_supers;
1745
1746 struct lock_class_key s_lock_key;
1747 struct lock_class_key s_umount_key;
1748 struct lock_class_key s_vfs_rename_key;
1749
1750 struct lock_class_key i_lock_key;
1751 struct lock_class_key i_mutex_key;
1752 struct lock_class_key i_mutex_dir_key;
1753 struct lock_class_key i_alloc_sem_key;
1754};

tiger-john说明:

1>name:文件系统的名字,不能为空;
2>get_sb:在安装文件系统时,调用此指针所指函数以在磁盘中获取超级块;
3>kill_sb:卸载文件文件系统时候,调用此指针所指函数以进行一些清理工作;
4>owner:如果一个文件系统以模块的形式加载到内核,则该字段用来说明哪个模块拥有这个结构。一般为THIS_MODULE;
5>next:所有的文件系统类型结构形成一个链表,该链表的头指针为全局变量file_systems(struct file_system_type *file_systems)。这个字段指向链表中下一个文件系统类型结构;
6>fs_supers:同一个文件系统类型下的所有超级块形成一个双联表,这个字段是这个双联表的头结点。超级块之间通过s_instances字段相互链接.

4>vfsmount结构体

a.当文件系统被实际安装时,将有一个vfsmount 结构体在安装点被创建。该结构体用来代表文件系统的实例即代表一个安装点。

b.vfsmount结构体被定义在 include/linux/mount.h中

36/*
37 * MNT_SHARED_MASK is the set of flags that should be cleared when a
38 * mount becomes shared. Currently, this is only the flag that says a


39 * mount cannot be bind mounted, since this is how we create a mount
40 * that shares events with another mount. If you add a new MNT_*
41 * flag, consider how it interacts with shared mounts.
42 */
43#define MNT_SHARED_MASK (MNT_UNBINDABLE)
44#define MNT_PROPAGATION_MASK (MNT_SHARED | MNT_UNBINDABLE)
45
46
47#define MNT_INTERNAL 0x4000
48
49struct vfsmount {
50 struct list_head mnt_hash; //散列表
51 struct vfsmount *mnt_parent; //指向上一层安转点的指针
52 struct dentry *mnt_mountpoint; //安装点的目录项
53 struct dentry *mnt_root; //安装树的根
54 struct super_block *mnt_sb; //指向超级块的指针
55 struct list_head mnt_mounts; //子链表
56 struct list_head mnt_child; / /通过mnt_child进行遍历

57 int mnt_flags; //安装标志
58 /* 4 bytes hole on 64bits arches without fsnotify */
59#ifdef CONFIG_FSNOTIFY
60 __u32 mnt_fsnotify_mask;
61 struct hlist_head mnt_fsnotify_marks;
62#endif
63 const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
64 struct list_head mnt_list;
65 struct list_head mnt_expire ; /* link in fs-specific expiry list */
66 struct list_head mnt_share; /* circular list of shared mounts */
67 struct list_head mnt_slave_list;/* list of slave mounts */
68 struct list_head mnt_slave; /* slave list entry */
69 struct vfsmount *mnt_master; /* slave is on master->mnt_slave_list */
70 struct mnt_namespace *mnt_ns; /* containing namespace */
71 int mnt_id; /* mount identifier */
72 int mnt_group_id; /* peer group identifier */
73 /*
74 * We put mnt_count & mnt_expiry_mark at the end of struct vfsmount
75 * to let these frequently modified fields in a separate cache line
76 * (so that reads of mnt_flags wont ping-pong on SMP machines)
77 */
78 atomic_t mnt_count; //使用计数
79 int mnt_expiry_mark; /* true if marked for expiry */
80 int mnt_pinned;
81 int mnt_ghosts;
82#ifdef CONFIG_SMP
83 int __percpu *mnt_writers;
84#else
85 int mnt_writers;
86#endif
87};

a.vfsmount 结构还保存了在安装时指定的标志信息,该信息存储在mmt_flags中。

MNT_NOSUID:禁止该文件系统的可执行文件设置setuid和setgid标志

MNT_NODEV:禁止访问该文件系统上的设备文件

MNT_NOEXEC:禁止执行该文件系统上的可执行文件

Tiger-John说明:

在管理员安装一些不是很安全的移动设备时,这些标志很有用。

b.为了对系统中的所有安装点进行快速查找,内核把它们按哈希表来组织,mnt_hash就是形成哈希表的队列指针。
c.mnt_mountpoint是指向安装点dentry结构的指针。而dentry指针指向安装点所在目录树中根目录的dentry结构。
d.mnt_parent是指向上一层安装点的指针。如果当前的安装点没有上一层安装点(如根设备),则这个指针为NULL。同时,vfsmount结构中还有mnt_mounts和mnt_child两个队列头,只要上一层vfsmount结构存在,就把当前vfsmount结构中mnt_child链入上一层vfsmount结构的mnt_mounts队列中。这样就形成一颗设备安装的树结构,从一个vfsmount结构的mnt_mounts队列开始,可以找到所有直接或间接安装在这个安装点上的其他设备。如图8.2。
e.mnt_sb指向所安装设备的超级块结构super_block。
f.mnt_list是指向vfsmount结构所形成链表的头指针。

我们来看个图:

Linux 内核编程之文件系统(二)

你可能感兴趣的:(linux)