一:
什么是文件系统,详见:http://zh.wikipedia.org/zh/%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F
其实一句话就是管理这块文件的机制(组织方式,数据结构之类...)
Linux系统中存在很多的文件系统,例如常见的ext2,ext3,ext4,sysfs,rootfs,proc...很多很多。。。我们知道每个文件系统是独立的,有自己的组织方法,操作方法。那么对于用户来说,不可能所有的文件系统都了解,那么怎么做到让用户透明的去处理文件呢?例如:我想写文件,那就直接read就OK,不管你是什么文件系统,具体怎么去读!OK,这里就需要引入虚拟文件系统。
所以虚拟文件系统就是:对于一个system,可以存在多个“实际的文件系统”,例如:ext2,ext3,fat32,ntfs...例如我现在有多个分区,对于每一个分区我们知道可以是不同的“实际文件系统”,例如现在三个磁盘分区分别是:ext2,ext3,fat32,那么每个“实际的文件系统”的操作和数据结构什么肯定不一样,那么,用户怎么能透明使用它们呢?那么这个时候就需要VFS作为中间一层!用户直接和VFS打交道。例如read,write,那么映射到VFS中就是sys_read,sys_write,那么VFS可以根据你操作的是哪个“实际文件系统”(哪个分区)来进行不同的实际的操作!那么这个技术也是很熟悉的“钩子结构”(此名称不知道是否合理,自己一直这样叫了)技术来处理的。其实就是VFS中提供一个抽象的struct结构体,然后对于每一个具体的文件系统要把自己的字段和函数填充进去,这样就解决了异构问题。
如图:
二:
Linux虚拟文件系统四大对象:
1)超级块(super block)
2)索引节点(inode)
3)目录项(dentry)
4)文件对象(file)
=> 超级块:一个超级块对应一个文件系统(已经安装的文件系统类型如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 };
解释字段:
s_list:指向超级块链表的指针,这个struct list_head是很熟悉的结构了,里面其实就是用于连接关系的prev和next字段。
内核中的结构处理都是有讲究的(内核协议栈中也说过),内核单独使用一个简单的结构体将所有的super_block都链接起来,但是这个结构不是super_block本身,因为本身数据结构太大,效率不高,所有仅仅使用
struct
{
list_head prev;
list_head next;
}
这样的结构来将super_block中的s_list链接起来,那么遍历到s_list之后,直接读取super_block这么长的一个内存块,就可以将这个
super_block直接读进来!这样就很快捷方便!这也是为什么s_list必须放在第一个字段的原因。
s_dev:包含该具体文件系统的块设备标识符。例如,对于 /dev/hda1,其设备标识符为 0x301
s_blocksize:文件系统中数据块大小,以字节单位
s_blocksize_bits:上面的size大小占用位数,例如512字节就是9 bits
s_dirt:脏位,标识是否超级块被修改
s_maxbytes:允许的最大的文件大小(字节数)
struct file_system_type *s_type:文件系统类型(也就是当前这个文件系统属于哪个类型?ext2还是fat32)
要区分“文件系统”和“文件系统类型”不一样!一个文件系统类型下可以包括很多文件系统即很多的super_block,后面会说!
struct super_operations *s_op:指向某个特定的具体文件系统的用于超级块操作的函数集合
struct dquot_operations *dq_op:指向某个特定的具体文件系统用于限额操作的函数集合
struct quotactl_ops *s_qcop:用于配置磁盘限额的的方法,处理来自用户空间的请求
s_flags:安装标识
s_magic:区别于其他文件系统的标识
s_root:指向该具体文件系统安装目录的目录项
s_umount:对超级块读写时进行同步
s_lock:锁标志位,若置该位,则其它进程不能对该超级块操作
s_count:对超级块的使用计数
s_active:引用计数
s_dirty:已修改的索引节点inode形成的链表,一个文件系统中有很多的inode,有些inode节点的内容会被修改,那么会先被记录,然后写回磁盘。
s_locked_inodes:要进行同步的索引节点形成的链表
s_files:所有的已经打开文件的链表,这个file和实实在在的进程相关的
s_bdev:指向文件系统被安装的块设备
u:u 联合体域包括属于具体文件系统的超级块信息
s_instances:具体的意义后来会说的!(同一类型的文件系统通过这个子墩将所有的super_block连接起来)
s_dquot:磁盘限额相关选项
=>索引节点inode:保存的其实是实际的数据的一些信息,这些信息称为“元数据”(也就是对文件属性的描述)。例如:文件大小,设备标识符,用户标识符,用户组标识符,文件模式,扩展属性,文件读取或修改的时间戳,链接数量,指向存储该内容的磁盘区块的指针,文件分类等等。
( 注意数据分成:元数据+数据本身 )
同时注意:inode有两种,一种是VFS的inode,一种是具体文件系统的inode。前者在内存中,后者在磁盘中。所以每次其实是将磁盘中的inode调进填充内存中的inode,这样才是算使用了磁盘文件inode。
注意inode怎样生成的:每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定(现代OS可以动态变化),一般每2KB就设置一个inode。一般文件系统中很少有文件小于2KB的,所以预定按照2KB分,一般inode是用不完的。所以inode在文件系统安装的时候会有一个默认数量,后期会根据实际的需要发生变化。
注意inode号:inode号是唯一的,表示不同的文件。其实在Linux内部的时候,访问文件都是通过inode号来进行的,所谓文件名仅仅是给用户容易使用的。当我们打开一个文件的时候,首先,系统找到这个文件名对应的inode号;然后,通过inode号,得到inode信息,最后,由inode找到文件数据所在的block,现在可以处理文件数据了。
inode和文件的关系:当创建一个文件的时候,就给文件分配了一个inode。一个inode只对应一个实际文件,一个文件也会只有一个inode。inodes最大数量就是文件的最大数量。
维基上说的也比较详细:维基-inode
440 struct inode { 441 struct list_head i_hash; 442 struct list_head i_list; 443 struct list_head i_dentry; 444 445 struct list_head i_dirty_buffers; 446 struct list_head i_dirty_data_buffers; 447 448 unsigned long i_ino; 449 atomic_t i_count; 450 kdev_t i_dev; 451 umode_t i_mode; 452 unsigned int i_nlink; 453 uid_t i_uid; 454 gid_t i_gid; 455 kdev_t i_rdev; 456 loff_t i_size; 457 time_t i_atime; 458 time_t i_mtime; 459 time_t i_ctime; 460 unsigned int i_blkbits; 461 unsigned long i_blksize; 462 unsigned long i_blocks; 463 unsigned long i_version; 464 unsigned short i_bytes; 465 struct semaphore i_sem; 466 struct rw_semaphore i_alloc_sem; 467 struct semaphore i_zombie; 468 struct inode_operations *i_op; 469 struct file_operations *i_fop; /* former ->i_op->default_file_ops */ 470 struct super_block *i_sb; 471 wait_queue_head_t i_wait; 472 struct file_lock *i_flock; 473 struct address_space *i_mapping; 474 struct address_space i_data; 475 struct dquot *i_dquot[MAXQUOTAS]; 476 /* These three should probably be a union */ 477 struct list_head i_devices; 478 struct pipe_inode_info *i_pipe; 479 struct block_device *i_bdev; 480 struct char_device *i_cdev; 481 482 unsigned long i_dnotify_mask; /* Directory notify events */ 483 struct dnotify_struct *i_dnotify; /* for directory notifications */ 484 485 unsigned long i_state; 486 487 unsigned int i_flags; 488 unsigned char i_sock; 489 490 atomic_t i_writecount; 491 unsigned int i_attr_flags; 492 __u32 i_generation; 493 union { 494 struct minix_inode_info minix_i; 495 struct ext2_inode_info ext2_i; 496 struct ext3_inode_info ext3_i; 497 struct hpfs_inode_info hpfs_i; 498 struct ntfs_inode_info ntfs_i; 499 struct msdos_inode_info msdos_i; 500 struct umsdos_inode_info umsdos_i; 501 struct iso_inode_info isofs_i; 502 struct nfs_inode_info nfs_i; 503 struct sysv_inode_info sysv_i; 504 struct affs_inode_info affs_i; 505 struct ufs_inode_info ufs_i; 506 struct efs_inode_info efs_i; 507 struct romfs_inode_info romfs_i; 508 struct shmem_inode_info shmem_i; 509 struct coda_inode_info coda_i; 510 struct smb_inode_info smbfs_i; 511 struct hfs_inode_info hfs_i; 512 struct adfs_inode_info adfs_i; 513 struct qnx4_inode_info qnx4_i; 514 struct reiserfs_inode_info reiserfs_i; 515 struct bfs_inode_info bfs_i; 516 struct udf_inode_info udf_i; 517 struct ncp_inode_info ncpfs_i; 518 struct proc_inode_info proc_i; 519 struct socket socket_i; 520 struct usbdev_inode_info usbdev_i; 521 struct jffs2_inode_info jffs2_i; 522 void *generic_ip; 523 } u; 524 };
解释一些字段:
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:文件锁链表
注意下面:address_space不是代表某个地址空间,而是用于描述页高速缓存中的页面的。一个文件对应一个address_space,一个address_space和一个偏移量可以确定一个页高速缓存中的页面。
i_mapping:表示向谁请求页面
i_data:表示被inode读写的页面
i_dquot:inode的磁盘限额
关于磁盘限额:在多任务环境下,对于每个用户的磁盘使用限制是必须的,起到一个公平性作用。
磁盘限额分为两种:block限额和inode限额,而且对于一个特文件系统来说,使用的限额机制都是一样的,所以限额的操作函数
放在super_block中就OK!
i_devices:设备链表。共用同一个驱动程序的设备形成的链表。
i_pipe:指向管道文件(如果文件是管道文件时使用)
i_bdev:指向块设备文件指针(如果文件是块设备文件时使用)
i_cdev:指向字符设备文件指针(如果文件是字符设备时使用)
i_dnotify_mask:目录通知事件掩码
i_dnotify:用于目录通知
i_state:索引节点的状态标识:I_NEW,I_LOCK,I_FREEING
i_flags:索引节点的安装标识
i_sock:如果是套接字文件则为True
i_write_count:记录多少进程以刻写模式打开此文件
i_attr_flags:文件创建标识
i_generation:保留
u:具体的inode信息
注意管理inode的四个链表:
inode_unused:将目前还没有使用的inode链接起来(通过i_list域链接)
inode_in_use:目前正在使用的inode链接起来(通过i_list域链接)
super_block中的s_dirty:将所有修改过的inode链接起来,这个字段在super_block中(通过i_list域链接起来)
inode_hashtable:注意为了加快inode的查找效率,将正在使用的inode和脏inode也会放在inode_hashtable这样一个hash结构中,
但是,不同的inode的hash值可能相等,所以将hash值相等的这些inode通过这个i_hash字段连接起来。
=>目录项:目录项是描述文件的逻辑属性,只存在于内存中,并没有实际对应的磁盘上的描述,更确切的说是存在于内存的目录项缓存,为了提高查找性能而设计。注意不管是文件夹还是最终的文件,都是属于目录项,所有的目录项在一起构成一颗庞大的目录树。例如:open一个文件/home/xxx/yyy.txt,那么/、home、xxx、yyy.txt都是一个目录项,VFS在查找的时候,根据一层一层的目录项找到对应的每个目录项的inode,那么沿着目录项进行操作就可以找到最终的文件。
注意:目录也是一种文件(所以也存在对应的inode)。打开目录,实际上就是打开目录文件。
67 struct dentry { 68 atomic_t d_count; 69 unsigned int d_flags; 70 struct inode * d_inode; /* Where the name belongs to - NULL is negative */ 71 struct dentry * d_parent; /* parent directory */ 72 struct list_head d_hash; /* lookup hash list */ 73 struct list_head d_lru; /* d_count = 0 LRU list */ 74 struct list_head d_child; /* child of parent list */ 75 struct list_head d_subdirs; /* our children */ 76 struct list_head d_alias; /* inode alias list */ 77 int d_mounted; 78 struct qstr d_name; 79 unsigned long d_time; /* used by d_revalidate */ 80 struct dentry_operations *d_op; 81 struct super_block * d_sb; /* The root of the dentry tree */ 82 unsigned long d_vfs_flags; 83 void * d_fsdata; /* fs-specific data */ 84 unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */ 85 };
解释一些字段:
d_count:引用计数
d_flags:目录项缓存标识,可取DCACHE_UNUSED、DCACHE_REFERENCED等
d_inode:与该目录项关联的inode
d_parent:父目录的目录项
d_hash:内核使用dentry_hashtable对dentry进行管理,dentry_hashtable是由list_head组成的链表,一个dentry创建之后,就通过
d_hash链接进入对应的hash值的链表中。
d_lru:最近未使用的目录项的链表
d_child:目录项通过这个加入到父目录的d_subdirs中
d_subdirs:本目录的所有孩子目录链表头
d_alias:一个有效的dentry必然与一个inode关联,但是一个inode可以对应多个dentry,因为一个文件可以被链接到其他文件,所以,这个dentry就是通过这个字段链接到属于自己的inode结构中的i_dentry链表中的。(inode中讲过)
d_mounted:安装在该目录的文件系统的数量!注意一个文件目录下可以有不同的文件系统!
d_name:目录项名称
d_time:重新变为有效的时间!注意只要操作成功这个dentry就是有效的,否则无效。
d_op:目录项操作
d_sb:这个目录项所属的文件系统的超级块
d_vfs_flags:一些标志
d_fsdata:文件系统私有数据
d_iname:存放短的文件名
一些解释:一个有效的dentry结构必定有一个inode结构,这是因为一个目录项要么代表着一个文件,要么代表着一个目录,而目录实际上也是文件。所以,只要dentry结构是有效的,则其指针d_inode必定指向一个inode结构。但是inode却可以对应多个
dentry,上面已经说过两次了。
注意:整个结构其实就是一棵树。
=>文件对象:注意文件对象描述的是进程已经打开的文件。因为一个文件可以被多个进程打开,所以一个文件可以存在多个文件对象。但是由于文件是唯一的,那么inode就是唯一的,目录项也是定的!
进程其实是通过文件描述符来操作文件的,注意每个文件都有一个32位的数字来表示下一个读写的字节位置,这个数字叫做文件位置。一般情况下打开文件后,打开位置都是从0开始,除非一些特殊情况。Linux用file结构体来保存打开的文件的位置,所以file称为打开的文件描述。这个需要好好理解一下!file结构形成一个双链表,称为系统打开文件表。
565 struct file { 566 struct list_head f_list; 567 struct dentry *f_dentry; 568 struct vfsmount *f_vfsmnt; 569 struct file_operations *f_op; 570 atomic_t f_count; 571 unsigned int f_flags; 572 mode_t f_mode; 573 loff_t f_pos; 574 unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin; 575 struct fown_struct f_owner; 576 unsigned int f_uid, f_gid; 577 int f_error; 578 579 size_t f_maxcount; 580 unsigned long f_version; 581 582 /* needed for tty driver, and maybe others */ 583 void *private_data; 584 585 /* preallocated helper kiobuf to speedup O_DIRECT */ 586 struct kiobuf *f_iobuf; 587 long f_iobuf_lock; 588 };
解释一些字段:
f_list:所有的打开的文件形成的链表!注意一个文件系统所有的打开的文件都通过这个链接到super_block中的s_files链表中!
f_dentry:与该文件相关的dentry
f_vfsmnt:该文件在这个文件系统中的安装点
f_op:文件操作,当进程打开文件的时候,这个文件的关联inode中的i_fop文件操作会初始化这个f_op字段
f_count:引用计数
f_flags:打开文件时候指定的标识
f_mode:文件的访问模式
f_pos:目前文件的相对开头的偏移
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin:预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及预读的页面数
f_owner:记录一个进程ID,以及当某些事发送的时候发送给该ID进程的信号
f_uid:用户ID
f_gid:组ID
f_error:写操作错误码
f_version:版本号,当f_pos改变时候,version递增
private_data:私有数据( 文件系统和驱动程序使用 )
注意:对于“正在使用”和“未使用”的文件对象分别使用一个双向链表进行管理。
注意上面的file只是对一个文件而言,对于一个进程(用户)来说,可以同时处理多个文件,所以需要另一个结构来管理所有的files!
即:用户打开文件表--->files_struct
172 struct files_struct { 173 atomic_t count; 174 rwlock_t file_lock; /* Protects all the below members. Nests inside tsk->alloc_lock */ 175 int max_fds; 176 int max_fdset; 177 int next_fd; 178 struct file ** fd; /* current fd array */ 179 fd_set *close_on_exec; 180 fd_set *open_fds; 181 fd_set close_on_exec_init; 182 fd_set open_fds_init; 183 struct file * fd_array[NR_OPEN_DEFAULT]; 184 };
解释一些字段:
count:引用计数
file_lock:锁,保护下面的字段
max_fds:当前文件对象的最大的数量
max_fdset:文件描述符最大数
next_fd:已分配的最大的文件描述符+1
fd:指向文件对象指针数组的指针,一般就是指向最后一个字段fd_arrray,当文件数超过NR_OPEN_DEFAULT时候,就会重新分配一个数组,然后指向这个新的数组指针!
close_on_exec:执行exec()时候需要关闭的文件描述符
open_fds:指向打开的文件描述符的指针
close_on_exec_init:执行exec()时候需要关闭的文件描述符初始化值
open_fds_init:文件描述符初值集合
fd_array:文件对象指针的初始化数组
注意上面的file和files_struct记录的是与进程相关的文件的信息,但是对于进程本身来说,自身的一些信息用什么表示,这里就涉及到fs_struct结构体。
5 struct fs_struct { 6 atomic_t count; 7 rwlock_t lock; 8 int umask; 9 struct dentry * root, * pwd, * altroot; 10 struct vfsmount * rootmnt, * pwdmnt, * altrootmnt; 11 };
解释一些字段:
count:引用计数
lock:保护锁
umask:打开文件时候默认的文件访问权限
root:进程的根目录
pwd:进程当前的执行目录
altroot:用户设置的替换根目录
注意:实际运行时,这三个目录不一定都在同一个文件系统中。例如,进程的根目录通常是安装于“/”节点上的ext文件系统,而当前工作目录可能是安装于/etc的一个文件系统,替换根目录也可以不同文件系统中。
rootmnt,pwdmnt,altrootmnt:对应于上面三个的安装点。
基本的概念和基本的结构总结完了,后面会总结看看这些之间的关系。