概述:此系列文章,主要参考《linux内核源代码情景分析》,进行相关笔记总结,剖析ext2文件系统的实现。
如果把内核比作PC的母版,VFS就是上面的插槽,具体的文件系统就是上面的接口卡。其中VFS和具体操作系统之间的界面是有明确定义的,这个界面的主体是一个file_operations的数据结构,其定义在include/linux/fs.h之中。
/* *NOTE: *read, write, poll, fsync, readv, writev, unlocked_ioctl andcompat_ioctl *can be called without the big kernel lock held in all filesystems. */ struct file_operations { struct module *owner; loff_t(*llseek) (struct file *, loff_t, int); ssize_t(*read) (struct file *, char __user *, size_t, loff_t *); ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t(*aio_read) (struct kiocb *, const struct iovec *, unsigned long,loff_t); ssize_t(*aio_write) (struct kiocb *, const struct iovec *, unsigned long,loff_t); int(*readdir) (struct file *, void *, filldir_t); unsignedint (*poll) (struct file *, struct poll_table_struct *); int(*ioctl) (struct inode *, struct file *, unsigned int, unsignedlong); long(*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long(*compat_ioctl) (struct file *, unsigned int, unsigned long); int(*mmap) (struct file *, struct vm_area_struct *); int(*open) (struct inode *, struct file *); int(*flush) (struct file *, fl_owner_t id); int(*release) (struct inode *, struct file *); int(*fsync) (struct file *, struct dentry *, int datasync); int(*aio_fsync) (struct kiocb *, int datasync); int(*fasync) (int, struct file *, int); int(*lock) (struct file *, int, struct file_lock *); ssize_t(*sendpage) (struct file *, struct page *, int, size_t, loff_t *,int); unsignedlong (*get_unmapped_area)(struct file *, unsigned long, unsignedlong, unsigned long, unsigned long); int(*check_flags)(int); int(*flock) (struct file *, int, struct file_lock *); ssize_t(*splice_write)(struct pipe_inode_info *, struct file *, loff_t *,size_t, unsigned int); ssize_t(*splice_read)(struct file *, loff_t *, struct pipe_inode_info *,size_t, unsigned int); int(*setlease)(struct file *, long, struct file_lock **); };
每个进程通过“打开文件”与具体的文件建立连接,或者说简历一个读写的“上下文”。这种连接是以file结构中的f_op指针(它是file_operations类型)来实现的。
Linux内核中对fs和具体文件系统的关系划分如下:
注:其中,VFS实现的是sys_read()etc;通过file结构中的f_op指针实现的“文件系统总线”。
进程和文件的连接是通过“打开文件”来实现的。其中,task_struct结构(include/linux/sched.h)中与文件相关的部分如下:
struct task_struct { 1331 /* filesystem information */ 1332 struct fs_struct *fs; 1333 /* open file information */ 1334 struct files_struct *files; ....... }
struct fs_struct { atomic_t count; rwlock_t lock; 9 int umask; struct dentry * root, * pwd, * altroot; 11 struct vfsmount *rootmnt, * pwdmnt, *altrootmnt; 12 }; //kernel Version
(注意2.6.34之中的,和2.4内核已经有一些不同,而下面的文字是按照2.4解释的。)结构中有六个指针。前三个是dentry结构指针,其中dentry结构中记录着文件的各项属性,如文件名/访问权限等。pwd:当前所在目录;root:进程根目录;altroot:替换根目录。后三个指针就各自指向代表着这些“安装”的fsmount数据结构。注意,fs_struct结构中的信息和文件系统和进程相关,与具体打开的文件无关。
与具体打开文件有关的信息在file结构中,而files_struct结构的主体就是一个file结构数组。
每次打开一个文件,进程就用fid来访问,fid实际上就是相应file结构在数组中的下标。file结构有个指针f_op,指向对应的file_operations结构。同时,file结构中还有一个指针f_dentry,指向该文件的dentry结构。
每个文件除了有一个dentry结构以外,还有一个inode数据结构,记载着文件哎你在存储介质上的位置和分布信息。
VFS与具体文件系统之间界面的主体是file_operation数据结构,另有dentry_operations和inode_operations.定义在include/linux/dcache.h之中。
134 struct 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 };
linux到底支持哪些具体的文件系统呢?inode中有个成分u,是一个union,可以从定义查看linux支持的文件系统。
一个文件包涵了两方面的信息,一个是数据本身,另一个是组织和管理信息。对于磁盘文件,这两部分都在文件系统之中。其中,后者存在inode和dentry之中,它与内存中的inode有些相似。
设备文件包涵组织和管理信息,但是不一定存储着数据;根据设备类型和性质的不同,可以是用于读写的磁盘,用于接受和发送的网络卡,用于采集和控制的一些机电设备。
特殊文件在内存中有inode和dentry但是不宜定在存储介质上有索引节点和目录项。linux上的特殊文件主要有proc,socket和管道文件。
三个不同类型的特殊文件都有一个共同点:都有一些用于组织和管理的信息。要访问一个文件的时候,需要通过它的索引才知道这种文件类型,组织方式,存储地点,下层驱动位置。(注意:我们要访问文件,是按名称存储的,具体而言,实际上我们给的是逻辑文件,需要的是物理文件)inode定义在include/linux/fs.h之中,具体结构如下
387 struct inode { 388 struct list_head i_hash; 389 struct list_head i_list; 390 struct list_head i_dentry; 391 392 struct list_head i_dirty_buffers; 393 394 unsigned long i_ino; 395 atomic_t i_count; 396 kdev_t i_dev; 397 umode_t i_mode; 398 nlink_t i_nlink; 399 uid_t i_uid; 400 gid_t i_gid; 401 kdev_t i_rdev; 402 loff_t i_size; 403 time_t i_atime; 404 time_t i_mtime; 405 time_t i_ctime; 406 unsigned long i_blksize; 407 unsigned long i_blocks; 408 unsigned long i_version; 409 struct semaphore i_sem; 410 struct semaphore i_zombie; 411 struct inode_operations *i_op; 412 struct file_operations *i_fop; /* former ->i_op->default_file_ops */ 413 struct super_block *i_sb; 414 wait_queue_head_t i_wait; 415 struct file_lock *i_flock; 416 struct address_space *i_mapping; 417 struct address_space i_data; 418 struct dquot *i_dquot[MAXQUOTAS]; 419 struct pipe_inode_info *i_pipe; 420 struct block_device *i_bdev; 422 unsigned long i_dnotify_mask; /* Directory notify events */ 423 struct dnotify_struct *i_dnotify; /* for directory notifications */ 424 425 unsigned long i_state; 426 427 unsigned int i_flags; 428 unsigned char i_sock; 429 430 atomic_t i_writecount; 431 unsigned int i_attr_flags; 432 __u32 i_generation; 433 union { 434 struct minix_inode_info minix_i; 435 struct ext2_inode_info ext2_i; 436 struct hpfs_inode_info hpfs_i; 437 struct ntfs_inode_info ntfs_i; 438 struct msdos_inode_info msdos_i; 439 struct umsdos_inode_info umsdos_i; 440 struct iso_inode_info isofs_i; 441 struct nfs_inode_info nfs_i; 442 struct sysv_inode_info sysv_i; 443 struct affs_inode_info affs_i; 444 struct ufs_inode_info ufs_i; 445 struct efs_inode_info efs_i; 446 struct romfs_inode_info romfs_i; 447 struct shmem_inode_info shmem_i; 448 struct coda_inode_info coda_i; 449 struct smb_inode_info smbfs_i; 450 struct hfs_inode_info hfs_i; 451 struct adfs_inode_info adfs_i; 452 struct qnx4_inode_info qnx4_i; 453 struct bfs_inode_info bfs_i; 454 struct udf_inode_info udf_i; 455 struct ncp_inode_info ncpfs_i; 456 struct proc_inode_info proc_i; 457 struct socket socket_i; 458 struct usbdev_inode_info usbdev_i; 459 void *generic_ip; 460 } u; 461 };
242 struct ext2_inode { 243 __le16 i_mode; /* File mode */ 244 __le16 i_uid; /* Low 16 bits of Owner Uid */ 245 __le32 i_size; /* Size in bytes */ 246 __le32 i_atime; /* Access time */ 247 __le32 i_ctime; /* Creation time */ 248 __le32 i_mtime; /* Modification time */ 249 __le32 i_dtime; /* Deletion Time */ 250 __le16 i_gid; /* Low 16 bits of Group Id */ 251 __le16 i_links_count; /* Links count */ 252 __le32 i_blocks; /* Blocks count */ 253 __le32 i_flags; /* File flags */ 254 union { 255 struct { 256 __le32 l_i_reserved1; 257 } linux1; 258 struct { 259 __le32 h_i_translator; 260 } hurd1; 261 struct { 262 __le32 m_i_reserved1; 263 } masix1; 264 } osd1; /* OS dependent 1 */ 265 __le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ 266 __le32 i_generation; /* File version (for NFS) */ 267 __le32 i_file_acl; /* File ACL */ 268 __le32 i_dir_acl; /* Directory ACL */ 269 __le32 i_faddr; /* Fragment address */ 270 union { 271 struct { 272 __u8 l_i_frag; /* Fragment number */ 273 __u8 l_i_fsize; /* Fragment size */ 274 __u16 i_pad1; 275 __le16 l_i_uid_high; /* these 2 fields */ 276 __le16 l_i_gid_high; /* were reserved2[0] */ 277 __u32 l_i_reserved2; 278 } linux2; 279 struct { 283 __le16 h_i_uid_high; 284 __le16 h_i_gid_high; 285 __le32 h_i_author; 286 } hurd2; 287 struct { 288 __u8 m_i_frag; /* Fragment number */ 289 __u8 m_i_fsize; /* Fragment size */ 290 __u16 m_pad1; 291 __u32 m_i_reserved2[2]; 292 } masix2; 293 } osd2; /* OS dependent 2 */ 294 };
虽然inode结构包涵了文件的组织和管理信息,但是“文件名”却不在其中。要知道,文件名是存在于dentry结构之中的。下面,我们来看看ext2_dir_entry_2数据结构,它也是在ext2_fs.h中定义的。
542 struct ext2_dir_entry { 543 __le32 inode; /* Inode number */ 544 __le16 rec_len; /* Directory entry length */ 545 __le16 name_len; /* Name length */ 546 char name[EXT2_NAME_LEN]; /* File name */ 547 };
563 /* 564 * Ext2 directory file types. Only the low 3 bits are used. The 565 * other bits are reserved for now. 566 */ 567 enum { 568 EXT2_FT_UNKNOWN = 0, 569 EXT2_FT_REG_FILE = 1, 570 EXT2_FT_DIR = 2, 571 EXT2_FT_CHRDEV = 3, 572 EXT2_FT_BLKDEV = 4, 573 EXT2_FT_FIFO = 5, 574 EXT2_FT_SOCK = 6, 575 EXT2_FT_SYMLINK = 7, 576 EXT2_FT_MAX 577 };
同样,我们在这里对比一下ext2_dir_entry_2和dentry结构。我们看看dentry数据结构
89 struct dentry { 90 atomic_t d_count; 91 unsigned int d_flags; /* protected by d_lock */ 92 spinlock_t d_lock; /* per dentry lock */ 93 int d_mounted; 94 struct inode *d_inode; /* Where the name belongs to - NULL is 95 * negative */ 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; /* lookup hash list */ 101 struct dentry *d_parent; /* parent directory */ 102 struct qstr d_name; 103 104 struct list_head d_lru; /* LRU list */ 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; /* our children */ 113 struct list_head d_alias; /* inode alias list */ 114 unsigned long d_time; /* used by d_revalidate */ 115 const struct dentry_operations *d_op; 116 struct super_block *d_sb; /* The root of the dentry tree */ 117 void *d_fsdata; /* fs-specific data */ 118 119 unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */ 120 };
以看出,inode和dentry作为vfs层面的数据结构,和ext2_inode有很大不同,前者包涵了更多的动态信息和对后者的抽象和补充。
这里有疑问:要找文件,需要先找到对应的目录项,而目录项又存在于目录之中,目录本身也是文件;如此进入循环查找的困境。所以,必然存在这样的一个目录,它的目录项一个固定的位置,这就是根目录。根目录的位置和文件系统的其他一些参数就记录在超级块之中。超级块在设备上的逻辑位置是固定的,总是在第二个逻辑块上(第一个是引导块)。所谓安装,就是从一个存储设备读入超级块,在内存中建立一个super_block结构,进而将设备根目录与文件系统已经存在的空白目录挂钩。
对于普通文件,文件系统最终要调用驱动程序进行读写,就ext2文件系统而言,从磁盘文件的角度,对存储介质的访问可以涉及到四种不同的目标,那就是
(1)文件中的数据
(2)文件的组织和管理信息
(3)磁盘的超级块(每个分区,即逻辑磁盘都有超级块)
(4)引导块
从磁盘驱动的角度看,文件查找流程如下:逻辑文件名——》索引节点——》磁盘块号和块内位置——》物理块与位置。
前面我们提到过,接口卡和插槽的关系。,因此,file结构中的f_op就可以看作插槽中的一个触点,并且在dentry/inode等结构中都有类似的触点。除了file以外,这个插槽有很多段,有关的数据结构有:
(1)文件操作跳转表,即file_operations数据结构,例如read,write。
(2)目录项操作跳转表,即dentry_operation数据结构,例如hash,compare操作。
(3)索引节点跳转表,即inode_opertions数据结构,例如mkdir,mknod操作。
(4)超级块操作跳转表,即super_operations数据结构,例如read_inode操作。
(5)超级快本身因为文件系统而不同.
由此可见,file,dentry,inode,super_block已经超级块的位置约定都属于VFS层面。inode结构中还有一个指针i_fop,也指向具体的file_operations数据结构,实际上file结构中的指针f_op只是inode结构中这个指针的一个副本,在打开文件的时候从目标文件的inode结构中复制到file结构中。