您的位置:首页 > 理论基础 > 计算机网络

Linux 内核编程之文件系统 VFS中的目录项对象和文件对象 http://www.linuxidc.com/Linux/2011-02/32127p2.htm

2015-09-03 20:29 736 查看

Linux 内核编程之文件系统

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

[日期:2011-02-10]来源:blog.csdn.net/tigerjb 作者:tiger-john[字体:大 中 小]
一 .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链表中。www.linuxidc.com该字段中的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结构体。

我们先看两个图:





此图来自陈莉君老师 博客,版权归属于陈莉君老师。

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指向它。



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结构所形成链表的头指针。

我们来看个图:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: