Linux支持许多文件系统,从日志文件系统到集群文件系统和加密文件系统。对于使用标准的和比较奇特的文件系统以及开发文件系统来说,Linux是一个很好的平台。
Linux文件系统结构是一个对复杂系统进行抽象化的有趣例子。通过使用一组通用的API函数,Linux可以在许多存储设备上支持许多的文件系统。例如,read 函数调用可以从指定的文件描述符读取一定数量的字节。read 函数不了解文件系统的类型,比如 ext3 或 NFS。它也不了解文件系统所在的存储媒体,比如 AT Attachment Packet Interface(ATAPI)磁盘、Serial-Attached SCSI(SAS)磁盘或 Serial Advanced Technology Attachment(SATA)磁盘。但是,当通过调用 read 函数读取一个文件时,数据会正常返回。
Linux系统存在很多的文件系统,例如常见的ext2、ext3、sysfs、rootfs、proc…Linux 中允许众多不同的文件系统共存,如 ext2, ext3, vfat 等。通过使用同一套文件 I/O 系统 调用即可对 Linux 中的任意文件进行操作而无需考虑其所在的具体文件系统格式;更进一步,对文件的 操作可以跨文件系统而执行。如图 1 所示,我们可以使用 cp 命令从 vfat 文件系统格式的硬盘拷贝数据到 ext3 文件系统格式的硬盘;而这样的操作涉及到两个不同的文件系统。
1、Linux虚拟文件系统(VFS)
VFS概念:
VFS:Virtual File System虚拟文件系统,或Virtual File Switch虚拟文件转换/开关。VFS是Linux和UNIX文件系统中采用的一种技术机制,旨在一个操作系统中支持多个不同类型的文件系统。VFS是操作系统内核中这样一组数据结构与子程序的集合,它位于操作系统系统调用界面与具体类型文件系统之间,负责记录操作系统中可以支持和已经安装有哪些文件系统类型,将相关系统调用转换为对具体类型文件系统的调用,负责不同类型文件系统间的协同工作(例如跨FS复制),实现对不同类型文件系统的动态装卸和可扩充性等。
VFS功能:
- 向用户、应用程序、和操作系统其他部件提供了一个通用的、统一的、标准的、抽象的、虚拟的系统调用接口界面(所以称Virtual)。
- 对以上应用程序等掩盖不同类型文件系统的差异和细节。为以上应用程序等提供了对具体文件系统类型的程序独立性和透明性。
- 例如,当用户程序AP1在两次运行中分别读EXT2、NTFS文件,都使用同样的read(…)系统调用函数,程序AP1不必改变
Linux中VFS示意图:
Linux的VFS目前支持的文件系统
目前至少50多种,可从fs.h中或fs/*.c或/proc/filesystems看到:
a、本地文件系统:EXT2,EXT3,EXT4,FAT,NTFS,minix,UFS,HFS,ISOFS,HPFS,AFFS(FFS),SYSV(S5FS),EFS,UDF等。
b、网络文件系统:NFS,coda,SAMBA,等
c、虚拟文件系统:PROC,等
2、VFS数据结构:
A、VFS基本数据结构:
Linux虚拟文件系统四大对象:超级块、索引节点、目录项、文件对象。
从本质上讲,文件系统是特殊的数据分层存储结构,它包含文件、目录和相关的控制信息。为了描述 这个结构,Linux引入了一些基本概念:
文件:一组在逻辑上具有完整意义的信息项的系列。在Linux中,除了普通文件,其他诸如目录、设备、套接字等 也以文件被对待。总之,“一切皆文件”。
目录:目录好比一个文件夹,用来容纳相关文件。因为目录可以包含子目录,所以目录是可以层层嵌套,形成 文件路径。在Linux中,目录也是以一种特殊文件被对待的,所以用于文件的操作同样也可以用在目录上。
目录项:在一个文件路径中,路径中的每一部分都被称为目录项;如路径/home/source/helloworld.c中,目录 /, home, source和文件 helloworld.c都是一个目录项。
索引节点:用于存储文件的元数据的一个数据结构。文件的元数据,也就是文件的相关信息,和文件本身是两个不同 的概念。它包含的是诸如文件的大小、拥有者、创建时间、磁盘位置等和文件相关的信息。
超级块:用于存储文件系统的控制信息的数据结构。描述文件系统的状态、文件系统类型、大小、区块数、索引节 点数等,存放于磁盘的特定扇区中。
超级块:一个超级块对应一个文件系统(已经安装的文件系统类型如ext2,此处是实际的文件系统哦,不是VFS)。之前我们已经说了文件系统用于管理这些文件的数据格式和操作之类的,系统文件有系统文件自己的文件系统,同时对于不同的磁盘分区也有可以是不同的文件系统。
那么一个超级块对于一个独立的文件系统。保存文件系统的类型、大小、状态等等。
(“文件系统”和“文件系统类型”不一样!一个文件系统类型下可以包括很多文件系统即很多的super_block),即同一个文件类型下可以有多个文件系统实体(关于这句话的理解仔细理解下图可得)
既然我们知道对于不同的文件系统有不同的super_block,那么对于不同的super_block的操作肯定也是不同的,所以我们在下面的super_block结构中可以看到上面说的抽象的struct结构(例如下面的:struct super_operations)。
对于VFS我们知道,与用户打交道的是VFS,然后VFS与底层真正的文件系统交流。那我们知道在一个VFS下面,允许存在很多的“文件系统类型”,例如ext2,ext3,ext4,sysfs,proc等等。这些类型是可以共存的,同时,对于每一个类型来说,可以存在多个文件系统实体,例如:在一个目录下有多个子目录,子目录之间的文件系统类型可以不一样,也可以有部分是一样的类型,借用windows中的例子说就是,D盘和E盘可以都是NTFS类型文件系统,也可以是不一样的文件类型系统。在Linux中,系统有一个全局变量叫做file_systems,这个变量用来管理所有的“文件系统类型”链表。也就是所有的文件系统类型都必须注册到(链接到)这个链表中,才可以被使用。如果是自己的文件系统,只要符合VFS的标准,也是可以注册进去的。最终形成一个单链表形式结构。
Linux下VFS的实现细节
如上的数据结构并不是孤立存在的。正是通过它们的有机联系,VFS才能正常工作。如下的几张图是对它们之间的联系的描述。
如图5所示,被Linux支持的文件系统,都有且仅有一个file_system_type结构而不管它有零个或多个实例被安装到系统 中。每安装一个文件系统,就对应有一个超级块和安装点。超级块通过它的一个域s_type指向其对应的具体的文件系统类型。具体的 文件系统通过file_system_type中的一个域fs_supers链接具有同一种文件类型的超级块。同一种文件系统类型的超级块通过域s_instances链接。
从图6可知:进程通过task_struct中的一个域files_struct files来了解它当前所打开的文件对象;而我们通常所说的文件 描述符其实是进程打开的文件对象数组的索引值。文件对象通过域f_dentry找到它对应的dentry对象,再由dentry对象的域d_inode找 到它对应的索引结点,这样就建立了文件对象与实际的物理文件的关联。最后,还有一点很重要的是, 文件对象所对应的文件操作函数列表是通过索引结点的域i_fop得到的。图6对第三部分源码的理解起到很大的作用。
//进程表的相关内容
Struct task_struct{
…………..........
/* filesystem information */文件系统信息指针
struct fs_struct *fs;
/* open file information */进程打开的文件对象指针
struct files_struct *files;
……………....
}
//struct fs_struct
struct fs_struct
{
atomic_t count;
rwlock_t lock;
int umask;
struct dentry * root, * pwd, * altroot;
struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
};
//进程打开文件表Struct files_struct
Struct files_struct
{
atomic_t count;
rwlock_t file_lock;
int max_fds;
int max_fdset;
int next_fd;
struct file **fd;/*current fd array*/
fd_set *close_on_exec;
fd_set *open_fds;
fd_set close_on_exec_init;
fd_set open_fds_init;
struct file * fd_array[NR_OREN_DEFAULT];
}
//进程打开文件表struct file
struct file
{
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op;
atomic_t f_count;
unsigned int f_flags;
mode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
unsigned int f_uid, f_gid;
struct file_ra_state f_ra;
unsigned long f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
spinlock_t f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
};
Linux下的多进程与多文件之间的关系
对于一个进程来说,可以打开多个文件,也可以多个进程打开一个文件,对于文件来说,不变的永远是自己的inode节点,变化的仅仅是和进程直接关系的file结构。可以看一下下面的大图:
VFS的OO(面向对象的思想):
- 系统打开文件表:目录项对象(dentry 对象)
- 内存活动I结点表:inode对象
- 进程打开文件表:文件对象(file对象)
- 存超级块表:超级块对象
A、关于目录项
目录:文件是通过目录组织起来的,目录相当于文件夹,用来容纳对应的文件。/home/csj/1.c。根目录是’/’,后面的home,csj也是目录,最后的1.c表示一个程序文件,目录也可以包含子目录,目录从而形成层层嵌套,形成文件路径。
目录项:路径中的每一个部分(/,home,csj,1.c)都被称为目录条目(也即目录项)。从而得知,目录项中可以包含普通文件如1.c,故目录不等同于目录项。目录项对象:引入目录项的概念主要是出于方便查找文件的目的。一个路径的各个组成部分,不管是目录还是 普通的文件,都是一个目录项对象。如,在路径/home/source/test.c中,目录 /, home, source和文件 test.c都对应一个目录项对象。不同于前面的两个对象,目录项对象没有对应的磁盘数据结构,VFS在遍 历路径名的过程中现场将它们逐个地解析成目录项对象。
目录也即是文件(即目录文件),“一切皆是文件”是 Unix/Linux 的基本哲学之一。不仅普通的文件,目录、字符设备、块设备、 套接字等在 Unix/Linux 中都是以文件被对待;它们虽然类型不同,但是对其提供的却是同一套操作界面。因而每个文件对应一个索引节点,所以路径/home/csj/1.c中包含的目录项各对应一个索引节点(/,home,csj这三个是目录文件,1.c相当于普通文件)。
B、文件的查找
VFS是如何从文件路径名找到相应的索引节点的?
- 分析路径名,将它拆分成一个文件名序列
- 除了最后一个文件名以外,其他所有文件名必定都是目录名。
搜索的起点:
- 绝对路径:current->fs->root
- 相对路径:current->fs->pwd
首先找到起点目录的索引节点。然后在这个索引节点的目录文件中,找到第一个目录名(你要找的文件的第一个目录项)所对应的索引节点在第一个目录名的索引节点的目录文件中找到第二个目录名(文件名)所对应的索引节点。
…反复,直到文件名序列的最后一项
struct inode {
struct list_head i_hash;
struct list_head i_list;
struct list_head i_dentry;
//指向目录项链表指针,注意一个inodes可以对应多个dentry,因为一个实际的文件可能被链接到其他的文件,那么就会有另一 //个dentry,这个链表就是将所有的与本inode有关的dentry都连在一起。
struct list_head i_dirty_buffers;
struct list_head i_dirty_data_buffers;
unsigned long i_ino;
atomic_t i_count;
kdev_t i_dev;
umode_t i_mode;
unsigned int i_nlink;
uid_t i_uid;
gid_t i_gid;
kdev_t i_rdev;
loff_t i_size;
time_t i_atime;
time_t i_mtime;
time_t i_ctime;
unsigned int i_blkbits;
unsigned long i_blksize;
unsigned long i_blocks;
unsigned long i_version;
unsigned short i_bytes;
struct semaphore i_sem;
struct rw_semaphore i_alloc_sem;
struct semaphore i_zombie;
struct inode_operations *i_op;
struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct super_block *i_sb;
wait_queue_head_t i_wait;
struct file_lock *i_flock;
struct address_space *i_mapping;
struct address_space i_data;
struct dquot *i_dquot[MAXQUOTAS];
/* These three should probably be a union */
struct list_head i_devices;
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct char_device *i_cdev;
unsigned long i_dnotify_mask; /* Directory notify events */
struct dnotify_struct *i_dnotify; /* for directory notifications */
unsigned long i_state;
unsigned int i_flags;
unsigned char i_sock;
atomic_t i_writecount;
unsigned int i_attr_flags;
__u32 i_generation;
union {
struct minix_inode_info minix_i;
struct ext2_inode_info ext2_i;
struct ext3_inode_info ext3_i;
struct hpfs_inode_info hpfs_i;
struct ntfs_inode_info ntfs_i;
struct msdos_inode_info msdos_i;
struct umsdos_inode_info umsdos_i;
struct iso_inode_info isofs_i;
struct nfs_inode_info nfs_i;
struct sysv_inode_info sysv_i;
struct affs_inode_info affs_i;
struct ufs_inode_info ufs_i;
struct efs_inode_info efs_i;
struct romfs_inode_info romfs_i;
struct shmem_inode_info shmem_i;
struct coda_inode_info coda_i;
struct smb_inode_info smbfs_i;
struct hfs_inode_info hfs_i;
struct adfs_inode_info adfs_i;
struct qnx4_inode_info qnx4_i;
struct reiserfs_inode_info reiserfs_i;
struct bfs_inode_info bfs_i;
struct udf_inode_info udf_i;
struct ncp_inode_info ncpfs_i;
struct proc_inode_info proc_i;
struct socket socket_i;
struct usbdev_inode_info usbdev_i;
struct jffs2_inode_info jffs2_i;
void *generic_ip;
} u;
}
//=>目录项:目录项是描述文件的逻辑属性,只存在于内存中,并没有实际对应的磁盘上的描述,更确切的说是存在于内存的目录项缓存,为了提高查找性能而设计。注意不管是文件夹还是最终的文件,都是属于目录项,所有的目录项在一起构成一颗庞大的目录树。例如:open一个文件/home/xxx/yyy.txt,那么/、home、xxx、yyy.txt都是一个目录项,VFS在查找的时候,根据一层一层的目录项找到对应的每个目录项的inode,那么沿着目录项进行操作就可以找到最终的文件。
//注意:目录也是一种文件(所以也存在对应的inode)。打开目录,实际上就是打开目录文件。