Linux2.4.30内核文件系统学习(多图)1:关键数据结构!--[if!supportLists]--1.!--[endif]--概述根据以前学习内核源码的经验,在学习文件系统实现之前,我大概定了个目标:1、!--[endif]--建立一个清晰的全局概念。为将来需要研究代码细节打下坚实基础。2、!--[endif]--只研究虚拟文件系统VFS的实现,不研究具体文件系统。为什么选择Linux2.4.30?因为可以参考《Linux源码情景分析》一书,减少学习难度。!--[if!supportLists]--1.1.!--[endif]--基本概念1、!--[endif]--一块磁盘(块设备),首先要按照某种文件系统(如NTFS)格式进行格式化,然后才能在其上进行创建目录、保存文件等操作。在Linux中,有“安装”文件系统和“卸载”文件系统的概念。一块经过格式化的“块设备”(不管是刚刚格式化完的,没有创建任何名录和文件;还是已经创建了目录和文件),只有先被“安装”,才能融入Linux的文件系统中,用户才可以在它上面进行正常的文件操作。2、!--[endif]--Linux把目录或普通文件,统一看成“目录节点”。通常一个“目录节点”具有两个重要属性:名称以及磁盘上实际对应的数据。本文中,“目录节点”有时简称为“节点”“符号链接”是一种特殊的目录节点,它只有一个名称,没有实际数据。这个名称指向一个实际的目录节点。3、!--[endif]--“接口结构”:在内核代码中,经常可以看到一种结构,其成员全部是函数指针,例如:structfile_operations{structmodule*owner;loff_t(*llseek)(structfile*,loff_t,int);ssize_t(*read)(structfile*,char*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar*,size_t,loff_t*);int(*readdir)(structfile*,void*,filldir_t);unsignedint(*poll)(structfile*,structpoll_table_struct*);int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);int(*mmap)(structfile*,structvm_area_struct*);int(*open)(structinode*,structfile*);int(*flush)(structfile*);int(*release)(structinode*,structfile*);int(*fsync)(structfile*,structdentry*,intdatasync);int(*fasync)(int,structfile*,int);int(*lock)(structfile*,int,structfile_lock*);ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);};这种结构的作用类似与C++中的“接口类”,它是用C语言进行软件抽象设计时最重要的工具。通过它,将一组通用的操作抽象出来,核心的代码只针对这种“接口结构”进行操作,而这些函数的具体实现由不同的“子类”去完成。以这个file_operations“接口”为例,它是“目录节点”提供的操作接口。不同的文件系统需要提供这些函数的具体实现。本文中,“接口结构”有时简称“接口”。!--[if!supportLists]--1.2.!--[endif]--虚拟文件系统Linux通过虚拟文件系统(VFS)来支持不同的具体的文件系统,那么VFS到底是什么?从程序员的角度看,我认为VFS就是一套代码框架(framework),它将用户与具体的文件系统隔离开来,使得用户能够通过这套框架,以统一的接口在不同的具体的文件系统上进行操作。这套框架包括:!--[if!supportLists]--1、!--[endif]--为用户提供统一的文件和目录的操作接口,如open,read,write!--[if!supportLists]--2、!--[endif]--抽象出文件系统共有的一些结构,包括“目录节点”inode、“超级块”super_block等。!--[if!supportLists]--3、!--[endif]--面向具体的文件系统,定义一系列统一的操作“接口”,如file_operations,inode_operations,dentry_operation,具体的文件系统必须提供它们的实现。!--[if!supportLists]--4、!--[endif]--提供一套机制,让具体的文件系统融入VFS框架中,包括文件系统的“注册”和“安装”!--[if!supportLists]--5、!--[endif]--实现这套框架逻辑的核心代码我对文件系统的学习,实际上就是学习虚拟文件系统这套框架是如何实现的。!--[if!supportLists]--2.!--[endif]--核心数据结构数据结构是代码的灵魂,要分析一个复杂的系统,关键是掌握那些核心的数据结构,这包括:1、!--[endif]--弄清数据结构的核心功能。一个数据结构通常具有比较复杂的成员,此外,还有一些成员用于建立数据结构之间的关系。如果要一个个去理解,就会陷入细节。2、!--[endif]--弄清数据结构之间的静态关系3、!--[endif]--弄清数据结构之间是如何建立起动态的关系的本文重点分析文件系统中的关键数据结构以及它们之间的关系。!--[if!supportLists]--2.1.!--[endif]--inode和file_operations1、!--[endif]--inode用以描述“目录节点”,它描述了一个目录节点物理上的属性,例如大小,创建时间,修改时间、uid、gid等2、!--[endif]--file_operations是“目录节点”提供的操作“接口”。它包括open,read,wirte,ioctl,llseek,mmap等操作。3、!--[endif]--一个inode通过成员i_fop对应一个file_operations4、!--[endif]--打开文件的过程就是寻找“目录节点”对应的inode的过程5、!--[endif]--文件被打开后,inode和file_operation都已经在内存中建立,file_operations的指针也已经指向了具体文件系统提供的函数,此后都文件的操作,都由这些函数来完成。例如打开了一个普通文件/root/file,其所在文件系统格式是ext2,那么,内存中结构如下:!--[if!supportLists]--2.2.!--[endif]--目录节点入口dentry本来,inode中应该包括“目录节点”的名称,但由于符号链接的存在,导致一个物理文件可能有多个文件名,因此把和“目录节点”名称相关的部分从inode中分开,放在一个专门的dentry结构中。这样:1、!--[endif]--一个dentry通过成员d_inode对应到一个inode上,寻找inode的过程变成了寻找dentry的过程。因此,dentry变得更加关键,inode常常被dentry所遮掩。可以说,dentry是文件系统中最核心的数据结构,它的身影无处不在。2、!--[endif]--由于符号链接的存在,导致多个dentry可能对应到同一个inode上例如,有一个符号链接/tmp/abc指向一个普通文件/root/file,那么dentry与inode之间的关系大致如下:d_inode‘file’dentry‘file’inoded_inode‘abc’dentryopenstructfile_operationsreadwriteioctlllseekmmapi_fop!--[if!vml]--!--[endif]--!--[if!supportLists]--2.3.!--[endif]--super_block和super_operations一个存放在磁盘上的文件系统如EXT2等,在它的格式中通常包括一个“超级块”或者“控制块”的部分,用于从整体上描述文件系统,例如文件系统的大小、是否可读可写等等。虚拟文件系统中也通过“超级块”这种概念来描述文件系统整体的信息,对应的结构是structsuper_block。super_block除了要记录文件大小、访问权限等信息外,更重要的是提供一个操作“接口”super_operations。structsuper_operations{structinode*(*alloc_inode)(structsuper_block*sb);void(*destroy_inode)(structinode*);void(*read_inode)(structinode*);void(*read_inode2)(structinode*,void*);void(*dirty_inode)(structinode*);void(*write_inode)(structinode*,int);void(*put_inode)(structinode*);void(*delete_inode)(structinode*);void(*put_super)(structsuper_block*);void(*write_super)(structsuper_block*);int(*sync_fs)(structsuper_block*);void(*write_super_lockfs)(structsuper_block*);void(*unlockfs)(structsuper_block*);int(*statfs)(structsuper_block*,structstatfs*);int(*remount_fs)(structsuper_block*,int*,char*);void(*clear_inode)(structinode*);void(*umount_begin)(structsuper_block*);structdentry*(*fh_to_dentry)(structsuper_block*sb,__u32*fh,intlen,intfhtype,intparent);int(*dentry_to_fh)(structdentry*,__u32*fh,int*lenp,intneed_parent);int(*show_options)(structseq_file*,structvfsmount*);};我们通过分析“获取一个inode”的过程来只理解这个“接口”中两个成员alloc_inode和read_inode的作用。在文件系统的操作中,经常需要获得一个“目录节点”对应的inode,这个inode有可能已经存在于内存中了,也可能还没有,需要创建一个新的in