可能小点有错误的理解。但是大体应该没问题。
参考博客很多主要进行总结。
为什么要使用虚拟文件系统?
Linux系统中存在很多的文件系统,例如常见的ext2,ext3,ext4,sysfs,rootfs,proc...很多很多。。。我们知道每个文件系统是独立的,有自己的组织方法,操作方法。那么对于用户来说,不可能所有的文件系统都了解,那么怎么做到让用户透明的去处理文件呢?例如:我想写文件,那就直接read就OK,不管你是什么文件系统,具体怎么去读!OK,这里就需要引入虚拟文件系统。
所以虚拟文件系统就是:对于一个system,可以存在多个“实际的文件系统”
比如说我所用的linux例如:ext2,ext3,fat32,ntfs...例如我现在有多个分区,
对于每一个分区我们知道可以是不同的“实际文件系统”,
例如现在三个磁盘分区分别是:ext4,tmpfs,那么每个“实际的文件系统”
的操作和数据结构什么肯定不一样,那么,用户怎么能透明使用它们呢?
那么这个时候就需要VFS作为中间一层!用户直接和VFS打交道。
例如read,write,那么映射到VFS中就是sys_read,sys_write,
那么VFS可以根据你操作的是哪个“实际文件系统”(哪个分区)来进行不同的实际的操作!
那么这个技术也是很熟悉的“钩子结构”(此名称不知道是否合理,自己一直这样叫了)技术来处理的。
其实就是VFS中提供一个抽象的struct结构体
然后对于每一个具体的文件系统要把自己的字段和函数填充进去,这样就解决了异构问题。
如图:df -Th命令查看当前文件系统
第二列是文件系统类型,最后一列是挂载点可以发现一个文件安装多次会有多个挂载点多个挂载点对应的是多个sb(超级快)超级块之间使用链表进行联系。先说到这(具体等会看)
具体对一个文件的操作如上图顺序进行。系统调用的并不知道底层文件的操作方式是什么。所以必须从vfs获取到对该文件的操作。
虚拟文件系统中包括四个部分1)超级块(super block)2)(对应一个文件系统(一个文件系统可能对应多个超级块所有超级块都是同))索引节点(inode)3)目录项(dentry)4)文件对象(file)
具体的vfs和不同文件系统之间的关系如下
超级块:一个超级块对应一个文件系统(已经安装的文件系统类型如ext2,此处是实际的文件系统哦,不是VFS)。之前我们已经说了文件系统用于管理这些文件的数据格式和操作之类的,系统文件有系统文件自己的文件系统,同时对于不同的磁盘分区也有可以是不同的文件系统。那么一个超级块对于一个独立的文件系统。保存文件系统的类型、大小、状态等等。
(“文件系统”和“文件系统类型”不一样!一个文件系统类型下可以包括很多文件系统即很多的super_block)
既然我们知道对于不同的文件系统有不同的super_block,那么对于不同的super_block的操作肯定也是不同的,所以我们在下面的super_block结构中可以看到上面说的抽象的struct结构(例如下面的:struct super_operations):
(linux内核2.4.37)
- struct super_block {
- 746 struct list_head s_list; /* Keep this first */
- 747 kdev_t s_dev;
- 748 unsigned long s_blocksize;
- 749 unsigned char s_blocksize_bits;
- 750 unsigned char s_dirt;
- 751 unsigned long long s_maxbytes; /* Max file size */
- 752 struct file_system_type *s_type;
- 753 struct super_operations *s_op;
- 754 struct dquot_operations *dq_op;
- 755 struct quotactl_ops *s_qcop;
- 756 unsigned long s_flags;
- 757 unsigned long s_magic;
- 758 struct dentry *s_root;
- 759 struct rw_semaphore s_umount;
- 760 struct semaphore s_lock;
- 761 int s_count;
- 762 atomic_t s_active;
- 763
- 764 struct list_head s_dirty; /* dirty inodes */
- 765 struct list_head s_locked_inodes;/* inodes being synced */
- 766 struct list_head s_files;
- 767
- 768 struct block_device *s_bdev;
- 769 struct list_head s_instances;
- 770 struct quota_info s_dquot; /* Diskquota specific options */
- 771
- 772 union {
- 773 struct minix_sb_info minix_sb;
- 774 struct ext2_sb_info ext2_sb;
- 775 struct ext3_sb_info ext3_sb;
- 776 struct hpfs_sb_info hpfs_sb;
- 777 struct ntfs_sb_info ntfs_sb;
- 778 struct msdos_sb_info msdos_sb;
- 779 struct isofs_sb_info isofs_sb;
- 780 struct nfs_sb_info nfs_sb;
- 781 struct sysv_sb_info sysv_sb;
- 782 struct affs_sb_info affs_sb;
- 783 struct ufs_sb_info ufs_sb;
- 784 struct efs_sb_info efs_sb;
- 785 struct shmem_sb_info shmem_sb;
- 786 struct romfs_sb_info romfs_sb;
- 787 struct smb_sb_info smbfs_sb;
- 788 struct hfs_sb_info hfs_sb;
- 789 struct adfs_sb_info adfs_sb;
- 790 struct qnx4_sb_info qnx4_sb;
- 791 struct reiserfs_sb_info reiserfs_sb;
- 792 struct bfs_sb_info bfs_sb;
- 793 struct udf_sb_info udf_sb;
- 794 struct ncp_sb_info ncpfs_sb;
- 795 struct usbdev_sb_info usbdevfs_sb;
- 796 struct jffs2_sb_info jffs2_sb;
- 797 struct cramfs_sb_info cramfs_sb;
- 798 void *generic_sbp;
- 799 } u;
- 800 /*
- 801 * The next field is for VFS *only*. No filesystems have any business
- 802 * even looking at it. You had been warned.
- 803 */
- 804 struct semaphore s_vfs_rename_sem; /* Kludge */
- 805
- 806 /* The next field is used by knfsd when converting a (inode number based)
- 807 * file handle into a dentry. As it builds a path in the dcache tree from
- 808 * the bottom up, there may for a time be a subpath of dentrys which is not
- 809 * connected to the main tree. This semaphore ensure that there is only ever
- 810 * one such free path per filesystem. Note that unconnected files (or other
- 811 * non-directories) are allowed, but not unconnected diretories.
- 812 */
- 813 struct semaphore s_nfsd_free_path_sem;
- 814 };
索引节点inode:保存的其实是实际的数据的一些信息,这些信息称为“元数据”(也就是对文件属性的描述)。例如:文件大小,设备标识符,用户标识符,用户组标识符,文件模式,扩展属性,文件读取或修改的时间戳,链接数量,指向存储该内容的磁盘区块的指针,文件分类等等。(索引节点是内核中(vfs的super块中定义的inode),对应磁盘中也有inode的节点)如果没找到就从磁盘上加载到内核中。node模块可链接到
address_space模块,方便查找自身文件数据是否已经缓存。
address_space模块:address_space是Linux内核中的一个关键抽象,它被作为文件系统和页缓存的中间
适配器,用来指示一个文件在页缓存中已经缓存了的物理页。因此,它是页缓存和外部设备中文件系统的桥
梁。如果将文件系统可以理解成数据源,那么address_space可以说关联了内存系统和文件系统。
i_hash:指向hash链表指针,用于inode的hash表,下面会说
i_list:指向索引节点链表指针,用于inode之间的连接,下面会说
i_dentry:指向目录项链表指针,注意一个inodes可以对应多个dentry,因为一个实际的文件可能被链接到其他的文件,那么就会有另一个dentry,这个链表就是将所有的与本inode有关的dentry都连在一起。
i_dirty_buffers和i_dirty_data_buffers:脏数据缓冲区
i_ino:索引节点号,每个inode都是唯一的
i_count:引用计数
i_dev:如果inode代表设备,那么就是设备号
i_mode:文件的类型和访问权限
i_nlink:与该节点建立链接的文件数(硬链接数)
i_uid:文件拥有者标号
i_gid:文件所在组标号
i_rdev:实际的设备标识
注意i_dev和i_rdev之间区别:如果是普通的文件,例如磁盘文件,存储在某块磁盘上,那么i_dev代表的就是保存这个文件的磁盘号,但是如果此处是特殊文件例如就是磁盘本身(因为所有的设备也看做文件处理),那么i_rdev就代表这个磁盘实际的磁盘号。
i_size:inode所代表的的文件的大小,以字节为单位
i_atime:文件最后一次访问时间
i_mtime:文件最后一次修改时间
i_ctime:inode最后一次修改时间
i_blkbits:块大小,字节单位
i_blksize:块大小,bit单位
i_blocks:文件所占块数
i_version:版本号
i_bytes:文件中最后一个块的字节数
i_sem:指向用于同步操作的信号量结构
i_alloc_sem:保护inode上的IO操作不被另一个打断
i_zombie:僵尸inode信号量
i_op:索引节点操作
i_fop:文件操作
i_sb:inode所属文件系统的超级块指针
i_wait:指向索引节点等待队列指针
i_flock:文件锁链表
目录项:目录项是描述文件的逻辑属性,只存在于内存中,并没有实际对应的磁盘上的描述,更确切的说是存在于内存的目录项缓存,为了提高查找性能而设计。注意不管是文件夹还是最终的文件,都是属于目录项,所有的目录项在一起构成一颗庞大的目录树。例如:open一个文件/home/xxx/yyy.txt,那么/、home、xxx、yyy.txt都是一个目录项,VFS在查找的时候,根据一层一层的目录项找到对应的每个目录项的inode,那么沿着目录项进行操作就可以找到最终的文件。(目录树)录项对象:方便查找文件的目的(路径),目录项对象没有对应的磁盘数据结构,目录项的块,存储的是这个
目录下的所有的文件的inode号和文件名等信息。其内部是树形结构,操作系统检索一个文件,都是从根目录开
始,按层次解析路径中的所有目录,直到定位到文件。(如果没有进行缓存)
注意:目录也是一种文件(所以也存在对应的inode)。打开目录,实际上就是打开目录文件。
=>文件对象:注意文件对象描述的是进程已经打开的文件。因为一个文件可以被多个进程打开,所以一个文件可以存在多个文件对象。但是由于文件是唯一的,那么inode就是唯一的,目录项也是定的!
进程其实是通过文件描述符来操作文件的,注意每个文件都有一个32位的数字来表示下一个读写的字节位置,这个数字叫做文件文件对象所对应的是file结构体
task_stuct 中有一个file_struct 结构体有一个保存所有打开文件指针的数组叫做
请看下图对应的结构如下
1.read()到sys_read();
2.通过task_struct->file_struct->fd[]->file*->f_op->read();
3通过目录项,找到该文件的inode;
4.在文件表中,通过文件内容偏移量计算出要读取的页;
5.通过inode找到文件对应的address_space;
6.在address_space中访问该文件的页缓存树,查找对应的页缓存结点:
(1)如果页缓存命中,那么直接返回文件内容;
(2)如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取
相应的页填充该缓存页;重新进行第6步查找页缓存;
写文件:
1.前五步一致
2.如果页缓存命中,直接把文件内容修改更新在页缓存的页中。写文件就结束了。这时候文件修改位于页缓存,并没有写
回到磁盘文件中去。
3.如果页缓存缺失,那么产生一个页缺失异常,创建一个页缓存页,同时通过inode找到文件该页的磁盘地址,读取相应
的页填充该缓存页。此时缓存页命中,进行第6步。
6.一个页缓存中的页如果被修改,那么会被标记成脏页。脏页需要写回到磁盘中的文件块。有两种方式可以把脏页写回磁
盘:
(1)手动调用sync()或者fsync()系统调用把脏页写回
(2)pdflush进程会定时把脏页写回到磁盘
同时注意,脏页不能被置换出内存,如果脏页正在被写回,那么会被设置写回标记,这时候该页就被上锁,其他写请求被
阻塞直到锁释放。