概述:此系列文章,主要参考《linux内核源代码情景分析》,进行相关笔记总结,剖析ext2文件系统的实现。
1.VFS与文件系统的关系
如果把内核比作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_operations数据结构,它实际上是一个函数跳转表,例如read就指向具体的文件系统实现读操作的入口函数。
每个进程通过“打开文件”与具体的文件建立连接,或者说简历一个读写的“上下文”。这种连接是以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;
.......
}
其中有两个指针, fs 是有关文件系统的信息; files 是有关已经打开文件的信息。其中 fs_struct 定义在 include/linux/fs_struct.h 之中,结构如下
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支持的文件系统。
2.磁盘文件
一个文件包涵了两方面的信息,一个是数据本身,另一个是组织和管理信息。对于磁盘文件,这两部分都在文件系统之中。其中,后者存在inode和dentry之中,它与内存中的inode有些相似。
3.设备文件
设备文件包涵组织和管理信息,但是不一定存储着数据;根据设备类型和性质的不同,可以是用于读写的磁盘,用于接受和发送的网络卡,用于采集和控制的一些机电设备。
4.特殊文件
特殊文件在内存中有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 };
下面,我们再来看看ext2_inode的数据结构:
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结构中。