一个操作系统最重要的两个部件是进程管理和文件系统。大部分嵌入式OS都只有进程管理,Linux进程调度管理在文章“Linux OpenGL学习笔记2“中,对其发展历史有过简单介绍。这篇文章主要研究Linux的文件系统EXT4几个重要数据结构,以及通过代码分析弄清楚EXT4如何从路径名找到目标文件。后续文章将在此基础上继续研究文件操作。
首先对EXT4文件系统的一些基本概念做个介绍。
如果武断一点说,文件系统本质是构建在块设备上的数据库,文件名是检索数据库中数据的一种手段。EXT4格式的存储设备上除了引导块和超级块以外,还有索引节点(index node,代码中用作inode)和数据块。如图所示
其中引导块占据第一个记录块,超级块占据第二个记录块,超级块记录了诸如该硬盘(或分区,每个分区都是一个逻辑硬盘)上总的block数目,总的inode数目,第一个数据块编号,块大小,挂载次数等等各种信息。超级块的格式是固定的,系统初始化时要将一个存储设备(通常就是从中引导出操作系统image的那个设备)作为整个系统的根设备,它的根目录就是整个文件系统的根,即总所周知的“/”。有了根目录,才能依次把其他文件mount到根目录下的空闲目录上。刚开始从一个设备上读入超级块时,首先在内存中建立一个super_block数据结构,由于文件系统初期只有“/”是空闲,因此只能将根设备的根目录安装于此。在给定一个路径的情况下,可以从“/”依次向下查找,这样很麻烦,因此才有了pwd这个数据结构代表进程当前所在目录,并提供了一种使用相对目录来查找的方法。Dentry数据结构中有成员struct dentry *d_parent表示当前目录的父目录(即shell中的”../”)的dentry,同时struct list_head d_child还记录的当前目录的同级目录链表,以及struct list_head d_subdirs表示当前目录的子目录链表。
但实际为了效率考虑,将数据和对应索引节点放在一起组成一个个“记录块”,同时又将记录块分组,组成“记录块组”,因此实际上EXT4格式的每个硬盘或分区有group descriptor table来组织记录块组,为了方便搜索还提供inode和block的bitmap,如图所示
硬盘上的inode保存着硬盘的原始信息,代码中成为raw_inode,部分成员如图所示
其中i_data[15]对于有存储内容的文件,如普通文件和目录文件而言,存放着一些指针,直接或者间接的指向文件内容的记录块。对于没有内容的文件,比如符号链接节点,它用来存放链接目标的文件名。
在正式开始研究代码之前,还需要弄清楚EXT4中两个最重要的数据结构:dentry和inode(不同于raw_inode,inode包含了一部分VFS信息)。
首先要明白,文件系统与进程密不可分,文件系统的使用者正是进程。进程与文件系统的连接是“已打开的文件“,进程最重要的数据结构struct task_struck中文件系统相关的成员如图所示
其中structfs_struct是关于文件系统的信息,struct files_struct表示已经打开的文件信息。首先看下struct fs_struct,如图所示
path中包含两个数据结构:vfsmount和dentry,vfsmount记录挂载情况,dentry记录文件的各项属性如文件名,访问权限等,一个文件只有一个dentry,但是却可以被多个进程打开或被一个进程多次打开。而path的实体root代表本进程的根目录,通常是安装于/下的EXT4文件系统,实体pwd代表进程当前所在目录。
再看下struct files_struct,如图所示
每当进程使用fd=fopen()系统调用打开一个文件时,该进程就通过打开文件号fd来访问这文件。Fd实际是structfiles_struct在链表中的位置。
除dentry外,每个文件还有一个inode数据结构记录文件在存储介质上的位置和分布信息。Dentry成员如图所示
其中d_inode即inode数据结构。Dentry和inode有联系也有区别,由于一个文件可以有几个文件名,而通过不同的文件名访问同一个文件,其访问权限也可能不同,所以dentry和inode是一对多的关系。Dentry用来描述文件的逻辑信息,inode用来描述文件的物理信息,仅此一份。可以预见,dentry最终要和inode相互关联起来,dentry才有意义。Linux文件系统逻辑结构图如图所示
inode数据结构非常重要,成员如图所示