对于软件分析,特别是复杂的大型系统软件来说我个人的步骤一般是:
1、 了解设计者的设计思想,用行话说就是软件架构、层次、模块组成。
2、 根据软件结构和模块整理出主要的数据结构或者类。
3、 结合数据结构进一步熟悉软件架构、层次和模块构成。对于复杂系统,这个过程花的时间可能会很长。直到你能画出整个系统的架构图。在你画出架构图之间一般不要去深入分析源码细节,这很容易迷失在代码中。
4、 根据你画的架构图熟悉各个模块,这个步骤可以有选择性的去熟悉你感兴趣的模块。如果模块还很复杂很大的话你需要重复前面三个步骤,直到你可以很轻松的去了解模块里面的细节,也就是读源码。
下面我们就按照以上四个步骤来分析FAT文件系统
网上关于FAT文件系统结构组成的文章很多,由4个部分组成:引导扇区、FAT表、根目录区、数据区
现在先粗略讲解下前面3个部分。
引导扇区:
扇区大小这里设为512byte,磁盘的第一个扇区作为引导扇区,引导扇区的内容主要包括操作系统引导信息、磁盘基本信息、文件系统相关信息(BPB数据结构)、分区表信息(磁盘分区信息)。由于这里我们文件系统只涉及数据存储而且磁盘也不局限于硬盘,所以引导信息和磁盘基本信息不分析,反正网上相关信息很多。暂时也不涉及多分区,分区信息也不分析。我们现在主要关注文件系统信息。这个信息对应的数据结构是ff.h文件中定义的struct FATFS。
FAT表:
FAT(file access table文件访问表),记录着文件和目录所在的簇(clust)或者块(block),如果一个文件涉及多个簇则通过簇链将上下簇联系起来。我们以FAT16为例来说明FAT表项内容。这里我们定义总簇数为4096,每簇扇区数为16,每个扇区大小为512字节。每一个表项长度为2个字节,对应磁盘的一个簇。由于0号和1号簇用做引导区和FAT记录区,所以0号和1号表项不能用,2号表项对应根目录起始簇号,即根目录的起始簇为第二簇。表项标志内容:簇链结束标志:0xFFFF,保留标志:0XFFF0,坏簇标志:0XFFF7。空闲簇:0x0000。若有一个文件占用3个簇,文件起始簇号是3,FAT表如下所示:
由上表可知0簇和1簇用作引导和FAT表,2、3簇用作根目录区,这里我们定义根目录项数为512,每个目录占32个字节。文件起始簇号为4,文件占用4、5、6共3簇。这里文件占用的3个簇连续,也可能不连续,表项6为0xffff表示文件在这一簇结束。表项7、8的记录为0x0000,表示空闲。表项总数为磁盘总簇数+2。
根目录区:
根目录是所有文件和目录的入口,现在我们只要了解根目录起始簇为2,占用2个簇就行了,具体信息到目录和文件管理的时候再分析。
下面介绍以上模块对应的数据结构:
文件系统结构:
typedef struct {
BYTE fs_type; /* FAT sub-type (0:Not mounted) */
BYTE drv; /* Physical drive number */
BYTE csize; /* Sectors per cluster (1,2,4...128) */
BYTE n_fats; /* Number of FAT copies (1,2) */
BYTE wflag; /* win[] dirty flag (1:must be written back) */
BYTE fsi_flag; /* fsinfo dirty flag (1:must be written back) */
WORD id; /* File system mount ID */
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
#if _MAX_SS != 512
WORD ssize; /* Bytes per sector (512, 1024, 2048 or 4096) */
#endif
#if _FS_REENTRANT
_SYNC_t sobj; /* Identifier of sync object */
#endif
#if !_FS_READONLY
DWORD last_clust; /* Last allocated cluster */
DWORD free_clust; /* Number of free clusters */
DWORD fsi_sector; /* fsinfo sector (FAT32) */
#endif
#if _FS_RPATH
DWORD cdir; /* Current directory start cluster (0:root) */
#endif
DWORD n_fatent; /* Number of FAT entries (= number of clusters + 2) */
DWORD fsize; /* Sectors per FAT */
DWORD fatbase; /* FAT start sector */
DWORD dirbase; /* Root directory start sector (FAT32:Cluster#) */
DWORD database; /* Data start sector */
DWORD winsect; /* Current sector appearing in the win[] */
BYTE win[_MAX_SS]; /* Disk access window for Directory, FAT (and Data on tiny cfg) */
} FATFS;
磁盘格式化的时候将相关信息写入到第一簇的第一个扇区,挂载文件系统时将相关信息读到上面的文件系统结构变量中。
目录结构:
typedef struct {
FATFS* fs; /* Pointer to the owner file system object */
WORD id; /* Owner file system mount ID */
WORD index; /* Current read/write index number */
DWORD sclust; /* Table start cluster (0:Root dir) */
DWORD clust; /* Current cluster */
DWORD sect; /* Current sector */
BYTE* dir; /* Pointer to the current SFN entry in the win[] */
BYTE* fn; /* Pointer to the SFN (in/out) {file[8],ext[3],status[1]} */
#if _USE_LFN
WCHAR* lfn; /* Pointer to the LFN working buffer */
WORD lfn_idx; /* Last matched LFN index number (0xFFFF:No LFN) */
#endif
} DIR;
这个结构其实并不是目录存储结构,只是作为内存中的管理结构。目录项存储结构是32个字节长度。为了方便调试我申请了32M内存空间,分成4096簇,每簇16个扇区,每扇区512字节。用vc创建了一个工程,用内存模拟磁盘进行操作。
#define SECTOR_SIZE 512 //512byte
#define SECTORS_PER_CLUST 16 //每个簇有8个扇区
#define SUM_CLUSTS4096 // 磁盘总簇数
#define SUM_SECTORS (SUM_CLUSTS*SECTORS_PER_CLUST) //总扇区数
移植FAT文件系统主要做的工作时修改diskio.c文件中的磁盘读写函数,将其改成读写内存。
申请一块内存,模拟磁盘空间
char virtualDisk[SUM_SECTORS*SECTOR_SIZE];
//初始化磁盘,暂时不用
DSTATUS disk_initialize (
BYTE drv /* Physical drive nmuber (0..) */
)
{
return 0;
}
//获得磁盘状态,不用
DSTATUS disk_status (
BYTE drv /* Physical drive nmuber (0..) */
)
{
return 0;
}
//读扇区
//drv:磁盘编号0~9
//*buff:数据接收缓冲首地址
//sector:扇区地址
//count:需要读取的扇区数
void drv_read(BYTE *buf, DWORD sector)
{
memcpy(buf,&virtualDisk[sector*SECTOR_SIZE],SECTOR_SIZE);
}
DRESULT disk_read (
BYTE drv, /* Physical drive nmuber (0..) */
BYTE *buff, /* Data buffer to store read data */
DWORD sector, /* Sector address (LBA) */
BYTE count /* Number of sectors to read (1..255) */
)
{
U8 res=0;
if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误
switch(drv)
{
case SD_CARD://SD卡
case EX_FLASH://外部flash
for(;count>0;count--)
{
drv_read(buff,sector);
sector++;
buff+=SECTOR_SIZE;
}
res=0;
break;
default:
res=1;
}
//处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值
if(res==0x00)return RES_OK;
else return RES_ERROR;
}
//写扇区
//drv:磁盘编号0~9
//*buff:发送数据首地址
//sector:扇区地址
//count:需要写入的扇区数
#if _READONLY == 0
void drv_write(const BYTE *buf, DWORD sector)
{
memcpy(&virtualDisk[sector*SECTOR_SIZE], buf, SECTOR_SIZE);
}
DRESULT disk_write (
BYTE drv, /* Physical drive nmuber (0..) */
const BYTE *buff, /* Data to be written */
DWORD sector, /* Sector address (LBA) */
BYTE count /* Number of sectors to write (1..255) */
)
{
U8 res=0;
if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误
switch(drv)
{
case SD_CARD://SD卡
case EX_FLASH://外部flash
for(;count>0;count--)
{
drv_write(buff,sector);
sector++;
buff+=SECTOR_SIZE;
}
res=0;
break;
default:
res=1;
}
//处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值
if(res == 0x00)return RES_OK;
else return RES_ERROR;
}
#endif /* _READONLY */
//其他表参数的获得
//drv:磁盘编号0~9
//ctrl:控制代码
//*buff:发送/接收缓冲区指针
DRESULT disk_ioctl (
BYTE drv, /* Physical drive nmuber (0..) */
BYTE ctrl, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
DRESULT res;
switch(ctrl)
{
case CTRL_SYNC:
res = RES_OK;
break;
case GET_SECTOR_SIZE:
*(WORD*)buff = SECTOR_SIZE;
res = RES_OK;
break;
case GET_BLOCK_SIZE:
*(WORD*)buff = SECTORS_PER_CLUST;
res = RES_OK;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff = SUM_SECTORS;
res = RES_OK;
break;
default:
res = RES_PARERR;
break;
}
return res;
}
//获得时间,不用
DWORD get_fattime (void)
{
return 0;
}
主函数
void main()
{
FATFS fs,u8 ret,u8 drvnum=1,format=1;
u16 bytes_per_clust = 8192; //每簇字节数 8k
//挂载文件系统.实际就是为struct FATFS结构指针,申请一块内存,存储相关信息
// drvnum:磁盘盘符号,每个盘符号对应一个磁盘,实际就是对应FATFS数组的索引
// format 0或者1 :格式化所使用的模式,模式涉及到很多硬件信息,我们在内存中模拟,所以为
//了简单起见,使用模式一
ret = f_mount(drvnum, &fs);
ret =f_mkfs (drvnum, format, bytes_per_clust);
return;
}
用vc调试可以看到第一扇区数如下
地址0x00460940为我们申请的内存数组virtualDisk的首地址,这里引导扇区之后就只FAT表,由该地址偏移512字节可以得到FAT表第一个表项的内容,地址:0x00460940+0x200=0x00460b40
由上图可知FAT表第一项为fff0(保留簇),第二项为0xffff(结束簇)。