Linux内核编程之文件系统
Linux文件编程之虚拟文件系统(VFS)
当用户层程序员在编写文件函数时,常常会用到open(),read()和write()这类系统调用,而且 用的也很爽,因为我们只需要知道这些函数如何调用就OK了,而不用深究具体文件系统和实际物理介质是如何实现的。而我们内核编程人员就要了解这些底层实 现,给上层人员提供更多的方便。因此我们的任务就更复杂,佛家有一句名言:“我不入地狱,谁如地狱”因此我们就要有奉献精神编写出具有统一简单效率高的接 口为用户层程序员提供方便。
如果你有这种奉献精神和喜爱Linux内核以及有着很高的好奇心的话,那我们就来深入学习内核看看Linux内核到底是如何实现这些功能的。
一.VFS的概念
Linux 之所以能支持除了Ext2文件系统之外的各种文件系统,是因为Linux提供了一种统一的框架,就是所谓的虚拟文件系统转换(Virtual FilesystemSwitch),简称虚拟文件系统(VFS)。这样, 用户程序可以通过同一个文件系统界面,也就是同一组系统调用,能够对各种不同的文件系统以及文件进行操作。
1.在上面提到Linux中支持几十种文件系统,它是怎么管理的呢,它都管理什么呢?
首先,VFS 只对挂载到文件系统种的文件系统进行管理,即它时按需管理的。
其次,因为它继承了Unix的设计思想,所以它也是对文件,目录项,索引节点和超级块进行管理。
2.VFS中四个主要对象:
1>超级块对象:描述已安装文件系统。
每个文件系统 都对应一个超级对象。文件系统的控制信息存储在超级块中。
2>索引节点对象:描述一个文件。
每个文件 都有一个索引节点对象。每个索引节点对象都有一索引节点号---->正是用这个号来唯一的标识某个文件系统中的指定文件。
3>目录项对象:描述一个目录项,是路径的组成部分。
VFS把每个目录看作一个由若干子目录和文件组成的常规文件。
例如:我们在查找路径名:/tmp/test时,内核为根目录“/”创建第一个目录项对象,为根目录下tmp项创建第二级目录项对象,为/tmp目录下的test项创建第三级目录项对象。
4>文件对象:描述由进程打开的文件。
Tiger-John说明:
1.因为 VFS 将目录作为一个文件来处理,所以不存在目录对象。虽然目录项不同于目录,但目录却和文件相同。
2.Linux中将文件的相关信息和文件本身区分开了。
在Linux 中文件的相关信息,被存储在一个单独的数据结构中,该结构被称为索引节点。文件(目录)信息按照索引节点形式存储在单独的块中;控制信息被集中存储在磁盘的超级块中
说了这么多,VFS到底是如何实现的呢?----现在我们就来深入内核代码来看看吧
二.VFS四个主要对象的实现
VFS采用的是面向对象的设计思想,使用一簇数据结构来代表通用文件对象。所以内核中的数据结构都使用C结构体实现。
1.superblock(超级块)对象:
1>超级块用来描述特定文件系统的信息。它存放在磁盘特定的扇区中 ,它在使用的时候将信息存在于内存中。
2> 当内核对一个文件系统进行初始化和注册时在内存为其分配一个超级块,这就是VFS超级块。
即,VFS超级块是各种具体文件系统在安装时建立的,并在这些文件系统卸载时被自动删除 。
3>超级块对象由结构体 super_block来体现。
VFS超级块的数据结构为 super_block在include/linux/fs.h中可以查看
1318struct super_block {
1319 struct list_head s_list; // 超级快链表指针
1320 dev_t s_dev; // 设备表示符
1321 unsigned char s_dirt; //脏标志
1322 unsigned char s_blocksize_bits; //以位为单位的块的大小
1323 unsigned long s_blocksize; //以字节为单位的块大小
1324 loff_t s_maxbytes; //文件大小上限
1325 struct file_system_type *s_type; //指向文件系统的file_system_type 数据结构的指针
1326 const struct super_operations *s_op; //超级块方法
1327 const struct dquot_operations *dq_op; //磁盘限额方法
1328 const struct quotactl_ops *s_qcop; //限额控制方法
1329 const struct export_operations *s_export_op; //导出方法
1330 unsigned long s_flags; //登录标志
1331 unsigned long s_magic; //文件系统的魔数
1332 struct dentry *s_root; //目录登录点
1333 struct rw_semaphore s_umount; //卸载信号量
1334 struct mutex s_lock; //超级块信号量
1335 int s_count; //超级块引用计数
1336 atomic_t s_active; //活动引用记数
1337#ifdef CONFIG_SECURITY
1338 void *s_security; //安全模块
1339#endif
1340 const struct xattr_handler **s_xattr;
1341
1342 struct list_head s_inodes; //把所有索引对象链接在一起,存放的是头结点
1343 struct hlist_head s_anon; //匿名目录项
1344#ifdef CONFIG_SMP
1345 struct list_head __percpu *s_files;
1346#else
1347 struct list_head s_files; //链接所有打开的文件。
1348#endif
1349 /* s_dentry_lru and s_nr_dentry_unused are protected by dcache_lock */
1350 struct list_head s_dentry_lru; /* unused dentry lru */
1351 int s_nr_dentry_unused; /* # of dentry on lru */
1352
1353 struct block_device *s_bdev; //相关的块设备
1354 struct backing_dev_info *s_bdi;
1355 struct mtd_info *s_mtd;
1356 struct list_head s_instances; //该类型文件系统
1357 struct quota_info s_dquot; //限额相关选项
1358
1359 int s_frozen;
1360 wait_queue_head_t s_wait_unfrozen;
1361
1362 char s_id[32]; //文本名字
1363
1364 void *s_fs_info; //文件系统特设信息
1365 fmode_t s_mode;
1366
1367 /* Granularity of c/m/atime in ns.
1368 Cannot be worse than a second */
1369 u32 s_time_gran;
1370
1371 /*
1372 * The next field is for VFS *only*. No filesystems have any business
1373 * even looking at it. You had been warned.
1374 */
1375 struct mutex s_vfs_rename_mutex; /* Kludge */
1376
1377 /*
1378 * Filesystem subtype. If non-empty the filesystem type field
1379 * in /proc/mounts will be "type.subtype"
1380 */
1381 char *s_subtype;
1382
1383 /*
1384 * Saved mount options for lazy filesystems using
1385 * generic_show_options()
1386 */
1387 char *s_options;
1388};
现在我们来对其中主要的数据结构进行分析
比较重要的数据结构以在上面有注释。
我们先来看一个图,再来具体解释:
1.s_list
:所有的超级块形成一个双联表,s_list.prev和s_list.next分别指向与当前超级块相邻的前一个元素和后一个元素。通常我们通过
list_entry宏来获取s_list所在超级块结构体的地址(超级块对像是以双向链表的形式链接在一起的)。
2. s_lock :保护链表免受多处理器系统上的同时访问。
3.s_fs_info: 字段指向具体文件系统的超级块。
例如:超级块对象指的是Ext2文件系统,该字段就指向ext2_sb_info数据结构。
4.s_dirt :来表示该超级块是否是脏的,也就是说,磁盘上的数据是否必须要更新。
5.超级块对象是通过函数alloc_super()创建并初始化的。在文件系统安装时,内核会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存中的超级块对象中 。
6. 超级对象中最重要的就是s_op,每一种文件系统都应该有自己的super_operations操作实例。它指向超级块的操作函数表, 它由struct super_operations结构体来表示。
现在来看一下它的定义:它的定义在 include/linux/fs.h头文件中可以看到
1560struct super_operations {
1561 struct inode *(*alloc_inode)(struct super_block *sb);
1562 void (*destroy_inode)(struct inode *);
1563
1564 void (*dirty_inode) (struct inode *);
1565 int (*write_inode) (struct inode *, struct writeback_control *wbc);
1566 int (*drop_inode) (struct inode *);
1567 void (*evict_inode) (struct inode *);
1568 void (*put_super) (struct super_block *);
1569 void (*write_super) (struct super_block *);
1570 int (*sync_fs)(struct super_block *sb, int wait);
1571 int (*freeze_fs) (struct super_block *);
1572 int (*unfreeze_fs) (struct super_block *);
1573 int (*statfs) (struct dentry *, struct kstatfs *);
1574 int (*remount_fs) (struct super_block *, int *, char *);
1575 void (*umount_begin) (struct super_block *);
1576
1577 int (*show_options)(struct seq_file *, struct vfsmount *);
1578 int (*show_stats)(struct seq_file *, struct vfsmount *);
1579#ifdef CONFIG_QUOTA
1580 ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
1581 ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
1582#endif
1583 int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
1584};
1>可以看到该结构体中的每一项都是一个指向超级块操作函数的指针,超级块操作函数执行文件系统和索引节点的低层操作。
2>当文件系统需要对超级块执行操作时,要在超级块对象中寻找需要的操作方法
例如:一个文件系统要写自己的超级块,需要调用:
sturct super_block * sb;
sb->s_op->write_super)(sb);
sb是指向文件系统超级块的指针,沿着该指针进入超级块操作函数表,并从表中取得writ_super()函数,该函数执行写入超级块的实际操作。
Tiger-John说明:
尽管writ_super()方法来自超级块,但是在调用时,还是要把超级块作为参数传递给它。
3>.分析其中比较重要的一些数据结构。
a.struct inode * alloc_inode(struct super_block * sb) :创建和初始化一个新的索引结点。
b.void destroy_inode(struct super_block *sb) :释放指定的索引结点 。
c.void dirty_inode(struct inode *inode) :VFS在索引节点被修改时会调用此函数。
d.void write_inode(struct inode *inode, struct writeback_control *wbc) 将指定的inode写回磁盘。
e.void drop_inode( struct inode * inode):删除索引节点。
f.void put_super(struct super_block *sb) :用来释放超级块。
g.void write_super(struct super_block *sb):更新磁盘上的超级块。
h.void sync_fs(struct super_block *sb,in wait):使文件系统的数据元素与磁盘上的文件系统同步,wait参数指定操作是否同步。
i.int statfs(struct super_block *sb,struct statfs *statfs):获取文件系统状态。把文件系统相关的统计信息放在statfs中。
2.VFS的索引节点
1>文件系统处理文件或目录时的所有信息都存放在称为索引节点的数据结构中。
文件名可以随时该,但是索引节点对文件是唯一的(它是随文件的存在而存在)。
2>具体文件系统的索引节点是存放在磁盘上的,是一种静态结构,要使用它,必须将其调入内存,填写 VFS的索引节点。VFS索引节点也称为动态节点。(即索引节点仅当文件被方位时才在内存中创建)
3>我们来深入下来看一下它的内核
它的定义在 /include/linux/fs.h中有这个结构体的定义
725struct inode {
726 struct hlist_node i_hash; //散列表
727 struct list_head i_list; //索引节点链表
728 struct list_head i_sb_list; //链接一个文件系统中所有inode的链表
729 struct list_head i_dentry; //目录项链表
730 unsigned long i_ino; //索引节点号
731 atomic_t i_count; //引用计数
732 unsigned int i_nlink; //硬连接数
733 uid_t i_uid; //使用者的id
734 gid_t i_gid; //使用组id
735 dev_t i_rdev; //实际设备标识符号
736 unsigned int i_blkbits;
737 u64 i_version; //版本号
738 loff_t i_size; //以字节为单位
739#ifdef __NEED_I_SIZE_ORDERED
740 seqcount_t i_size_seqcount;
741#endif
742 struct timespec i_atime; //最后访问时间
743 struct timespec i_mtime; //最后修改时间
744 struct timespec i_ctime; //最后改变时间
745 blkcnt_t i_blocks; //文件的块数
746 unsigned short i_bytes; //使用的字节数
747 umode_t i_mode; //访问权限控制
748 spinlock_t i_lock; //自旋锁
749 struct mutex i_mutex;
750 struct rw_semaphore i_alloc_sem;
751 const struct inode_operations *i_op; //索引节点操作表
752 const struct file_operations *i_fop; //默认的索引节点链表
753 struct super_block *i_sb; //相关的超级块
754 struct file_lock *i_flock; //文件锁链表
755 struct address_space *i_mapping; //相关的地址映射
756 struct address_space i_data; //设备地址映射
757#ifdef CONFIG_QUOTA
758 struct dquot *i_dquot[MAXQUOTAS]; //节点的磁盘限额
759#endif
760 struct list_head i_devices; //块设备链表
761 union {
762 struct pipe_inode_info *i_pipe; //管道信息
763 struct block_device *i_bdev; //块设备驱动
764 struct cdev *i_cdev;
765 };
766
767 __u32 i_generation; //索引节点版本号
768
769#ifdef CONFIG_FSNOTIFY
770 __u32 i_fsnotify_mask; /* all events this inode cares about */
771 struct hlist_head i_fsnotify_marks;
772#endif
773
774 unsigned long i_state; //状态标志
775 unsigned long dirtied_when; //首次修改时间
776
777 unsigned int i_flags; //文件系统标志
778
779 atomic_t i_writecount; //写者计数
780#ifdef CONFIG_SECURITY
781 void *i_security; //安全模块
782#endif
783#ifdef CONFIG_FS_POSIX_ACL
784 struct posix_acl *i_acl;
785 struct posix_acl *i_default_acl;
786#endif
787 void *i_private; /* fs or device private pointer */
788};
在我们看完这段代码后,是不是发现在上面被紫色标记的一些信息我们都很熟悉,还记的我们在终端下输入命令:ls 命令后可以看到文件的信息,这些信息就是记录在这里的。
这是为什么呢?
在原理课中我们知道,文件是由FCB(文件控制块控制的),而具体到Linux下,文件是有索引节点结构控制的。所以在struct inode 里存放了文件的基本信息。
大家有没有发现在怎么在索引节点里面会包含超级块的对象呢(上面被红色标记的),有些人可能不明白了,先看看下面的图,再来解释把。
从上面对的图我们可以看出索引节点 对象靠i_sb指回到了超级块对象。
i_hash :为了提高查找inode的效率,每一个inode都会有一个hash值。该字段指向hash值相同的inode所形成的双链表该字段包含prev和next两个指针,分别指向上述链表的前一个元素和后一个元素;
i_list :所有索引结点形成的双联表,(从图上可以看出,索引节点对象是靠它来链接的)
i_dentry :所有引用该inode的目录项将形成一个双联表,该字段即为这个双联表的头结点
i_ino :索引结点号。通过ls -i命令可以查看文件的索引节点号;
i_count :引用计数;
i_nlink :硬链接数。当该inode描述一个目录时,这个值至少为2,代表.和..的数目;
i_uid :inode所属文件的拥有者的id,通过ls -n可查看拥有者id;
i_gid :inode所属文件所在组的id,通过ls -n可查看组id;
i_rdev :如果该inode描述的是一个设备文件,此值为设备号;
i_blkbits :以位为单位的块大小;
i_atime :文件最近一次被访问的时间。通过ls -lu可查看该时间;
i_mtime :文件最近一次被修改的时间,这里的修改只文件内容被修改。通过ls -l可查看该时间;
i_ctime :文件最近一次被修改的时间,这里的修改除了指文件内容被修改外,更强调的是文件的属性被修改。通过ls -lc可查看该时间;
i_blocks :文件使用块的个数,通过ls -s可以查看该某个文件的块使用数目;
i_mode :文件的访问权限;
i_op : 指向索引结点操作结构体的指针;
i_fop : 指向文件操作街头体的指针;
i_sb : 指向inode所属文件系统的超级块的指针;
i_pipe :如果inode所代表的文件是一个管道,则使用该字段;
i_bdev :如果inode所代表的文件是一个块设备,则使用该字段;
i_cdev :如果inode所代表的文件是一个字符设备,则使用该字段;
i_state : 索引节点的状态信息。
Tiger-John说明:
1.在同一个文件系统中,每个索引节点号都是唯一的,内核可以根据索引节点号的散列值来查找其inode结构。
2.inode中有两个设备号i_dev和i_rdev。
a.特设文件外,每个节点都存储在某个设备上,这就是i_dev。
b. 如果索引节点所代表的并不是常规文件,而是某个设备,则需要另一个设备号,这就是i_rdev。
3.对i_state的说明:
每个VFS索引节点都会复制磁盘索引节点包含的一些数据,比如文件占有的磁盘数。如果i_state 的值等于I_DIR,该索引节点就是“脏“的。也就是说,对应的磁盘索引节点必须被更新。
4.三个重要的双向链表:
a.未用索引节点链表,正在使用索引节点链表和脏索引节点链表。每个索引节点对象总是出现在上面三种的一个。
b.这3个链表都是通过索引节点的i_list 域链接在一起的。
c.属于“正在使用“或“脏“链表的索引节点对象也同时存放在一个散列表中。
<散列表加快了对索引节点对象的搜索>.
5.一个索引节点代表文件系统中的一个文件,它也可 以是设备或管道这样的特殊文件。所以在索引节点结构体中有一些和特殊文件相关的项。
6.有时候某些文件系统并不能完整地包含索引节点结构体要求的所有信息。那么此时刚怎么办呢?
此时,可以给它赋一些其它的值。
例如:一个文件系统可能并不记录文件的访问时间,这时就可以在i_atime中存储0。
7.i_list和i_sb_list的区别
a:i_list:VFS中使用四个链表来管理不同状态的inode结点。inode_unused将当前未使用的inode链接起 来,inode_in_use将当前正在被使用的inode链接起来,超级块中的s_dirty将所有脏inode链接起来,i_hash将所有hash 值相同的inode链接起来。i_list中包含prev和next两个指针,分别指向与当前inode处于同一个状态链表的前后两个元素
b.i_sb_list:每个文件系统中的inode都会形成一个双联表,这个双链表的头结点存放在超级块的s_inodes中。而该字段中的prev和next指针分别指向在双链表中与其相邻的前后两个元素
c..索引结点中i_sb_list链表是链接一个文件系统中所有inode的链表,因此相邻的inode之间均会由此链表链接;而i_list链接的是处于同一个状态的所有inode。所以,相邻inode之间并不一定链接在一起。
4>与索引节点关联的方法叫索引节点操作表,它是在 struct inode_operations这个结构体中具体描述的。它的定义在 include/linux/fs.h头文件中定义。
1516struct inode_operations {
1517 int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
1518 struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
1519 int (*link) (struct dentry *,struct inode *,struct dentry *);
1520 int (*unlink) (struct inode *,struct dentry *);
1521 int (*symlink) (struct inode *,struct dentry *,const char *);
1522 int (*mkdir) (struct inode *,struct dentry *,int);
1523 int (*rmdir) (struct inode *,struct dentry *);
1524 int (*mknod) (struct inode *,struct dentry *,int,dev_t);
1525 int (*rename) (struct inode *, struct dentry *,
1526 struct inode *, struct dentry *);
1527 int (*readlink) (struct dentry *, char __user *,int);
1528 void * (*follow_link) (struct dentry *, struct nameidata *);
1529 void (*put_link) (struct dentry *, struct nameidata *, void *);
1530 void (*truncate) (struct inode *);
1531 int (*permission) (struct inode *, int);
1532 int (*check_acl)(struct inode *, int);
1533 int (*setattr) (struct dentry *, struct iattr *);
1534 int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
1535 int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
1536 ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
1537 ssize_t (*listxattr) (struct dentry *, char *, size_t);
1538 int (*removexattr) (struct dentry *, const char *);
1539 void (*truncate_range)(struct inode *, loff_t, loff_t);
1540 long (*fallocate)(struct inode *inode, int mode, loff_t offset,
1541 loff_t len);
1542 int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
1543 u64 len);
1544};
inode_operations对象包括了内核针对特定文件所能调用的方法。
现在我们对其中一些重要的结果进行分析:
create()
:如果该inode描述一个目录文件,那么当在该目录下创建或打开一个文件时,内核必须为这个文件创建一个inode。VFS通过调用该inode的
i_op->create()函数来完成上述新inode的创建。该函数的第一个参数为该目录的
inode,第二个参数为要打开新文件的dentry,第三个参数是对该文件的访问权限。如果该inode描述的是一个普通文件,那么该inode永远都
不会调用这个create函数;
lookup() :查找指定文件的dentry;
link :用于在指定目录下创建一个硬链接。这个link函数最终会被系统调用link()调用。该函数的第一个参数是原始文件的dentry,第二个参数即为上述指定目录的inode,第三个参数是链接文件的dentry。
unlink ():在某个目录下删除指定的硬链接。这个unlink函数最终会被系统调用unlink()调用。 第一个参数即为上述硬链接所在目录的inode,第二个参数为要删除文件的dentry。
symlink ():在某个目录下新建
mkdir:在指定的目录下创建一个子目录,当前目录的inode会调用i_op->mkdir()。该函数会被系统调用mkdir()调用。第一个参数即为指定目录的inode,第二个参数为子目录的dentry,第三个参数为子目录权限;
rmdir ():从inode所描述的目录中删除一个指定的子目录时,该函数会被系统调用rmdir()最终调用;
mknod() :在指定的目录下创建一个特殊文件,比如管道、设备文件或套接字等。
以上对索引节点中数据结构的解释来自edsionte
-------------------------------------------------------------------------------------
Tiger-John总结:
1.对于不同的文件系统,上面的每个函数的具体实现是不同的,也不是每个函数都必须实现,没有实现的函数对应的域应当设置为NULL 。
2.上面我们说了两个主要的操作对像:superblock和inode。它们两个对象中都包含一个操作对象。super_operations和inode_opetations它们有什么区别呢
a.super_operations对象:其中包括内核针对特定文件系统 所有调用的方法。
b.inode_operations对象: 其中包括内核对特定文件 的所有调用的方法。
所以它们一个是针对文件系统,一个是针对文件 。
3.本来inode 中应该包括“目录节点”的名称,但由于符号链接的存在,导致一个物理文件可能有多个文件名,因此把和“目录节点”名称相关的部分从 inode 中分开,放在一个专门的 dentry 结构中。
一 .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结构所形成链表的头指针。
我们来看个图: