只要使用就成了,管他什么OS,什么CPU,万能的。
********************************* * 《ecos增值包》之FAT文件系统篇 * ********************************* ---FAT12/16/32在SD/MMC/CF/HDD等介质上的实现 2007/10/28 asdjf@163.com www.armecos.com
很多网友对实现FAT文件系统感兴趣,经常有人来信询问设计思路或者索要源码,其实,使用《ecos增值包》是不需要理会这些细节的,直接使用UNIX文件系统API接口就能在各种介质上完成FAT文件访问,不过,我们仍然愿意为《ecos增值包》正式用户提供这个设计思路文档。 《ecos增值包》支持FAT12/16/32,标准UNIX文件系统接口,各种容量的SD/MMC/CF/HDD等介质,充分考虑到了速度、效率、容量、低功耗、简单性、安全性、可靠性、扩展性、并发访问、存储介质多样等各种性能指标参数。 下面我们分四个层次逐步讲解FAT文件系统的设计思路。注意:《ecos增值包》不仅仅只支持FAT文件系统,它还支持RAMFS/ROMFS/JFFS2/Yaffs/TrueFFS等,所以下面介绍的内容是高度抽象的,不仅仅只适合于FAT文件系统。 1、首先,我们介绍块设备驱动的特点和实现,抽象出SD/MMC/CF/HDD等块设备共有的特性,设计出适合现在及未来任何介质的万能块设备驱动,进而实现FAT over anything。 2、接着讨论高速块设备驱动,这是体现块设备性能的关键指标。设计思路很简单,就是提供一个cache,读写数据尽量在高速cache中完成,必要时才读写真正的慢速块设备。这里用到了链表、HASH映射、排序二叉树等复杂的数据结构。 3、有了高速的块设备驱动,文件系统的性能就有了物质保障,但是恰当地选择下层和上层数据结构,能进一步将性能发挥到极致。这里给出了块设备、磁盘、目录、节点等的数据结构描述。 4、准备好了FAT相关的数据结构,接着就可以提供操作这些数据结构的函数,当然这是完全符合UNIX文件系统API接口的函数集合。用户可以使用简单统一的mount/open/read/write/close等API函数操作各种块设备。 最后,我们说明UNIX相对DOS文件系统的优势并举例说明UNIX文件系统的使用方法。 ---------- 块设备驱动 ---------- SD卡、MMC卡、CF卡、硬盘等大容量存储设备虽然介质形态不同,但都可以抽象出共同的特征。一般,此类设备以数据块方式被访问,工作过程中可能被移动改变。比如:动态插入、移除、改变设备,这就要求OS能够检测出设备是否在位,是否被删除,是否被更换成另一个设备。此外,有些设备提供了电源开关,在不需要使用时可以关闭电源以节省能量。有些设备涉及机械运动,如:硬盘磁头移动,这时采用一些软件算法可以大大提高性能,如:电梯排队算法,聚集操作等。还有,通过DMA + 中断等硬件加速方式可以进一步提高吞吐效率。ecos比Linux设备驱动强在没有复杂I/O结构,不涉及特权模式,可以直接操作设备,这样代码大小和读写效率都比较好。 经过抽象,所有块设备驱动都可以总结为下面几种操作:读read、写write、设备自身属性操作ioctl(如弹出/关闭光盘托盘等)、初始化init、查找/枚举设备lookup。不同的设备操作方法不尽相同,如SD卡通过SPI总线访问设备而CF卡通过ATA接口访问,但是经过驱动程序抽象后,上层软件将看到统一的设备接口,所以不论硬盘、光盘、SD卡、CF卡,经过设备驱动抽象后对高层软件来说都是一样的。 SD/MMC/CF/HDD/CDROM/DVDROM等介质的设备驱动详见各相关部分的描述,下面给出一个IDE硬盘接口函数的例子(其他介质与此类似,函数内部操作替换成特定介质要求的操作,对外接口不变,都是下面这个样子):
static cyg_bool ide_disk_init(struct cyg_devtab_entry *tab);
static Cyg_ErrNo ide_disk_read(disk_channel *chan, void *buf, cyg_uint32 len, cyg_uint32 block_num);
static Cyg_ErrNo ide_disk_write(disk_channel *chan, const void *buf, cyg_uint32 len, cyg_uint32 block_num);
static Cyg_ErrNo ide_disk_get_config(disk_channel *chan, cyg_uint32 key, const void *xbuf, cyg_uint32 *len);
static Cyg_ErrNo ide_disk_set_config(disk_channel *chan, cyg_uint32 key, const void *xbuf, cyg_uint32 *len);
static Cyg_ErrNo ide_disk_lookup(struct cyg_devtab_entry **tab, struct cyg_devtab_entry *sub_tab, const char *name);
-------------- 高速块设备驱动 -------------- 仅有块设备驱动还是远远不够的,因为这些大容量存储设备相对CPU而言都是慢速设备,如果不采取任何措施直接访问,那么效果会非常不令人满意。更何况现代的虚拟存储系统会把内存中暂时不需要的数据转移到硬盘上,从而模拟出一个比实际大得多的内存,所以,块设备驱动不仅与文件系统性能有关,还和OS虚拟内存性能有关。如何实现高速大容量数据访问呢?一个非常有效的办法就是通过内存提供一块CACHE暂存数据,读写数据尽量在高速cache中完成,必要时才读写真正的慢速大容量块设备。这种方法存在一定的风险,如果在数据真正写入设备前掉电,那么cache缓存里的数据将丢失。 上面笼统地说到要缓存数据,但是何时缓存,怎么缓存,切入点在什么地方并没有说清楚。其实具体到FAT文件系统里,就是要缓存FAT表数据、文件目录和文件数据。其他一些数据,如引导信息等在设备枚举初始化后就被存到相应的设备信息结构体里了,以后再访问这些数据直接从这些结构体里提取即可,不需要每次都重新读设备。 先说说FAT表是如何缓存的吧。一般来说FAT表大小不固定,FAT表数量也可以有多个(用于冗余备份,提高安全性),而且操作频繁,属于关键数据,但是如果一次将整个FAT表缓存,首先不知道整个FAT表有多大,数量有多少,即使知道这些,内存可能也装不下整个FAT表,况且只有少部分FAT信息被用到,那么怎么缓存FAT表才是最佳的呢?办法就是只缓存用到的一小块FAT信息,把整个FAT表打散成一个个小碎片,用到哪块就缓存哪块。问题又来了,你是如何知道某个小碎片对应哪个FAT表片段啊?如果内存不够用了该怎么办啊? 呵呵,这就要用到一系列的复杂数据结构了。FAT表里使用“簇”来描述信息,而内存中使用内存块来表述,所以,首先要写一个“簇号<-->块号”的互换函数,使得FAT信息和内存块对应起来。然后把缓存的FAT碎片用链表连接起来,以后每次读写FAT信息时,先在这个链表里查找该FAT信息是否已经被缓存,判断方法是把簇号转换成块号,在链表里搜索有无相同块号的内存块,如果有相同块号的内存块,就说明此FAT片段先前已经被缓存。与此同时,把该块内存块移动到链表头部,根据临近相关原理,下次被搜索的概率最大,因为离头部最近,所以可以提高搜索效率和命中率。如果内存不够用了,那么首先释放链表末尾的数据块,因为那个位置的内存块意味着最长时间没有被访问过。如果事先将各个内存块连成排序二叉树,那么查找速度还会提升。任何写入的FAT信息将被同时写入多个FAT表里。 说完FAT表这个最关键的数据缓存,接着就要说说文件目录的缓存。和通过簇号信息访问FAT表不同,目录是通过路径名访问的,那么如何通过路径名得知目录是否被缓存呢?ecos文件系统和UNIX文件系统一样,把文件和目录抽象成节点node,在node里保存相关信息。从文件名映射到节点的最好办法就是HASH映射。
static unsigned int hash_fn(const char *str, unsigned int strlen) { unsigned int i = 0, val = 0;
while (i++ < strlen) val = (val ^ (int)toupper(*str++)) << 1; return(val); }
通过上面提供的HASH算法,给出文件路径名和其长度,立即可以得到节点号。如:node = hash_fn(filename, filenamelen)。HASH算法可能产生碰撞,此时需进一步调整,不过总体而言,映射效率很高。文件名比较时先比较长度再比较文件名字串,这样速度快。通过node_hash_find()函数可以立即判断目录是否已在缓存里。 最后说下文件数据缓存。FAT文件对应簇号,只要根据簇号找到内存块就可以找到数据缓存,和FAT表的缓存原理一样。 读数据时,先判断cache缓存里是否已存在,如果存在就直接读取数据,否则,从真实设备中读取数据并缓存; 写数据时,直接写入cache中,没有就创建一个内存缓存块,当释放缓存时,数据写入实际设备中。只要在缓存链表里的数据肯定都是“脏”的。 通过缓存FAT表片段、文件目录项、文件数据,大容量慢速存储设备就被虚拟成了一个高速的块设备。 ----------------------- FAT文件系统相关数据结构 ----------------------- 有了高速的块设备驱动,接下来就要在上面实现FAT文件系统,这时,看到的块设备就是统一的FAT格式,FAT文件系统的本意也就是在块设备上以FAT格式组织文件信息。FAT文件系统的操作对象就是一堆数据结构。 首先介绍一个宏定义:DISK_INSTANCE(_number_,_port_,_chan_,_mbr_supp_,_name_) 不同的介质支持的FAT结构略有不同,如软盘和CF卡不支持MBR而SD卡和硬盘就支持。有些设备格式化为几个逻辑分区,每个分区都要用一个名字对应。这个宏就是用来通知OS介质支持的具体FAT格式的。如:
DISK_INSTANCE(0, 0, 0, true, "/dev/hda/"); DISK_INSTANCE(1, 0, 1, true, "/dev/hdb/"); DISK_INSTANCE(2, 1, 0, true, "/dev/hdc/"); DISK_INSTANCE(3, 1, 1, true, "/dev/hdd/");
就表示支持4个硬盘,IDE0的主从,IDE1的主从,均支持MBR,其设备名后缀依次为a、b、c、d。 文件目录项结构体 typedef struct fatfs_dir_entry_s { char filename[12+1]; // File name mode_t mode; // Node type size_t size; // Size of file in bytes time_t ctime; // Creation timestamp time_t atime; // Last access timestamp time_t mtime; // Last write timestamp cyg_uint8 priv_data; // Private data cyg_uint32 cluster; // First cluster number cyg_uint32 parent_cluster; // First cluster of parent dentry fatfs_data_pos_t disk_pos; // Position of dir entry on disk #ifdef CYGCFG_FS_FAT_USE_ATTRIBUTES cyg_fs_attrib_t attrib; // Attribute bits for DOS compatability #endif //CYGCFG_FS_FAT_USE_ATTRIBUTES } fatfs_dir_entry_t;
存储设备全局结构体 typedef struct fatfs_disk_s { cyg_uint32 sector_size; // Sector size in bytes cyg_uint32 sector_size_log2; // Sector size log2 cyg_uint32 cluster_size; // Cluster size in bytes cyg_uint32 cluster_size_log2; // Cluster size log2 cyg_uint32 fat_tbl_pos; // Position of the first FAT table cyg_uint32 fat_tbl_size; // FAT table size in bytes cyg_uint32 fat_tbl_nents; // Number of entries in FAT table cyg_uint32 fat_tbls_num; // Number of FAT tables cyg_uint32 fat_root_dir_pos; // Position of the root dir cyg_uint32 fat_root_dir_size; // Root dir size in bytes cyg_uint32 fat_root_dir_nents; // Max number of entries in root dir cyg_uint32 fat_root_dir_cluster; // Cluster number of root dir (FAT32) cyg_uint32 fat_data_pos; // Position of data area fatfs_type_t fat_type; // Type of FAT - 12, 16 or 32 cyg_io_handle_t dev_h; // Disk device handle fatfs_node_t *root; // Root dir node
cyg_uint8 *bcache_mem; // Block cache memory base cyg_blib_t blib; // Block cache and access library instance
fatfs_node_t node_pool_base[FATFS_NODE_POOL_SIZE]; // Node pool base fatfs_node_t *node_pool[FATFS_NODE_POOL_SIZE]; // Node pool cyg_uint32 node_pool_free_cnt; // Node pool free cnt fatfs_node_list_t live_nlist; // List of nodes with refcnt > 0 fatfs_node_list_t dead_nlist; // List of nodes with refcnt == 0 fatfs_hash_table_t node_hash; // Hash of nodes in live and dead lists } fatfs_disk_t;
上面论述的是FAT文件系统最重要的几个内部结构体,原始的FAT结构在相关标准参考里有详细描述,这里不再赘述。 ---------------- UNIX文件系统接口 ---------------- 上面提供了FAT文件系统的数据结构,下面提供操作这些数据结构的接口函数,它们都符合UNIX标准。
// 文件系统操作Filesystem operations static int fatfs_mount (cyg_fstab_entry *fste, cyg_mtab_entry *mte); static int fatfs_umount (cyg_mtab_entry *mte); static int fatfs_open (cyg_mtab_entry *mte, cyg_dir dir, const char *name, int mode, cyg_file *fte); static int fatfs_unlink (cyg_mtab_entry *mte, cyg_dir dir, const char *name); static int fatfs_mkdir (cyg_mtab_entry *mte, cyg_dir dir, const char *name); static int fatfs_rmdir (cyg_mtab_entry *mte, cyg_dir dir, const char *name); static int fatfs_rename (cyg_mtab_entry *mte, cyg_dir dir1, const char *name1, cyg_dir dir2, const char *name2 ); static int fatfs_link (cyg_mtab_entry *mte, cyg_dir dir1, const char *name1, cyg_dir dir2, const char *name2, int type); static int fatfs_opendir(cyg_mtab_entry *mte, cyg_dir dir, const char *name, cyg_file *fte ); static int fatfs_chdir (cyg_mtab_entry *mte, cyg_dir dir, const char *name, cyg_dir *dir_out ); static int fatfs_stat (cyg_mtab_entry *mte, cyg_dir dir, const char *name, struct stat *buf); static int fatfs_getinfo(cyg_mtab_entry *mte, cyg_dir dir, const char *name, int key, void *buf, int len ); static int fatfs_setinfo(cyg_mtab_entry *mte, cyg_dir dir, const char *name, int key, void *buf, int len );
// 文件操作File operations static int fatfs_fo_read (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio); static int fatfs_fo_write (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio); static int fatfs_fo_lseek (struct CYG_FILE_TAG *fp, off_t *pos, int whence ); static int fatfs_fo_ioctl (struct CYG_FILE_TAG *fp, CYG_ADDRWORD com, CYG_ADDRWORD data); static int fatfs_fo_fsync (struct CYG_FILE_TAG *fp, int mode ); static int fatfs_fo_close (struct CYG_FILE_TAG *fp); static int fatfs_fo_fstat (struct CYG_FILE_TAG *fp, struct stat *buf ); static int fatfs_fo_getinfo(struct CYG_FILE_TAG *fp, int key, void *buf, int len ); static int fatfs_fo_setinfo(struct CYG_FILE_TAG *fp, int key, void *buf, int len );
// 目录操作Directory operations static int fatfs_fo_dirread (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio); static int fatfs_fo_dirlseek(struct CYG_FILE_TAG *fp, off_t *pos, int whence);
依次完成上面的各个函数就可以实现FAT文件系统。除了这些内容,还需要一些辅助函数,如:UNIX-DOS时间格式转换、FAT簇操作、FAT项类型判断等。 ------------------------- UNIX相对DOS文件系统的优势 ------------------------- 1、标准的文件系统API接口,Linux里的大量现成源码能很容易地移植到ecos系统中; 2、信息隐藏得比较好。挂装操作可以把任何介质上的任何文件系统挂装到任何目录,用户看到的只是统一的目录,不会感觉到介质差异,有利于编程操作。而DOS文件系统要指定驱动器,暴露了底层细节,程序移植性差。 3、支持多任务并发。DOS是单任务的,独占系统,本质上不支持多任务。UNIX天然支持多用户多任务并发访问。 -------------------- 如何使用ecos文件系统 -------------------- 1、挂装 文件系统使用前需要挂装操作,即把某介质上的某文件系统挂装到某目录,函数如下所示: mount(设备名,挂装目录,文件系统名),如: 挂装RAM文件系统,设备名恒为空,挂装在根目录: err = mount( "", "/", "ramfs" ); 挂装FAT文件系统,设备名为/dev/hda/硬盘,挂装在根目录: err = mount( "/dev/hda/", "/", "fatfs" ); //hda后一定要加“/”,否则挂不上 2、打开 访问任何文件前先按某种属性打开并获得句柄。 open(文件名,打开属性) 以只读方式打开文件filename,返回句柄fd: fd = open(filename, O_RDONLY); 3、读写 (文件句柄,缓冲区,长度),返回实际读出/写入的数据长度 len = read(fd, buf, len); len = write(fd, buf, len); 4、关闭 关闭文件,导致缓存数据实际写入存储设备。 close(文件句柄) close(fd); 5、卸载 卸载整个文件系统,导致缓存数据实际写入存储设备。 err = umount(挂装目录); err = umount( "/" ); 由上可见,《ecos增值包》文件系统的使用是相当简单容易的,www.armecos.com免费下载提供bin演示程序,有EASYARM2200或者SMARTARM2200开发板并对文件系统感兴趣的网友,可以尝试使用,该演示支持ROMFS/RAMFS/CF/SD/等介质,使用上面介绍的文件系统进行开发。除此之外,《ecos增值包》还提供其他文件系统支持,各种bootloader,多种完整TCP/IP协议栈,USB、GUI、VxWorks等内容,一揽子解决嵌入式开发遇到的各种问题。 |