Unix文件系统概念
1. VFS虚拟文件系统
unix为了支持多种文件系统,包括DOS FAT、Windows NTFS、UNIX EXT2、EXT3等文件系统格式,采用了面向对象的实现了VFS虚拟文件系统,屏蔽了下层硬件和文件系统的差异,向上提供了统一的访问接口。

vfs使用一组struct数据结构作为对象,并定义了对应的opertions结构,其内部包括了操作对应数据结构的方法(函数指针)。其中最重要的对象有四个:super_block对象、inode对象、dentry对象和file对象。
2. 四大对象
在super_block、inode、dentry和file对象中,都有对应的super_operations、inode_operations、dentry_operations和file_operations结构体,这些struct中定义了函数指针来对对象进行操作。如file_operations中定义了read、write、open、flush等函数指针,对该file对象进行读、写、打开和写回的操作。
如果将磁盘分为多个分区,每个分区代表一个文件系统,则在文件系统在磁盘中的表示可以分为:自举块、超级块、inode表和数据块(包括目录和数据)。每个文件对应一个inode结构,有唯一的inode编号,并且unix将目录看作是一种特殊的文件,也用一个inode结构表示,可以嵌套包含普通文件和子目录。超级块用于储存特定文件系统的信息,对于非基于磁盘的文件系统,如sysfs,则在内存中现场创建此结构。

忽略超级块和自举块,文件系统的结构就是下图的样子。inode表中可以指向文件的数据块,可以指向目录块。而目录如前所述,是一种特殊的文件,里面包含一个及多个目录项,用inode号和文件名来表示。所以unix文件系统不是按照文件名存取文件的,而是借由inode来完成,它在当前文件系统的唯一表示一个文件的。一个大文件可能包括多个数据块,目录块也包含多个目录项。

