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

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

 

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(结束簇)。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

缥缈孤鸿_jason

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值