FAT文件系统解析(一) 引导扇区、FAT表及根目录区分析

来源:互联网 发布:数据库与管理信息系统 编辑:程序博客网 时间:2024/06/05 04:14

对于软件分析,特别是复杂的大型系统软件来说我个人的步骤一般是:

1、 了解设计者的设计思想,用行话说就是软件架构、层次、模块组成。

2、 根据软件结构和模块整理出主要的数据结构或者类。

3、 结合数据结构进一步熟悉软件架构、层次和模块构成。对于复杂系统,这个过程花的时间可能会很长。直到你能画出整个系统的架构图。在你画出架构图之间一般不要去深入分析源码细节,这很容易迷失在代码中。

4、 根据你画的架构图熟悉各个模块,这个步骤可以有选择性的去熟悉你感兴趣的模块。如果模块还很复杂很大的话你需要重复前面三个步骤,直到你可以很轻松的去了解模块里面的细节,也就是读源码。

 

下面我们就按照以上四个步骤来分析FAT文件系统

网上关于FAT文件系统结构组成的文章很多,由4个部分组成:引导扇区、FAT表、根目录区、数据区

现在先粗略讲解下前面3个部分。

引导扇区:

扇区大小这里设为512byte,磁盘的第一个扇区作为引导扇区,引导扇区的内容主要包括操作系统引导信息、磁盘基本信息、文件系统相关信息(BPB数据结构)、分区表信息(磁盘分区信息)。由于这里我们文件系统只涉及数据存储而且磁盘也不局限于硬盘,所以引导信息和磁盘基本信息不分析,反正网上相关信息很多。暂时也不涉及多分区,分区信息也不分析。我们现在主要关注文件系统信息。这个信息对应的数据结构是ff.h文件中定义的struct FATFS

 

FAT表:

FATfile access table文件访问表),记录着文件和目录所在的簇(clust)或者块(block),如果一个文件涉及多个簇则通过簇链将上下簇联系起来。我们以FAT16为例来说明FAT表项内容。这里我们定义总簇数为4096,每簇扇区数为16,每个扇区大小为512字节。每一个表项长度为2个字节,对应磁盘的一个簇。由于0号和1号簇用做引导区和FAT记录区,所以0号和1号表项不能用,2号表项对应根目录起始簇号,即根目录的起始簇为第二簇。表项标志内容:簇链结束标志:0xFFFF,保留标志:0XFFF0,坏簇标志:0XFFF7。空闲簇:0x0000。若有一个文件占用3个簇,文件起始簇号是3FAT表如下所示:

由上表可知0簇和1簇用作引导和FAT表,23簇用作根目录区,这里我们定义根目录项数为512,每个目录占32个字节。文件起始簇号为4,文件占用4563簇。这里文件占用的3个簇连续,也可能不连续,表项60xffff表示文件在这一簇结束。表项78的记录为0x0000,表示空闲。表项总数为磁盘总簇数+2

 

根目录区:

根目录是所有文件和目录的入口,现在我们只要了解根目录起始簇为2,占用2个簇就行了,具体信息到目录和文件管理的时候再分析。

 

下面介绍以上模块对应的数据结构:

文件系统结构:

typedef struct {

    BYTEfs_type;      /* FAT sub-type (0:Not mounted) */

    BYTEdrv;          /* Physical drive number */

    BYTEcsize;             /* Sectors per cluster (1,2,4...128) */

    BYTEn_fats;            /* Number of FAT copies (1,2) */

    BYTEwflag;             /* win[] dirty flag (1:must be written back) */

    BYTEfsi_flag;     /* fsinfo dirty flag (1:must be written back) */

    WORDid;                /* File system mount ID */

    WORDn_rootdir;         /* Number of root directory entries (FAT12/16) */

#if _MAX_SS != 512

    WORDssize;             /* 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[] */

    BYTEwin[_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 */

    WORDid;                /* Owner file system mount ID */

    WORDindex;             /* 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 */

    WORDlfn_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=1format=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(结束簇)。
0 0
原创粉丝点击