2.1 超级块
struct super_block{
struct list_head s_list; /* 指向所有超级块的链表 */
dev_t s_dev; /* 设备标示标示符 */
unsigned long s_blocksize; /* 块大小 */
unsigned char s_dirty; /* 修改(脏)标志 */
struct file_system_type s_type;/* 文件系统类型 */
struct super_operation s_op; /* 超级块操作方法 */
unsigned long s_magic; /* 文件系统幻数 */
struct dentry *s_root; /* 目录挂载点 */
int s_count; /* 超级块引用计数 */
...
};
以上,是超级块对象核心的结构。该对象通过alloc_super函数创建并初始化,在安装文件系统时,就会调用此函数,从磁盘中读出超级块,并将信息填充到此结构体中。
struct super_operations{
/* 在inode表中创建并初始化一个新的inode对象 */
struct inode* (*alloc_inode)(struct super_block *sb);
/* 释放给定的inode节点 */
void (*destory_inode)(struct inode *);
/* inode节点脏时,VFS调用此函数 */
void (*dirty_inode)(struct inode *);
/* 将给定inode写入磁盘,wait指定是否同步 */
int (*write_inode)(struct inode *, int wait);
/* 当最后一个inode节点的引用被释放时,VFS调用此函数 */
void (*drop_inode)(struct inode *);
/* 从磁盘删除给定inode节点 */
void (*delete_inode)(struct inode *);
/* 释放超级块,VFS卸载文件系统时调用 */
void (*put_super)(struct super_block *sb);
/* 更新磁盘上的超级块 */
void (*write_super)(struct super_block *sb);
/* 同步内存与磁盘中文件系统的数据元 */
int (*sync_fs)(struct super_block *sb, int wait);
/* 获取文件系统状态 */
int (*statfs)(struct dentry *, struct kstatfs *);
/* 重新挂在文件系统时,VFS调用此函数 */
int (*remount_fs)(struct super_block *, int *, char *);
/* 释放inode,并清除inode对应的数据的所有页面 */
void (*clear_inode)(struct inode *);
...
};
super_operations结构提供了创建、释放、写入和删除inode的方法,以及更新、释放和锁定超级块的方法。
2.2 索引节点
inode包含文件的元数据,如文件权限、字节数、所属组id号、拥有者id号、时间戳和引用数等,但就是不会包括文件名。文件名在目录的目录项中存在,与inode有对应关系。
struct inode {
struct hlist_node i_hash; /* 哈希表 */
struct list_head i_list; /* 索引节点链表 */
struct list_head i_dentry; /* 目录项链表 */
unsigned long i_ino; /* 节点号 */
atomic_t i_count; /* 引用记数 */
umode_t i_mode; /* 访问权限控制 */
unsigned int i_nlink; /* 硬链接数 */
uid_t i_uid; /* 使用者id */
gid_t i_gid; /* 使用者id组 */
kdev_t i_rdev; /* 实设备标识符 */
loff_t i_size; /* 文件大小(字节) */
struct timespec i_atime; /* 最后访问时间 */
struct timespec i_mtime; /* 最后修改时间 */
struct timespec i_ctime; /* 最后改变时间 */
unsigned long i_blksize;/* 以字节为单位的块大小 */
unsigned long i_blocks; /* 文件的块数 */
unsigned short i_bytes; /* 使用的字节数 */
spinlock_t i_lock; /* 自旋锁 */
struct super_block *i_sb; /* 相关的超级块 */
struct file_lock *i_flock; /* 文件锁链表 */
struct pipe_inode_info *i_pipe; /* 管道信息 */
struct block_device *i_bdev; /* 块设备驱动 */
unsigned long i_state; /* 状态标志 */
unsigned int i_flags; /* 文件系统标志 */
unsigned char i_sock; /* 可能是个套接字吧 */
atomic_t i_writecount;/* 写者记数 */
...
};
inode_operations主要是对给定inode完成create、link、symlink、unlink、truncate等操作。
struct inode_operations{
/* 为dentry对象创建一个新的索引节点,mode指定初始模式 */
int (*create)(struct inode *dir, struct dentry *, int mode);
/* 在特定的目录中寻找索引节点,该索引节点对应dentry中文件名 */
struct dentry * (*lookup)(struct inode *dir,
struct dentry *);
/* 创建硬链接 */
int (*link)(struct dentry *old_dentry,
struct inode *dir, struct dentry *);
/* 从dir目录中删除dentry指定的索引节点对象 */
int (*unlink)(struct inode *dir, struct dentry *);
/* 创建符号链接,名称由symname指定 */
int (*symlink)(struct inode *dir, struct dentry *, const char *symname);
/* 创建新目录 */
int (*mkdir)(struct inode *dir, struct dentry *, int mode);
/* 删除目录 */
int (*rmdir)(struct inode *dir, struct dentry *);
/* 创建特殊文件,如设备文件、管道文件或套接字 */
int (*mknod)(struct inode *, struct dentry *, int, dev_t);
/* 移动文件,源文件在old_dir指定的目录文件中*/
/* 源文件由old_dentry目录项指定,拷贝到new_dir中 */
int (*rename)(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry);
/* 读符号链接文件内容 */
int (*readlink)(struct dentry *, char __user *buffer, int len);
/* 截断,修改文件大小 */
void (*truncate)(struct inode *);
/* 检查给定inode所代表文件是否具有特定的访问模式 */
int (*permission)(struct inode *, int mask);
...
};
如前所述,目录也是文件,在自己的文件系统有唯一的inode结构对应。而dentry就是一个临时的目录项结构,用于标示路径。如/usr/bin/ls,其中“/” ,“usr”,“bin”,“ls”都用dentry来标示。
所以,也不难理解inode_operaions中各种定义的函数。下面以创建软链接和硬链接来举例说明inode和dentry之间联系。
软链接与硬链接
首先,硬链接是多个文件引用同一个inode,这些文件存在目录文件的目录项中,这些引用都会使得inode引用计数加1,当所有目录项都不再引用该inode时,就会释放该inode以及inode管理的的数据块。所以,删除一个硬链接时,会触发unlink系统调用,如果不是最后一个删除者,那么此文件仍然存在于文件系统中。另外,inode是在当前文件系统中唯一表示一个文件的,在不同的文件系统,那么inode就不能保证是唯一的,而硬链接依靠共享同一个inode实现,所以不能跨越文件系统创建硬链接。
软链接是一个符号链接,软链接是一个特殊的文件,所以它也有自己的inode。软链接文件内容是一个文件路径,就是它链接的路径。在作查找时,是跟随软链接的,所以open一个软链接,是打开它指向的那个路径,真正需要获取软链接内容,需要readlink系统调用来实现。软链接有自己独立的inode,所以它是可以跨越文件系统而独立存在的。

