由于Microsoft公司的Windows操作系统占据了桌面操作系统绝大多数份额,而手持智能设备与PC机的数据交换又在所难免,因此,绝大多数的大容量嵌入式智能设备必须采用与PC机兼容的FAT/FAT32文件系统。随着便携式硬盘的应用,FAT32在嵌入式硬盘上已成为主流的格式。
同时,随着CPU处理能力的提高,面向存储的应用需求在手持智能设备上也随着不断增长,文件系统的访问性能将是未来的手持设备非常关键的因素。然而,当硬盘在手持智能设备上应用时,由于硬盘访问的高耗能特性,对于手持设备的设计构成了极大的挑战。而硬盘的能耗又与读写访问的时间成正比,从节能的角度出发,系统设计者同样希望在单位时间内读取更多内容,以减少硬盘访问时间从而达到节能的目的。因此,在Linux上的FAT32的优化实现成为非常迫切的需求。
1 Linux中FAT32文件系统读操作分析
1.1 虚拟文件系统与FAT32[1-2]
Linux系统中的虚拟文件系统VFS(Virtual File System)是一个非常强大的机制,其设计思路是在内核中提供一个文件系统框架,包括接口函数集、管理用的数据结构以及各种缓存机制。VFS提供上下两个方面的接口,上层接口是提供给I/O系统的用户使用的,包括应用程序和内核的其他管理模块,通过该接口可使I/O系统(文件、设备、网络等)完成如打开、关闭、读、写等;下层接口是提供给真实文件系统的,VFS支持的每个真实文件系统都要通过这个接口来实现。通过这种机制,Linux将系统存在的各种真实文件系统(如EXT2/EXT3、FAT/FAT32、JFFS/JFFS2等)以及设备文件都统一到一种操作中,以此来实现系统的管理与调度。
FAT(File Allocation Table)文件系统是Microsoft公司推出的广泛使用在Dos、Windows 9X、Windows 2000以及Windows XP系统中。由于Windows系列的操作系统的普及,其FAT文件系统被人们所广泛熟悉和应用。当前针对大容量硬盘,FAT32文件系统占据了主要的地位。在FAT32文件系统中,以下三个概念与文件的组织密切相关:
扇区(Sector): 数据存取的最小物理单位。
簇(Cluster):文件最小分配单位,与分区大小、文件系统相关。
逻辑扇区(Logic Sector):在文件系统实现中,为了优化和统一设计所定义的读写长度。
1.2 文件读在内核中的实现
以读操作为例,通过Linux系统中VFS的作用,从用户空间对FAT32的操作,系统可以抽象成从fread( )映射到内核函数do_generic_file_read( )来完成具体的文件读操作。在文件/μCLinux/linux-2.4.x/mmnommu/filemap.c中存在这个接口实现的原型。虽然这类接口并不是基本的,但正如大多数文件系统的实现,FAT32就是通过这类接口来实现文件的各种操作。
图1描述了函数do_generic_file_read( )的实现原理。从函数入口处获得目标内容的文件描述指针,从而获得文件入口。通过分析描述符inode以及当前状态,系统获得预读read_ahead的大小,进行相应的计算,获得所需要获取的目标内容Page页索引以及offset偏移量。然后发起预读的指令,并等待获得相应的Page内容后,将其拷贝到buffer中进行组织,并提供上层程序磁盘文件在内存中的映像。
1.3 文件预读机制与Page读[1-4]
在do_generic_file_read的实现中,磁盘读动作实际是在预读read_ahead中完成的,即预读机制。这是由于Linux系统为了获得更高的性能以及充分利用CPU处理能力,VFS设计中做了一层buffer/cache缓冲。当系统发现buffer/cache中有即将要访问的内容缺失时,系统将发起一次预读请求。下层文件系统根据寻找CPU以及总线的空闲状态,执行具体的预读机制。这样,上下层构成一个异步过程来完成系统的任务,以达到充分利用系统资源的目的。
在考察read_ahead( )的实现中可以发现,实际上read_ahead( )函数的主要功能是根据实际需求不断调用文件系统中的readpage( )函数来完成的。这是由于Linux的内存管理都是按照页(Page)模式进行组织的。也就是说,每次从具体的对象数据存储设备(如硬盘)上读取相应的数据时,将严格按照page的大小进行读取动作。根据一般定义,Page采用4 096B为单位。在Linux上的FAT32实现中,将由fat_readpage( )具体应用实例来实现这个功能。
1.4 Block读实现[3-4]
由于不同的硬件设备存在不同的物理结构,在文件系统格式化时,最基本的存储单元Cluster的大小是不同的。如通常能够见到的有512B、1KB等。也就是说,实际文件的存储是按照不同的目标存储设备划分为不同的块来存储的。在文件系统实现中,为了兼容不同的目标系统与硬件设备,在FAT文件系统中的Page读动作的实现中,引入了一个Block概念,即根据具体文件描述,按照Block大小完成整个Page的读命令。
在μCLinux/linux-2.4.x/mmnommu/filemap.c文件中,fat_readpage( )的实现就是根据上述目标进行相应设计的,即通过inode获取相应文件的具体存储信息,然后将Page读转化为按照Block块方式进行读操作。也就是通过反复调用block_read_full_page( )函数来满足最后Page内容的获取。
函数block_read_full_page( )的具体实现过程如图2所示。系统根据传入的参数,获得Block大小,生成相应的缓存空间,然后反复发出Block读的Request,直到完成整个Page的读任务。
如图2所示Block_read_full_page( )的实现机理中,最重要的是根据系统状况,经过计算确切地获得将由多少个Block来组成一个Page。
在Linux实现中,Block大小决定于文件描述符inode中的i_blkbits域。在Linux中的FAT32文件系统设计中,inode->i_blkbits是由FAT32系统中的logic_sector_size决定的,即用/linux-2.4.x/fs/fat/inode.c来实现从FAT32文件系统映射到Linux的inode各项定义。
1.5 系统MAKE_REQUEST[1-4]
经过上述各个步骤的计算,在文件系统实现中,将文件读操作转化为若干个不同的Block读需求,最后向下层驱动程序层发起具体的命令Request。上述的转化,基本上是根据底层配置以及内存管理的需求,将大的/整体的命令细分/拆分为更加细小的动作。
而在实际执行过程中,肯定存在较多的过度拆分的情况,以致于产生过多低效率的命令,因此,在具体实现过程中,为了避免这种情况,在实际发出Request之前,需要对其进行相应的检查,合并相关的Request,以提高系统实现性能。这个过程将由submit_bh来完成。
图3所示是submit_bh函数中的主体调用子函数_make_request的实现过程。在FAT32实现中,_make_request根据获得的Block大小、存储设备的sector number,准备好内存空间后,向IDE发出具体的Request。而具体的Request合并将发生在发出Request之前。其实现原理根据当前队列中Request的地址相关性来判断。
由于Microsoft公司的Windows操作系统占据了桌面操作系统绝大多数份额,而手持智能设备与PC机的数据交换又在所难免,因此,绝大多数的大容量嵌入式智能设备必须采用与PC机兼容的FAT/FAT32文件系统。随着便携式硬盘的应用,FAT32在嵌入式硬盘上已成为主流的格式。
同时,随着CPU处理能力的提高,面向存储的应用需求在手持智能设备上也随着不断增长,文件系统的访问性能将是未来的手持设备非常关键的因素。然而,当硬盘在手持智能设备上应用时,由于硬盘访问的高耗能特性,对于手持设备的设计构成了极大的挑战。而硬盘的能耗又与读写访问的时间成正比,从节能的角度出发,系统设计者同样希望在单位时间内读取更多内容,以减少硬盘访问时间从而达到节能的目的。因此,在Linux上的FAT32的优化实现成为非常迫切的需求。
1 Linux中FAT32文件系统读操作分析
1.1 虚拟文件系统与FAT32[1-2]
Linux系统中的虚拟文件系统VFS(Virtual File System)是一个非常强大的机制,其设计思路是在内核中提供一个文件系统框架,包括接口函数集、管理用的数据结构以及各种缓存机制。VFS提供上下两个方面的接口,上层接口是提供给I/O系统的用户使用的,包括应用程序和内核的其他管理模块,通过该接口可使I/O系统(文件、设备、网络等)完成如打开、关闭、读、写等;下层接口是提供给真实文件系统的,VFS支持的每个真实文件系统都要通过这个接口来实现。通过这种机制,Linux将系统存在的各种真实文件系统(如EXT2/EXT3、FAT/FAT32、JFFS/JFFS2等)以及设备文件都统一到一种操作中,以此来实现系统的管理与调度。
FAT(File Allocation Table)文件系统是Microsoft公司推出的广泛使用在Dos、Windows 9X、Windows 2000以及Windows XP系统中。由于Windows系列的操作系统的普及,其FAT文件系统被人们所广泛熟悉和应用。当前针对大容量硬盘,FAT32文件系统占据了主要的地位。在FAT32文件系统中,以下三个概念与文件的组织密切相关:
扇区(Sector): 数据存取的最小物理单位。
簇(Cluster):文件最小分配单位,与分区大小、文件系统相关。
逻辑扇区(Logic Sector):在文件系统实现中,为了优化和统一设计所定义的读写长度。
1.2 文件读在内核中的实现
以读操作为例,通过Linux系统中VFS的作用,从用户空间对FAT32的操作,系统可以抽象成从fread( )映射到内核函数do_generic_file_read( )来完成具体的文件读操作。在文件/μCLinux/linux-2.4.x/mmnommu/filemap.c中存在这个接口实现的原型。虽然这类接口并不是基本的,但正如大多数文件系统的实现,FAT32就是通过这类接口来实现文件的各种操作。
图1描述了函数do_generic_file_read( )的实现原理。从函数入口处获得目标内容的文件描述指针,从而获得文件入口。通过分析描述符inode以及当前状态,系统获得预读read_ahead的大小,进行相应的计算,获得所需要获取的目标内容Page页索引以及offset偏移量。然后发起预读的指令,并等待获得相应的Page内容后,将其拷贝到buffer中进行组织,并提供上层程序磁盘文件在内存中的映像。
1.3 文件预读机制与Page读[1-4]
在do_generic_file_read的实现中,磁盘读动作实际是在预读read_ahead中完成的,即预读机制。这是由于Linux系统为了获得更高的性能以及充分利用CPU处理能力,VFS设计中做了一层buffer/cache缓冲。当系统发现buffer/cache中有即将要访问的内容缺失时,系统将发起一次预读请求。下层文件系统根据寻找CPU以及总线的空闲状态,执行具体的预读机制。这样,上下层构成一个异步过程来完成系统的任务,以达到充分利用系统资源的目的。
在考察read_ahead( )的实现中可以发现,实际上read_ahead( )函数的主要功能是根据实际需求不断调用文件系统中的readpage( )函数来完成的。这是由于Linux的内存管理都是按照页(Page)模式进行组织的。也就是说,每次从具体的对象数据存储设备(如硬盘)上读取相应的数据时,将严格按照page的大小进行读取动作。根据一般定义,Page采用4 096B为单位。在Linux上的FAT32实现中,将由fat_readpage( )具体应用实例来实现这个功能。
1.4 Block读实现[3-4]
由于不同的硬件设备存在不同的物理结构,在文件系统格式化时,最基本的存储单元Cluster的大小是不同的。如通常能够见到的有512B、1KB等。也就是说,实际文件的存储是按照不同的目标存储设备划分为不同的块来存储的。在文件系统实现中,为了兼容不同的目标系统与硬件设备,在FAT文件系统中的Page读动作的实现中,引入了一个Block概念,即根据具体文件描述,按照Block大小完成整个Page的读命令。
在μCLinux/linux-2.4.x/mmnommu/filemap.c文件中,fat_readpage( )的实现就是根据上述目标进行相应设计的,即通过inode获取相应文件的具体存储信息,然后将Page读转化为按照Block块方式进行读操作。也就是通过反复调用block_read_full_page( )函数来满足最后Page内容的获取。
函数block_read_full_page( )的具体实现过程如图2所示。系统根据传入的参数,获得Block大小,生成相应的缓存空间,然后反复发出Block读的Request,直到完成整个Page的读任务。
如图2所示Block_read_full_page( )的实现机理中,最重要的是根据系统状况,经过计算确切地获得将由多少个Block来组成一个Page。
在Linux实现中,Block大小决定于文件描述符inode中的i_blkbits域。在Linux中的FAT32文件系统设计中,inode->i_blkbits是由FAT32系统中的logic_sector_size决定的,即用/linux-2.4.x/fs/fat/inode.c来实现从FAT32文件系统映射到Linux的inode各项定义。
1.5 系统MAKE_REQUEST[1-4]
经过上述各个步骤的计算,在文件系统实现中,将文件读操作转化为若干个不同的Block读需求,最后向下层驱动程序层发起具体的命令Request。上述的转化,基本上是根据底层配置以及内存管理的需求,将大的/整体的命令细分/拆分为更加细小的动作。
而在实际执行过程中,肯定存在较多的过度拆分的情况,以致于产生过多低效率的命令,因此,在具体实现过程中,为了避免这种情况,在实际发出Request之前,需要对其进行相应的检查,合并相关的Request,以提高系统实现性能。这个过程将由submit_bh来完成。
图3所示是submit_bh函数中的主体调用子函数_make_request的实现过程。在FAT32实现中,_make_request根据获得的Block大小、存储设备的sector number,准备好内存空间后,向IDE发出具体的Request。而具体的Request合并将发生在发出Request之前。其实现原理根据当前队列中Request的地址相关性来判断。
举报