2.3 目录项
对于路径中每一部分都看作是dentry对象,如/bin/ls,bin属于目录文件,ls是普通文件,他们都用inode对象表示。VFS为了方便查找操作,将“/”,“bin”,“ls”都看作是目录项对象。
struct dentry{
atomic_t d_count; /* 使用计数 */
struct inode *d_inode; /* 相关联的索引节点 */
struct hlist_node d_hash; /* 散列表 */
struct qstr d_name; /* 目录项名称 */
struct dentry *d_parent; /* 父目录的目录项对象 */
struct list_head d_subdirs; /* 子目录链表 */
struct dentry_operations *d_op; /* 目录项操作节点 */
struct super_block *d_sb; /* 文件超级块 */
unsigned char d_iname[DNAE_INLINE_LEN_MIN]; /* 短文件名 */
...
};
目录项不存在磁盘中,VFS根据字符串形式路径在内存动态创建。
struct dentry_operations{
/* 验证目录项对象是否有效 */
int (*d_revalidate)(struct dentry *, struct nameidata *);
/* 生成目录项的哈希值 */
int (*d_hash)(struct dentry *, struct qstr *);
/* 比较文件名,可能按字典序,可能不区分大小写 */
int (*d_compare)(struct dentry *, struct qstr *name1, struct qstr *name2);
/* d_count为0时被调用,删除目录项,需要加锁 */
int (*d_delete)(struct dentry *);
/* 目录项将被释放时调用 */
void (*d_release)(struct dentry *);
/* 磁盘索引节点被删除时被调用 */
void (*d_input)(struct dentry *, struct inode *);
};
目录项的存在,是便于VFS作路径查找。为了加速查找过程,VFS还是用了目录项缓存dcache,并使用hashtable缓存最近解析过的路径的目录项。方便在下次需要解析路径时,使用d_hash函数计算哈希值,然后快速在hashtable中找到匹配项。因为文件访问空间和时间的局部性,对目录项的缓存命中率概率较高。
2.4 文件
文件对象表示进程中已打开的文件,是已打开文件在内存中的表示。在使用open时被创建,在调用close时撤销。同时,多个进程可能同时打开同一个文件,所以同一个文件可能包含多个文件对象,但他们对应的dentry和inode对象是唯一的。
struct file{
struct path f_path; /* 包含目录项 */
struct file_operations *f_op;/* 文件操作方法 */
spinlock_t f_lock; /* 文件结构锁 */
atomic_t f_count; /* 使用计数 */
unsigned int f_flags; /* 打开文件指定的标志 */
mode_t f_mode; /* 访问模式 */
loff_t f_pos; /* 文件偏移量 */
...
};
文件对象和dentry对象一项,是在内存中创建的,没有对应的磁盘数据,所以不需要脏标志来判读是否需要写回磁盘。脏标志存在于inode中,可以通过f_path找到dentry,然后在找到对应inode结构。
struct file_operations{
struct module *owner;
/* 更新偏移量指针 */
loff_t (*llseek)(struct file *, loff_t offset, int);
/* 读操作 */
ssize_t (*read)(struct file *, char *buf, size_t count, loff_t offset);
/* 写操作 */
ssize_t (*write)(struct file, const char *, size_t, loff_t *);
/* 异步读 */
ssize_t (*aio_read)(struct kiocb *iocb, char *buf, size_t, loff_t);
/* 异步写 */
ssize_t (*aio_write)(struct kiocb *, const char *, size_t, loff_t);
/* 获取目录列表中的一项 */
int (*readdir)(struct file *, void *dirent, filldir_t filldir);
/* io异步,监控多个文件描述符事件 */
unsigned int (*poll)(struct file *, struct poll_table_struct *);
/* 用于向设备发送命令和传递参数 */
int (*ioctl)(struct inode *, struct file *,
unsigned int cmd, unsigned long arg);
/* 将文件映射到指定的地址空间 */
int (*map)(struct file *, struct vm_area_struct *vma);
/* 打开文件 */
int (*open)(struct inode *, struct file *);
/* 将给定文件数据写回磁盘 */
int (*fsync)(struct file *, struct dentry *, int datasync);
/* 给指定文件上锁 */
int (*lock)(struct file *, int cmd, struct file_lock *lock);
/* scatter read and write */
ssize_t (*readv)(struct file *, const struct invective *,
unsigned long count, loff_t *);
ssize_t (*writev)(struct file *, const struct invective *,
unsigned long count, loff_t *);
/* 拷贝一个文件到另一个文件,在内核态完成,减少拷贝 */
ssize_t (*sendfile)(struct file *, loff_t *, size_t size,
read_actor_t, void *target);
ssize_t (*sendpage)(struct file *, struct page *,
int offset, size_t size, loff_t *, int more);
...
};
本文深入探讨Unix文件系统,介绍VFS虚拟文件系统如何屏蔽不同文件系统的差异,以及超级块、索引节点(inode)、目录项(dentry)和文件这四大核心对象的作用和相互关系。inode存储文件元数据,目录作为一种特殊文件,通过dentry表示路径,文件对象则代表已打开的文件。
&spm=1001.2101.3001.5002&articleId=105690767&d=1&t=3&u=77dd6206475745d987a23dc1ab1ae09a)
722

被折叠的 条评论
为什么被折叠?



