前面分析了 FAT32 文件系统结构,接下来结合代码分析其实现过程。
一、分析假设
- 假设一个磁盘就一个分区。
- 只分析 FAT32 文件系统相关的代码。
- 函数的大部分分析,都写入代码注释中。
二、FatFs 文件系统顶层函数接口
2.1 f_mkfs()函数分析
为了方便分析,排除视觉障碍,已经删除了不在假设范围内代码。
/*-----------------------------------------------------------------------*/ /* Create FAT file system on the logical drive */ /*-----------------------------------------------------------------------*/ FRESULT f_mkfs ( const TCHAR* path, /* 磁盘号 */ BYTE opt, /* 格式化的类型:FAT32 */ DWORD au, /* 格式化时,要设置的簇大小,以字节为单位 */ void* work, /* 用户提供的 buffer */ UINT len /* 用户提供的 buffer 的大小,以字节为单位 */ ) { const UINT n_fats = 1; /* Number of FATs for FAT12/16/32 volume (1 or 2) */ const UINT n_rootdir = 512; /* Number of root directory entries for FAT12/16 volume */ static const WORD cst[] = {1, 4, 16, 64, 256, 512, 0}; /* Cluster size boundary for FAT12/16 volume (4Ks unit) */ static const WORD cst32[] = {1, 2, 4, 8, 16, 32, 0}; /* Cluster size boundary for FAT32 volume (128Ks unit) */ BYTE fmt, sys, *buf, *pte, pdrv, part; WORD ss; DWORD szb_buf, sz_buf, sz_blk, n_clst, pau, sect, nsect, n; DWORD b_vol, b_fat, b_data; /* Base LBA for volume, fat, data */ DWORD sz_vol, sz_rsv, sz_fat, sz_dir; /* Size for volume, fat, dir, data */ UINT i; int vol; DSTATUS stat; /*1. 获取磁盘号,进行一些必要参数的计算和分析,判断参数是否满足要求*/ /* Check mounted drive and clear work area */ vol = get_ldnumber(&path); /* 找到物理磁盘 */ if (vol < 0) return FR_INVALID_DRIVE; if (FatFs[vol]) FatFs[vol]->fs_type = 0; /* fat 文件系统的描述,放到一个全局变量里面 */ pdrv = LD2PD(vol); /* 找到物理磁盘号 */ part = LD2PT(vol); /* 分区表,Partition (0:create as new, 1-4:get from partition table) */ /* Check physical drive status */ stat = disk_initialize(pdrv); if (stat & STA_NOINIT) return FR_NOT_READY; if (stat & STA_PROTECT) return FR_WRITE_PROTECTED; /* 获取块大小,块是擦除的基本单元 */ if (disk_ioctl(pdrv, GET_BLOCK_SIZE, &sz_blk) != RES_OK || !sz_blk || sz_blk > 32768 || (sz_blk & (sz_blk - 1))) sz_blk = 1; /* Erase block to align data area */ ss = _MAX_SS; /* 默认扇区大小为 512 字节 */ if ((au != 0 && au < ss) || au > 0x1000000 || (au & (au - 1))) return FR_INVALID_PARAMETER; /* Check if au is valid */ au /= ss; /* 一个簇占多少个扇区 */ /* Get working buffer */ buf = (BYTE*)work; /* Working buffer */ sz_buf = len / ss; /* 下面写数据要用到一个 buffer, 这里计算出用户提供的 buffer 的大小(以扇区为单位)*/ szb_buf = sz_buf * ss; /* buffer 大小以字节为单位 */ if (!szb_buf) return FR_MKFS_ABORTED; /* buffer 如果不足 1 个扇区就报错 */ /* Determine where the volume to be located (b_vol, sz_vol) */ if (_MULTI_PARTITION && part != 0) { /* 判断 fatfs 是否是支持多扇区的,我们不分区多扇区的玩法 */ /* Get partition information from partition table in the MBR */ if (disk_read(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; /* Load MBR */ if (ld_word(buf + BS_55AA) != 0xAA55) return FR_MKFS_ABORTED; /* Check if MBR is valid */ pte = buf + (MBR_Table + (part - 1) * SZ_PTE); if (!pte[PTE_System]) return FR_MKFS_ABORTED; /* No partition? */ b_vol = ld_dword(pte + PTE_StLba); /* Get volume start sector */ sz_vol = ld_dword(pte + PTE_SizLba); /* Get volume size */ } else { /* 获取这个分区总的扇区个数 */ if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_vol) != RES_OK) return FR_DISK_ERR; b_vol = (opt & FM_SFD) ? 0 : 63; /* opt=FM_FAT32,所以此时,保留扇区个数 b_vol=63 */ if (sz_vol < b_vol) return FR_MKFS_ABORTED; sz_vol -= b_vol; /* 分区总扇区数减去保留扇区 */ } if (sz_vol < 128) return FR_MKFS_ABORTED; /* Check if volume size is >=128s */ /* Pre-determine the FAT type */ do { if (_FS_EXFAT && (opt & FM_EXFAT)) { /* exFAT possible? */ if ((opt & FM_ANY) == FM_EXFAT || sz_vol >= 0x4000000 || au > 128) { /* exFAT only, vol >= 64Ms or au > 128s ? */ fmt = FS_EXFAT; break; } } if (au > 128) return FR_INVALID_PARAMETER; /* au 现在的单位是扇区 */ if (opt & FM_FAT32) { /* FAT32 possible? */ if ((opt & FM_ANY) == FM_FAT32 || !(opt & FM_FAT)) { /* FAT32 only or no-FAT? */ fmt = FS_FAT32; break; /* 跳出 do while()循环 */ } } if (!(opt & FM_FAT)) return FR_INVALID_PARAMETER; /* no-FAT? */ fmt = FS_FAT16; } while (0); { /* Create an FAT12/16/32 volume */ do { pau = au; /* Pre-determine number of clusters and FAT sub-type */ if (fmt == FS_FAT32) { /* FAT32 volume */ if (!pau) { /* 如果簇大小用户设置为 0,那么就默认设置为 128K */ n = sz_vol / 0x20000; /* Volume size in unit of 128KS */ for (i = 0, pau = 1; cst32[i] && cst32[i] <= n; i++, pau <<= 1) ; /* Get from table */ } n_clst = sz_vol / pau; /* 计算出本分区总共的簇数 */ sz_fat = (n_clst * 4 + 8 + ss - 1) / ss; /* sz_fat 代表这个分区需要一个 fat 表需要占的扇区数 */ sz_rsv = 32; /* 保留扇区数 */ sz_dir = 0; /* No static directory */ if (n_clst <= MAX_FAT16 || n_clst > MAX_FAT32) return FR_MKFS_ABORTED; } else { /* FAT12/16 volume */ if (!pau) { /* au auto-selection */ n = sz_vol / 0x1000; /* Volume size in unit of 4KS */ for (i = 0, pau = 1; cst[i] && cst[i] <= n; i++, pau <<= 1) ; /* Get from table */ } n_clst = sz_vol / pau; if (n_clst > MAX_FAT12) { n = n_clst * 2 + 4; /* FAT size [byte] */ } else { fmt = FS_FAT12; n = (n_clst * 3 + 1) / 2 + 3; /* FAT size [byte] */ } sz_fat = (n + ss - 1) / ss; /* FAT size [sector] */ sz_rsv = 1; /* Number of reserved sectors */ sz_dir = (DWORD)n_rootdir * SZDIRE / ss; /* Rootdir size [sector] */ } b_fat = b_vol + sz_rsv; /* FAT 表所在的扇区号(63+32) */ b_data = b_fat + sz_fat * n_fats + sz_dir; /* 数据区所在的扇区号(FAT 表的基地址号+FAT 表所占的扇区数+0) */ /* Align data base to erase block boundary (for flash memory media) */ n = ((b_data + sz_blk - 1) & ~(sz_blk - 1)) - b_data; /* 由于擦除是按块来的,所以在此进行调整:需要让保留区、fat 表的要占完整的一个块 */ if (fmt == FS_FAT32) { /* FAT32: Move FAT base */ sz_rsv += n; b_fat += n; /* 单位为扇区 */ } else { /* FAT12/16: Expand FAT size */ sz_fat += n / n_fats; } /* Determine number of clusters and final check of validity of the FAT sub-type */ if (sz_vol < b_data + pau * 16 - b_vol) return FR_MKFS_ABORTED; /* Too small volume */ n_clst = (sz_vol - sz_rsv - sz_fat * n_fats - sz_dir) / pau; if (fmt == FS_FAT32) { if (n_clst <= MAX_FAT16) { /* Too few clusters for FAT32 */ if (!au && (au = pau / 2) != 0) continue; /* Adjust cluster size and retry */ return FR_MKFS_ABORTED; } } if (fmt == FS_FAT16) { if (n_clst > MAX_FAT16) { /* Too many clusters for FAT16 */ if (!au && (pau * 2) <= 64) { au = pau * 2; continue; /* Adjust cluster size and retry */ } if ((opt & FM_FAT32)) { fmt = FS_FAT32; continue; /* Switch type to FAT32 and retry */ } if (!au && (au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */ return FR_MKFS_ABORTED; } if (n_clst <= MAX_FAT12) { /* Too few clusters for FAT16 */ if (!au && (au = pau * 2) <= 128) continue; /* Adjust cluster size and retry */ return FR_MKFS_ABORTED; } } if (fmt == FS_FAT12 && n_clst > MAX_FAT12) return FR_MKFS_ABORTED; /* Too many clusters for FAT12 */ /* Ok, it is the valid cluster configuration */ break; } while (1); /* 2.创建分区的 DBR */ mem_set(buf, 0, ss); mem_cpy(buf + BS_JmpBoot, "\xEB\xFE\x90" "MSDOS5.0", 11);/* Boot jump code (x86), OEM name */ st_word(buf + BPB_BytsPerSec, ss); /* Sector size [byte] */ buf[BPB_SecPerClus] = (BYTE)pau; /* Cluster size [sector] */ st_word(buf + BPB_RsvdSecCnt, (WORD)sz_rsv); /* Size of reserved area */ buf[BPB_NumFATs] = (BYTE)n_fats; /* Number of FATs */ st_word(buf + BPB_RootEntCnt, (WORD)((fmt == FS_FAT32) ? 0 : n_rootdir)); /* Number of root directory entries */ if (sz_vol < 0x10000) { st_word(buf + BPB_TotSec16, (WORD)sz_vol); /* Volume size in 16-bit LBA */ } else { st_dword(buf + BPB_TotSec32, sz_vol); /* Volume size in 32-bit LBA */ } buf[BPB_Media] = 0xF8; /* Media descriptor byte */ st_word(buf + BPB_SecPerTrk, 63); /* Number of sectors per track (for int13) */ st_word(buf + BPB_NumHeads, 255); /* Number of heads (for int13) */ st_dword(buf + BPB_HiddSec, b_vol); /* Volume offset in the physical drive [sector] */ if (fmt == FS_FAT32) { st_dword(buf + BS_VolID32, GET_FATTIME()); /* VSN */ st_dword(buf + BPB_FATSz32, sz_fat); /* FAT size [sector] */ st_dword(buf + BPB_RootClus32, 2); /* Root directory cluster # (2) */ st_word(buf + BPB_FSInfo32, 1); /* Offset of FSINFO sector (VBR + 1) */ st_word(buf + BPB_BkBootSec32, 6); /* Offset of backup VBR (VBR + 6) */ buf[BS_DrvNum32] = 0x80; /* Drive number (for int13) */ buf[BS_BootSig32] = 0x29; /* Extended boot signature */ mem_cpy(buf + BS_VolLab32, "NO NAME " "FAT32 ", 19); /* Volume label, FAT signature */ } else { st_dword(buf + BS_VolID, GET_FATTIME()); /* VSN */ st_word(buf + BPB_FATSz16, (WORD)sz_fat); /* FAT size [sector] */ buf[BS_DrvNum] = 0x80; /* Drive number (for int13) */ buf[BS_BootSig] = 0x29; /* Extended boot signature */ mem_cpy(buf + BS_VolLab, "NO NAME " "FAT ", 19); /* Volume label, FAT signature */ } st_word(buf + BS_55AA, 0xAA55); /* Signature (offset is fixed here regardless of sector size) */ if (disk_write(pdrv, buf, b_vol, 1) != RES_OK) return FR_DISK_ERR; /* Write it to the VBR sector */ /* Create FSINFO record if needed */ if (fmt == FS_FAT32) { disk_write(pdrv, buf, b_vol + 6, 1); /* Write backup VBR (VBR + 6) */ mem_set(buf, 0, ss); st_dword(buf + FSI_LeadSig, 0x41615252); st_dword(buf + FSI_StrucSig, 0x61417272); st_dword(buf + FSI_Free_Count, n_clst - 1); /* Number of free clusters */ st_dword(buf + FSI_Nxt_Free, 2); /* Last allocated cluster# */ st_word(buf + BS_55AA, 0xAA55); disk_write(pdrv, buf, b_vol + 7, 1); /* Write backup FSINFO (VBR + 7) */ disk_write(pdrv, buf, b_vol + 1, 1); /* Write original FSINFO (VBR + 1) */ } /* 3.初始化 fat 表 */ mem_set(buf, 0, (UINT)szb_buf); sect = b_fat; /* FAT start sector */ for (i = 0; i < n_fats; i++) { /* Initialize FATs each */ if (fmt == FS_FAT32) { st_dword(buf + 0, 0xFFFFFFF8); /* 目录项 0 */ st_dword(buf + 4, 0xFFFFFFFF); /* 目录项 1 */ st_dword(buf + 8, 0x0FFFFFFF); /* 根目录项 */ } else { st_dword(buf + 0, (fmt == FS_FAT12) ? 0xFFFFF8 : 0xFFFFFFF8); /* Entry 0 and 1 */ } nsect = sz_fat; /* Number of FAT sectors */ do { /* Fill FAT sectors */ n = (nsect > sz_buf) ? sz_buf : nsect; if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) return FR_DISK_ERR; mem_set(buf, 0, ss); sect += n; nsect -= n; } while (nsect); } /* 4.把根目录所在的扇区全部清 0 */ nsect = (fmt == FS_FAT32) ? pau : sz_dir; /* Number of root directory sectors */ do { n = (nsect > sz_buf) ? sz_buf : nsect; if (disk_write(pdrv, buf, sect, (UINT)n) != RES_OK) return FR_DISK_ERR; sect += n; nsect -= n; } while (nsect); } /* 5.在分区的第 0 号扇区写入 MBR */ if (_FS_EXFAT && fmt == FS_EXFAT) { sys = 0x07; /* HPFS/NTFS/exFAT */ } else { if (fmt == FS_FAT32) { sys = 0x0C; /* FAT32X */ } else { if (sz_vol >= 0x10000) { sys = 0x06; /* FAT12/16 (>=64KS) */ } else { sys = (fmt == FS_FAT16) ? 0x04 : 0x01; /* FAT16 (<64KS) : FAT12 (<64KS) */ } } } if (_MULTI_PARTITION && part != 0) { /* Update system ID in the partition table */ if (disk_read(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; /* Read the MBR */ buf[MBR_Table + (part - 1) * SZ_PTE + PTE_System] = sys; /* Set system type */ if (disk_write(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; /* Write it back to the MBR */ } else { if (!(opt & FM_SFD)) { /* Create partition table in FDISK format */ mem_set(buf, 0, ss); st_word(buf + BS_55AA, 0xAA55); /* MBR signature */ pte = buf + MBR_Table; /* Create partition table for single partition in the drive */ /*5.1 MBR 中分区表的信息*/ pte[PTE_Boot] = 0; /* Boot indicator */ pte[PTE_StHead] = 1; /* Start head */ pte[PTE_StSec] = 1; /* Start sector */ pte[PTE_StCyl] = 0; /* Start cylinder */ pte[PTE_System] = sys; /* System type */ n = (b_vol + sz_vol) / (63 * 255); /* (End CHS is incorrect) */ pte[PTE_EdHead] = 254; /* End head */ pte[PTE_EdSec] = (BYTE)(n >> 2 | 63); /* End sector */ pte[PTE_EdCyl] = (BYTE)n; /* End cylinder */ st_dword(pte + PTE_StLba, b_vol); /* Start offset in LBA */ st_dword(pte + PTE_SizLba, sz_vol); /* Size in sectors */ if (disk_write(pdrv, buf, 0, 1) != RES_OK) return FR_DISK_ERR; /* Write it to the MBR */ } } if (disk_ioctl(pdrv, CTRL_SYNC, 0) != RES_OK) return FR_DISK_ERR; return FR_OK; }
2.1.1 格式化函数的参数
分析思考我们在 Windows 上格式化一个磁盘,我们可以得出格式化磁盘需要内容:
- 在“需要格式化的磁盘”右键;
- 选择要格式化的文件系统类型,FAT16/FAT32/EXTFAT 等;
- 选择簇大小。
因此,格式化函数的第 1 个参数是磁盘的路径,根据磁盘的路径函数就能找到对应的磁盘,第 2 个参数也是要传入文件系统的类型,第 3 个参数也就是簇的大小,第 4、5 个参数是这个函数运行所需要的内存。
2.1.2 格式化函数的作用
与我们之前分析 FAT 文件系统原理是一致的,此函数总共进行如下的操作:
- 获取磁盘号,进行一些必要参数的计算和分析,判断参数是否满足要求。
- 创建 DBR。同时创建 FSINFO 信息,FSINFO 信息的作用表示了当前分区还可用的簇多少已经下一个可用的簇号,从而可以方便操作系统处理。
- 初始化 FAT 表。
- 初始化根目录区。把根目录所在的簇全部清 0。
- 创建 MBR,并且把当前的分区信息写入主分区字段内。
2.1.3 总结
从 f_mkfs 我们可以看出,此函数的作用与我们之前分析的 FAT 文件系统是完全吻合的。
2.2 f_mount 函数分析
为了方便分析,排除视觉障碍,已经删除了不在假设范围内代码。
FRESULT f_mount ( FATFS* fs, /* fat 文件系统描述的一个结构体,需要用户进行分配,然后会被记录在全局的 FATFS[]这个数组中 */ const TCHAR* path, /* 物理磁盘 */ BYTE opt /* 挂载选项,1-代表立马挂载,从代码分析中知,这个值必须传入 1 */ ) { FATFS *cfs; int vol; FRESULT res; const TCHAR *rp = path; /* 获取驱动号,为了找到 FatFs[]数组中的空闲项 */ vol = get_ldnumber(&rp); if (vol < 0) return FR_INVALID_DRIVE; cfs = FatFs[vol]; /* Pointer to fs object */ if (cfs) { #if _FS_LOCK != 0 clear_lock(cfs); #endif #if _FS_REENTRANT /* Discard sync object of the current volume */ if (!ff_del_syncobj(cfs->sobj)) return FR_INT_ERR; #endif cfs->fs_type = 0; /* Clear old fs object */ } if (fs) { fs->fs_type = 0; /* Clear new fs object */ #if _FS_REENTRANT /* Create sync object for the new volume */ if (!ff_cre_syncobj((BYTE)vol, &fs->sobj)) return FR_INT_ERR; #endif } /* 把用户分配的 FATFS 结构图放入全局数组指针中 */ FatFs[vol] = fs; /* Register new fs object */ if (!fs || opt != 1) return FR_OK; /* Do not mount now, it will be mounted later */ /* 这个函数才是挂载时的关键所在 */ res = find_volume(&path, &fs, 0); /* Force mounted the volume */ LEAVE_FF(fs, res); }
从 f_mount 函数分析中可知,find_volume()函数才是挂载的核心代码,
static FRESULT find_volume ( /* FR_OK(0): successful, !=0: any error occurred */ const TCHAR** path, /* 硬件磁盘,与 f_mount 函数的参数是一致的 */ FATFS** rfs, /* 与 f_mount 函数的 fs 参数是一致的 */ BYTE mode /* 这个传入的是 0 */ ) { BYTE fmt, *pt; int vol; DSTATUS stat; DWORD bsect, fasize, tsect, sysect, nclst, szbfat, br[4]; WORD nrsv; FATFS *fs; UINT i; /* Get logical drive number */ *rfs = 0; vol = get_ldnumber(path); if (vol < 0) return FR_INVALID_DRIVE; /* Check if the file system object is valid or not */ /* 1.找到用户分配的 FATFS 结构体 */ fs = FatFs[vol]; /* Get pointer to the file system object */ if (!fs) return FR_NOT_ENABLED; /* Is the file system object available? */ ENTER_FF(fs); /* Lock the volume */ *rfs = fs; /* Return pointer to the file system object */ mode &= (BYTE)~FA_READ; /* Desired access mode, write access or not */ /* 判定磁盘当前状态,如果磁盘被初始化过,那么就判定是挂载过了,直接返回 OK */ if (fs->fs_type) { /* If the volume has been mounted */ stat = disk_status(fs->drv); if (!(stat & STA_NOINIT)) { /* and the physical drive is kept initialized */ if (!_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check write protection if needed */ return FR_WRITE_PROTECTED; } return FR_OK; /* The file system object is valid */ } } /* The file system object is not valid. */ /* Following code attempts to mount the volume. (analyze BPB and initialize the fs object) */ /* 2.进行 FATFS 结构体填充 */ fs->fs_type = 0; /* Clear the file system object */ fs->drv = LD2PD(vol); /* Bind the logical drive and a physical drive */ /* 2.1 初始化磁盘 */ stat = disk_initialize(fs->drv); /* Initialize the physical drive */ if (stat & STA_NOINIT) { /* Check if the initialization succeeded */ return FR_NOT_READY; /* Failed to initialize due to no medium or hard error */ } if (!_FS_READONLY && mode && (stat & STA_PROTECT)) { /* Check disk write protection if needed */ return FR_WRITE_PROTECTED; } /* Find an FAT partition on the drive. Supports only generic partitioning, FDISK and SFD. */ bsect = 0; /* 2.2 check_fs()函数会把磁盘的第 1 个扇区(就是 MBR)读入到 fs->win[]数组中, 判断 MBR 是否是合法的 MBR*/ fmt = check_fs(fs, bsect); /* Load sector 0 and check if it is an FAT-VBR as SFD */ /* 2.3 fmt=0,说明是 FAT32 文件系统 */ if (fmt == 2 || (fmt < 2 && LD2PT(vol) != 0)) { /* Not an FAT-VBR or forced partition number */ for (i = 0; i < 4; i++) { /* Get partition offset */ pt = fs->win + (MBR_Table + i * SZ_PTE); br[i] = pt[PTE_System] ? ld_dword(pt + PTE_StLba) : 0; } i = LD2PT(vol); /* Partition number: 0:auto, 1-4:forced */ if (i) i--; do { /* Find an FAT volume */ bsect = br[i]; fmt = bsect ? check_fs(fs, bsect) : 3; /* Check the partition */ } while (!LD2PT(vol) && fmt >= 2 && ++i < 4); } if (fmt == 4) return FR_DISK_ERR; /* An error occured in the disk I/O layer */ if (fmt >= 2) return FR_NO_FILESYSTEM; /* No FAT volume is found */ /* An FAT volume is found. Following code initializes the file system object */ { /* 读取 MBR 中的 BPB_BytsPerSec 域,判断扇区大小是否是 512(SS(fs)) */ if (ld_word(fs->win + BPB_BytsPerSec) != SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_BytsPerSec must be equal to the physical sector size) */ /* 2.4 获取 FAT 表的大小、FAT 表的个数、每个簇的扇区数、根目录项数(对于 FAT32 不存在这个)、磁盘簇的个数、MBR、FAT 表、数据区的位置、 */ fasize = ld_word(fs->win + BPB_FATSz16); /* Number of sectors per FAT */ if (fasize == 0) fasize = ld_dword(fs->win + BPB_FATSz32); fs->fsize = fasize; fs->n_fats = fs->win[BPB_NumFATs]; /* Number of FATs */ if (fs->n_fats != 1 && fs->n_fats != 2) return FR_NO_FILESYSTEM; /* (Must be 1 or 2) */ fasize *= fs->n_fats; /* Number of sectors for FAT area */ fs->csize = fs->win[BPB_SecPerClus]; /* Cluster size */ if (fs->csize == 0 || (fs->csize & (fs->csize - 1))) return FR_NO_FILESYSTEM; /* (Must be power of 2) */ fs->n_rootdir = ld_word(fs->win + BPB_RootEntCnt); /* Number of root directory entries */ if (fs->n_rootdir % (SS(fs) / SZDIRE)) return FR_NO_FILESYSTEM; /* (Must be sector aligned) */ tsect = ld_word(fs->win + BPB_TotSec16); /* Number of sectors on the volume */ if (tsect == 0) tsect = ld_dword(fs->win + BPB_TotSec32); nrsv = ld_word(fs->win + BPB_RsvdSecCnt); /* Number of reserved sectors */ if (nrsv == 0) return FR_NO_FILESYSTEM; /* (Must not be 0) */ /* Determine the FAT sub type */ sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZDIRE); /* RSV + FAT + DIR */ if (tsect < sysect) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ nclst = (tsect - sysect) / fs->csize; /* Number of clusters */ if (nclst == 0) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ fmt = FS_FAT32; if (nclst <= MAX_FAT16) fmt = FS_FAT16; if (nclst <= MAX_FAT12) fmt = FS_FAT12; /* Boundaries and Limits */ fs->n_fatent = nclst + 2; /* Number of FAT entries */ fs->volbase = bsect; /* Volume start sector */ fs->fatbase = bsect + nrsv; /* FAT start sector */ fs->database = bsect + sysect; /* Data start sector */ if (fmt == FS_FAT32) { if (ld_word(fs->win + BPB_FSVer32) != 0) return FR_NO_FILESYSTEM; /* (Must be FAT32 revision 0.0) */ if (fs->n_rootdir) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must be 0) */ fs->dirbase = ld_dword(fs->win + BPB_RootClus32); /* Root directory start cluster */ szbfat = fs->n_fatent * 4; /* (Needed FAT size) */ } else { if (fs->n_rootdir == 0) return FR_NO_FILESYSTEM;/* (BPB_RootEntCnt must not be 0) */ fs->dirbase = fs->fatbase + fasize; /* Root directory start sector */ szbfat = (fmt == FS_FAT16) ? /* (Needed FAT size) */ fs->n_fatent * 2 : fs->n_fatent * 3 / 2 + (fs->n_fatent & 1); } if (fs->fsize < (szbfat + (SS(fs) - 1)) / SS(fs)) return FR_NO_FILESYSTEM; /* (BPB_FATSz must not be less than the size needed) */ #if !_FS_READONLY /* Get FSINFO if available */ fs->last_clst = fs->free_clst = 0xFFFFFFFF; /* Initialize cluster allocation information */ fs->fsi_flag = 0x80; #if (_FS_NOFSINFO & 3) != 3 /* 2.5 判断是否存在 FSINFO 扇区,如果存在则读出可用簇数、下一个可用簇号到 FATFS 结构体中 */ if (fmt == FS_FAT32 /* Enable FSINFO only if FAT32 and BPB_FSInfo32 == 1 */ && ld_word(fs->win + BPB_FSInfo32) == 1 && move_window(fs, bsect + 1) == FR_OK) { fs->fsi_flag = 0; if (ld_word(fs->win + BS_55AA) == 0xAA55 /* Load FSINFO data if available */ && ld_dword(fs->win + FSI_LeadSig) == 0x41615252 && ld_dword(fs->win + FSI_StrucSig) == 0x61417272) { #if (_FS_NOFSINFO & 1) == 0 fs->free_clst = ld_dword(fs->win + FSI_Free_Count); #endif #if (_FS_NOFSINFO & 2) == 0 fs->last_clst = ld_dword(fs->win + FSI_Nxt_Free); #endif } } #endif /* (_FS_NOFSINFO & 3) != 3 */ #endif /* !_FS_READONLY */ } fs->fs_type = fmt; /* FAT sub-type */ fs->id = ++Fsid; /* File system mount ID */ #if _USE_LFN == 1 fs->lfnbuf = LfnBuf; /* Static LFN working buffer */ #if _FS_EXFAT fs->dirbuf = DirBuf; /* Static directory block working buuffer */ #endif #endif #if _FS_RPATH != 0 fs->cdir = 0; /* Initialize current directory */ #endif #if _FS_LOCK != 0 /* Clear file lock semaphores */ clear_lock(fs); #endif return FR_OK; }
总结
f_mount 函数就是读出 MBR 扇区的内容放入 FATFS 结构图中,供以后使用。
2.3 f_open 函数分析
2.3.1 关键结构体
(1)FAT 文件系统描述一个文件夹的结构体。虽然是一个文件夹结构体,但是再打开一个文件的开始,先创建这个结构体,然后先把文件的信息填充到这个结构体,最终使用这个结构体填充 FIL 结构图的一些重要信息。
typedef struct { _FDID obj; /* 存放下一个目标的信息 */ DWORD dptr; /* 当前目录项的起始地址(单位:字节) */ DWORD clust; /* 当前簇 */ DWORD sect; /* 当前簇对应的扇区位置(通过目录项的位置确定) */ BYTE* dir; /* 指向当前扇区 win[]中的偏移(单位:字节) */ BYTE fn[12]; /* 存放 8.3 文件名,最后 1 个字节存放标志:是否有长目录项 */ #if _USE_LFN != 0 DWORD blk_ofs; /* 指向第 1 个长目录项的起始位置(字节:单位) (0xFFFFFFFF:Invalid) */ #endif #if _USE_FIND const TCHAR* pat; /* Pointer to the name matching pattern */ #endif } DIR;
(2)FAT 文件系统描述一个文件的结构体。
typedef struct { _FDID obj; /* 存放下一个目标的信息 */ BYTE flag; /* 文件状态标志 */ BYTE err; /* 文件错误码 */ FSIZE_t fptr; /* 文件的读写指针位置 */ DWORD clust; /* fptr 所在的当前簇 */ DWORD sect; /* Sector number appearing in buf[] (0:invalid) */ #if !_FS_READONLY DWORD dir_sect; /* Sector number containing the directory entry */ BYTE* dir_ptr; /* Pointer to the directory entry in the win[] */ #endif #if _USE_FASTSEEK DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */ #endif #if !_FS_TINY BYTE buf[_MAX_SS]; /* File private data read/write window */ #endif } FIL;
(3)_FDID 结构体。
在(1)和(2)的结构体中,都有 _FDID 结构体,那么分析一下这个结构体的内容:
注:在 FATFS 中有个全局的指针数组 FATFS[],在 f_mount 的时候可知,这个数组会被填充,填充的内容是 MBR 的内容,之后,我们把这个数组指针称作为**“超级快”**。
typedef struct { FATFS* fs; /* 指向全局的在挂载的时候磁盘对应的超级块 */ WORD id; /* Owner file system mount ID */ BYTE attr; /* 目标属性,根据目录项中的属性进行填充 */ BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous (no data on FAT), =3:got flagmented, b2:sub-directory stretched) */ DWORD sclust; /* 目标的起始簇号 */ FSIZE_t objsize; /* 文件或者文件夹的大小 */ #if _FS_LOCK != 0 UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */ #endif } _FDID;
2.3.2 f_open 函数分析
下面是 f_open 的源代码:
FRESULT f_open ( FIL* fp, /* 返回的文件描述符 */ const TCHAR* path, /* 打开文件的路径 */ BYTE mode /* 文件打开的模式 */ ) { FRESULT res; DIR dj; /* 分配一个 DIR 结构体 */ FATFS *fs; #if !_FS_READONLY DWORD dw, cl, bcs, clst, sc; FSIZE_t ofs; #endif if (!fp) return FR_INVALID_OBJECT; /* Get logical drive */ mode &= _FS_READONLY ? FA_READ : FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_CREATE_NEW | FA_OPEN_ALWAYS | FA_OPEN_APPEND | FA_SEEKEND; res = find_volume(&path, &fs, mode); if (res == FR_OK) { dj.obj.fs = fs; /* 根据不同的配置来分配 fs->lfn,用于存放 path 指向的路径 */ INIT_NAMBUF(fs); res = follow_path(&dj, path); /* Follow the file path */ #if !_FS_READONLY /* R/W configuration */ if (res == FR_OK) { if (dj.fn[NSFLAG] & NS_NONAME) { /* Origin directory itself? */ res = FR_INVALID_NAME; } #if _FS_LOCK != 0 else { res = chk_lock(&dj, (mode & ~FA_READ) ? 1 : 0); } #endif } /* Create or Open a file */ if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) { if (res != FR_OK) { /* 文件不存在 创建文件,省略不少代码 */ } else { /* 文件存在 但是不允许写入 */ if (dj.obj.attr & (AM_RDO | AM_DIR)) { /* Cannot overwrite it (R/O or DIR) */ res = FR_DENIED; } else { if (mode & FA_CREATE_NEW) res = FR_EXIST; /* Cannot create as new file */ } } if (res == FR_OK && (mode & FA_CREATE_ALWAYS)) { /* 覆盖文件,省略不少代码 */ } } else { /* 成功打开一个文件 */ if (res == FR_OK) { /* Following succeeded */ if (dj.obj.attr & AM_DIR) { /* It is a directory */ res = FR_NO_FILE; } else { if ((mode & FA_WRITE) && (dj.obj.attr & AM_RDO)) { /* R/O violation */ res = FR_DENIED; } } } } if (res == FR_OK) { if (mode & FA_CREATE_ALWAYS) /* Set file change flag if created or overwritten */ mode |= FA_MODIFIED; fp->dir_sect = fs->winsect; /* Pointer to the directory entry */ fp->dir_ptr = dj.dir; } #else #endif if (res == FR_OK) { /* 使用 dj 结构体对文件描述符 fp 填充 */ /* 具体填充的代码,参考源码,最终会进行一些总结概括 */ } FREE_NAMBUF(); } if (res != FR_OK) fp->obj.fs = 0; /* Invalidate file object on error */ LEAVE_FF(fs, res); }
(1)在注释 1 的地方,先调用 INIT_NAMBUF(fs)宏,初始化 fs->lfn,这个 lfn 之后会被用来存放 path 中以’/’为分隔符的各个段的名字。
(2)接着在注释 2 的地方调用 follow_path(&dj, path),追踪 path 的各个段,下面是 follow_path()函数的源码:
/*-----------------------------------------------------------------------*/ /* 跟踪文件路径 */ /*-----------------------------------------------------------------------*/ static FRESULT follow_path ( /* FR_OK(0): successful, !=0: error code */ DIR* dp, /* Directory object to return last directory and found object */ const TCHAR* path /* Full-path string to find a file or directory */ ) { FRESULT res; BYTE ns; _FDID *obj = &dp->obj; FATFS *fs = obj->fs; { /* With heading separator */ while (*path == '/' || *path == '\\') path++; /* Strip heading separator */ obj->sclust = 0; /* Start from the root directory */ } if ((UINT)*path < ' ') { /* Null path name is the origin directory itself */ dp->fn[NSFLAG] = NS_NONAME; res = dir_sdi(dp, 0); } else { /* Follow path */ for (;;) { /***************** 1.获取 path 的每一段名字 *****************/ res = create_name(dp, &path); if (res != FR_OK) break; /***************** 2.判断磁盘上面是否存在提取出来的目录项 *****************/ res = dir_find(dp); /* Find an object with the segment name */ /***************** 3.获取当前文件名获取的状态 *****************/ ns = dp->fn[NSFLAG]; if (res != FR_OK) { /* Failed to find the object */ if (res == FR_NO_FILE) { /* 不存在此目录项 */ if (_FS_RPATH && (ns & NS_DOT)) { /* If dot entry is not exist, stay there */ if (!(ns & NS_LAST)) continue; /* Continue to follow if not last segment */ dp->fn[NSFLAG] = NS_NONAME; res = FR_OK; } else { /* Could not find the object */ if (!(ns & NS_LAST)) res = FR_NO_PATH; /* Adjust error code if not last segment */ } } break; } if (ns & NS_LAST) break; /* 是最后一段 */ /* Get into the sub-directory */ if (!(obj->attr & AM_DIR)) { /* 是子目录,不需要再追踪 */ res = FR_NO_PATH; break; } { /********* 4.设置下一个簇的位置*********/ obj->sclust = ld_clust(fs, fs->win + dp->dptr % SS(fs)); /* Open next directory */ } } } return res; }
在这个函数中又调用 create_name()和 dir_find(dp)两个函数,这两个函数是追踪的核心代码,也是 f_open 的核心代码。
(2.1)下面是 create_name()的源码:
/*-----------------------------------------------------------------------*/ /* 使用传入的 Path,然后用'/'进行分段,提取每一段的名字 ,同时修改 path 指向的位置*/ /*-----------------------------------------------------------------------*/ static FRESULT create_name ( /* FR_OK: successful, FR_INVALID_NAME: could not create */ DIR* dp, /* Pointer to the directory object */ const TCHAR** path /* Pointer to pointer to the segment in the path string */ ) { //#if _USE_LFN != 0 /* LFN configuration */ BYTE b, cf; WCHAR w, *lfn; UINT i, ni, si, di; const TCHAR *p; /* Create LFN in Unicode */ p = *path; lfn = dp->obj.fs->lfnbuf; si = di = 0; for (;;) { w = p[si++]; /* 从 path 中获取一个字符 */ if (w < ' ') break; /* 如果字符的值<' ',那么就判定 path 没有字符了,因为文件的命名字符不能<' ' */ if (w == '/' || w == '\\') { /* 如果字符是/或者\字符,就跳出来,说明一段已经结束了 */ while (p[si] == '/' || p[si] == '\\') si++; break; } /* 一段的名字太长了,报错, 这个判断写再下面才容易阅读,尴尬中。。。 */ if (di >= _MAX_LFN) return FR_INVALID_NAME; /* 如果字符不是 UNICODE 编码的话,就进行转码 */ #if !_LFN_UNICODE w &= 0xFF; if (IsDBCS1(w)) { /* Check if it is a DBC 1st byte (always false on SBCS cfg) */ b = (BYTE)p[si++]; /* Get 2nd byte */ w = (w << 8) + b; /* Create a DBC */ if (!IsDBCS2(b)) return FR_INVALID_NAME; /* Reject invalid sequence */ } w = ff_convert(w, 1); /* Convert ANSI/OEM to Unicode */ if (!w) return FR_INVALID_NAME; /* Reject invalid code */ #endif /* 非法字符检测 */ if (w < 0x80 && chk_chr("\"*:<>\?|\x7F", w)) return FR_INVALID_NAME; /* Reject illegal characters for LFN */ /******************* 1.把字符存入 lfn 的 buffer 中 *******************/ lfn[di++] = w; } /******************* 2.让 path 指向下一段*******************/ *path = &p[si]; cf = (w < ' ') ? NS_LAST : 0; /******************** 3.判断是否是最后一段 ********************/ /* 在 lfn 中,从后向前找,找到第 1 个并不是空格也不是点的位置 */ while (di) { w = lfn[di - 1]; if (w != ' ' && w != '.') break; di--; } lfn[di] = 0; /* 在不是空格也不是点的后面设置字符串结束标志 */ if (di == 0) return FR_INVALID_NAME; /* 判断名字是否为空 */ /* Create SFN in directory form */ mem_set(dp->fn, ' ', 11); /*从 lfn 的头开始找到不是空格也不是点的位置*/ for (si = 0; lfn[si] == ' ' || lfn[si] == '.'; si++) ; if (si) cf |= NS_LOSS | NS_LFN; /* 如果 lfn 的头部存在空格和点,设置标志 */ while (di && lfn[di - 1] != '.') di--; /* 从后向前找到点的位置,作为扩展名的起始位置 */ /* 此时 si 指向文件名的起始位置,di 指向文件名的结尾 */ i = b = 0; ni = 8; for (;;) { w = lfn[si++]; /* Get an LFN character */ if (!w) break; /* Break on end of the LFN */ if (w == ' ' || (w == '.' && si != di)) { /* Remove spaces and dots */ cf |= NS_LOSS | NS_LFN; continue; } /* 判断是否有长文件名 */ if (i >= ni || si == di) { /* 如果文件名有 8 个字符或者到了文件名的结束字符 */ if (ni == 11) { /* 如果 ni==11,说明需要设置长文件名 */ cf |= NS_LOSS | NS_LFN; break; } if (si != di) cf |= NS_LOSS | NS_LFN; /* 文件名的长度肯定是大于 8 个字符,需要设置长文件名 */ if (si > di) break; /* di 指向的是文件名的结尾,说明不存在扩展名 */ si = di; i = 8; ni = 11; /* 直接让 si=di,下一次循环就直接会跳出,不会再给 dp->fn 赋值了 */ b <<= 2; continue; } /* 字符编码转换 */ if (w >= 0x80) { /* Non ASCII character */ #ifdef _EXCVT w = ff_convert(w, 0); /* Unicode -> OEM code */ if (w) w = ExCvt[w - 0x80]; /* Convert extended character to upper (SBCS) */ #else w = ff_convert(ff_wtoupper(w), 0); /* Upper converted Unicode -> OEM code */ #endif cf |= NS_LFN; /* Force create LFN entry */ } /* 下面把小写转换成大写存入 dp->fn 中 */ if (_DF1S && w >= 0x100) { /* Is this DBC? (always false at SBCS cfg) */ if (i >= ni - 1) { cf |= NS_LOSS | NS_LFN; i = ni; continue; } dp->fn[i++] = (BYTE)(w >> 8); } else { /* SBC */ if (!w || chk_chr("+,;=[]", w)) { /* Replace illegal characters for SFN */ w = '_'; cf |= NS_LOSS | NS_LFN;/* Lossy conversion */ } else { if (IsUpper(w)) { /* ASCII large capital */ b |= 2; } else { if (IsLower(w)) { /* ASCII small capital */ b |= 1; w -= 0x20; } } } } /******************* 4.把短文件名放入 dp->fn[]中 *******************/ dp->fn[i++] = (BYTE)w; } /* 如果 dp->fn[0]== 0XE5 那么就替换成 0x05,因为 E5 代表的是被删除的目录项*/ if (dp->fn[0] == DDEM) dp->fn[0] = RDDEM; if (ni == 8) b <<= 2; if ((b & 0x0C) == 0x0C || (b & 0x03) == 0x03) cf |= NS_LFN; /* 进一步保证需要有长文件名 */ /* 文件名和扩展名不存在长文件名标志 */ if (!(cf & NS_LFN)) { /* When LFN is in 8.3 format without extended character, NT flags are created */ if ((b & 0x03) == 0x01) cf |= NS_EXT; /* NT flag (Extension has only small capital) */ if ((b & 0x0C) == 0x04) cf |= NS_BODY; /* NT flag (Filename has only small capital) */ } /******************* 5.在 dp->fn[]最后一个字节写入文件名的标志:是否有长文件名、是否是最后一段 *******************/ dp->fn[NSFLAG] = cf; /* 把 CF 放入 fn 的最后一个字节 */ return FR_OK; }
create_name ()函数的主要作用是根据 path 中的“/”进行分割每一段的名字,并且把短文件名存入 dp->fn[]中,具体操作步骤:
- 跟进 path 找出一段文件名,然后存入 lfn[]缓存中。
- 修改 path 指向下一段文件名的起始位置。
- 把 lfn[]制作成短文件名放入 dp->fn[]中。
- 把当前的状态放入 dp->fn[]的最后一个字节,用来指示:是否有长文件名,是否是最后一段。
(2.2)dir_find()函数的源码:
/*-----------------------------------------------------------------------*/ /*根据 dp->sect、dp->fn 找到相应的目录项, */ /*-----------------------------------------------------------------------*/ static FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */ DIR* dp /* Pointer to the directory object with the file name */ ) { FRESULT res; FATFS *fs = dp->obj.fs; BYTE c; #if _USE_LFN != 0 BYTE a, ord, sum; #endif /*********** 1.根据 dp->obj.sclust,设置 dp 的当前信息 ************/ res = dir_sdi(dp, 0); /* Rewind directory object */ if (res != FR_OK) return res; /* On the FAT12/16/32 volume */ #if _USE_LFN != 0 ord = sum = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */ #endif do { /********* 2.如果当前扇区的内容不在 win[]缓冲,就从磁盘读出来 *************/ res = move_window(fs, dp->sect); if (res != FR_OK) break; c = dp->dir[DIR_Name]; if (c == 0) { res = FR_NO_FILE; break; } /* 判断目录项的第 1 个字节是否合法 */ //#if _USE_LFN != 0 /* LFN configuration */ dp->obj.attr = a = dp->dir[DIR_Attr] & AM_MASK; /* 如果第 1 个字节是 E5 或者是卷标,就判定也不是合适的目录项 */ if (c == DDEM || ((a & AM_VOL) && a != AM_LFN)) { ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */ } else { if (a == AM_LFN) { /* 是长目录项 */ if (!(dp->fn[NSFLAG] & NS_NOLFN)) { if (c & LLEF) { /* 如果 c==0x40 那么就是最后一个长目录项 */ sum = dp->dir[LDIR_Chksum]; c &= (BYTE)~LLEF; ord = c; /* LFN start order */ dp->blk_ofs = dp->dptr; /* Start offset of LFN */ } /* Check validity of the LFN entry and compare it with given name */ ord = (c == ord && sum == dp->dir[LDIR_Chksum] && cmp_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF; } } else { /* An SFN entry is found */ if (!ord && sum == sum_sfn(dp->dir)) break; /* LFN matched? */ /********* 3.比较短目录名是否匹配 **************/ if (!(dp->fn[NSFLAG] & NS_LOSS) && !mem_cmp(dp->dir, dp->fn, 11)) break; ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */ } } /********** 4.寻找下一个目录项:会修改 dp->dptr,dp->dir 的值,也可能修改当前簇,当前扇区等信息 如果此时在当前目录下没有目录项了,就会返回错误*************** */ res = dir_next(dp, 0); } while (res == FR_OK); return res; }
dir_find ()函数的作用是在扇区中找到每一个目录项,取出目录项的文件名与 dp->fn[]中的文件名对比,用来确定是否找到对应的目录项,主要步骤:
- dir_sdi(dp, 0)函数用来设置 dp 的当前扇区 dp->sect 等信息。
- 读出当前扇区的信息到 win[]中。
- 然后取出 win[]中的每一个目录项。
- 判断目录项中的文件名是否与 dp->fn[]中的文件名一致。
(3)通过 follow_path()函数的返回值,就可以最终判定出 path 的目标文件是否存在。如果文件存在,在 f_open()的最后会设置文件描述符 fp 的信息:
else { /* Open an existing file */ if (res == FR_OK) { /* Following succeeded */ if (dj.obj.attr & AM_DIR) { /* It is a directory */ res = FR_NO_FILE; } else { if ((mode & FA_WRITE) && (dj.obj.attr & AM_RDO)) { /* R/O violation */ res = FR_DENIED; } } } }
主要设置文件描述符 fp 的当前扇区,文件的打开模式 mode 等信息。
2.3.3 核心思想总结
通过 f_open()函数的过程,核心思想:
- 使用一个 _FDID 结构体记录要解析的簇信息。
- 用 DIR 结构体把 _FDID 结构体对应的簇,解析出对应的扇区。
- 把扇区上面的每个目录项进行分析和目标名字是否匹配。
- 如果最终匹配到文件目录项,就使用 DIR 结构体里面对应的信息,将 FILE 结构体初始化,返回到用户空间。
2.4 f_read 函数分析
下面是 f_read()函数的源码:
FRESULT f_read ( FIL* fp, /* Pointer to the file object */ void* buff, /* Pointer to data buffer */ UINT btr, /* Number of bytes to read */ UINT* br /* Pointer to number of bytes read */ ) { FRESULT res; FATFS *fs; DWORD clst, sect; FSIZE_t remain; UINT rcnt, cc, csect; BYTE *rbuff = (BYTE*)buff; *br = 0; /* Clear read byte counter */ /********* 1.如果文件可读数据不足,调整要读取的数据字节数 **********/ remain = fp->obj.objsize - fp->fptr; if (btr > remain) btr = (UINT)remain; /* Truncate btr by remaining bytes */ for ( ; btr; /* Repeat until all data read */ rbuff += rcnt, fp->fptr += rcnt, *br += rcnt, btr -= rcnt) { if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */ csect = (UINT)(fp->fptr / SS(fs) & (fs->csize - 1)); /* Sector offset in the cluster */ /* 够了一个正簇,需要找到文件的下一个簇的位置 */ if (csect == 0) { /* On the cluster boundary? */ if (fp->fptr == 0) { /* On the top of the file? */ clst = fp->obj.sclust; /* Follow cluster chain from the origin */ } else { /* Middle or end of the file */ if (fp->cltbl) { clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */ } else { clst = get_fat(&fp->obj, fp->clust); /* Follow cluster chain on the FAT */ } } fp->clust = clst; /* Update current cluster */ } sect = clust2sect(fs, fp->clust); /* Get current sector */ sect += csect; cc = btr / SS(fs); /* When remaining bytes >= sector size, */ /************ 2.如果要读出的数据大于 1 个扇区,就先把整数扇区直接读入到用户的缓冲区 ****************/ if (cc) { /* Read maximum contiguous sectors directly */ if (csect + cc > fs->csize) { /* Clip at cluster boundary */ cc = fs->csize - csect; } if (disk_read(fs->drv, rbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR); if ((fp->flag & FA_DIRTY) && fp->sect - sect < cc) { mem_cpy(rbuff + ((fp->sect - sect) * SS(fs)), fp->buf, SS(fs)); } rcnt = SS(fs) * cc; /* Number of bytes transferred */ continue; } /* fp 缓冲区对应的 sect 值与要读的扇区不相同的话,先要把脏扇区回写到磁盘,再从磁盘把数据读入到 fp 对应的缓冲 */ if (fp->sect != sect) { /* Load data sector if not in cache */ if (fp->flag & FA_DIRTY) { /* Write-back dirty sector cache */ if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); fp->flag &= (BYTE)~FA_DIRTY; } if (disk_read(fs->drv, fp->buf, sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); /* Fill sector cache */ } fp->sect = sect; } rcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */ if (rcnt > btr) rcnt = btr; /* Clip it by btr if needed */ /******* 3.把不足一个扇区的内容,通过当前扇区的缓冲区,直接读入到用户缓冲区 ********/ mem_cpy(rbuff, fp->buf + fp->fptr % SS(fs), rcnt); /* Extract partial sector */ } LEAVE_FF(fs, FR_OK); }
读文件相对来说简单一些:
- 找出文件所在的簇的起始位置,这个簇在 f_open()函数的时候已经被确认了。
- 然后在把簇中的内容读取到用户的缓冲区。
核心思想总结
- 在 f_open()函数的时候,fp->obj.objsize 中记录的文件的大小,fp->clust 记录了文件的起始簇号。
- 每次读文件的时候都会更改 fp->fptr 的值,如果 fp->fptr 的值够了一个簇,但是要读取的大小还不足,此时就要找到文件对应的下一个簇的位置,然后更新 fp->clust。
- 在文件描述符的 fp 对应的结构体中,sect 指向当前的读扇区,buf[]缓冲当前的扇区内容。
2.5 f_write 函数分析
下面 f_write()函数的源码:
FRESULT f_write ( FIL* fp, /* Pointer to the file object */ const void* buff, /* Pointer to the data to be written */ UINT btw, /* Number of bytes to write */ UINT* bw /* Pointer to number of bytes written */ ) { FRESULT res; FATFS *fs; DWORD clst, sect; UINT wcnt, cc, csect; const BYTE *wbuff = (const BYTE*)buff; for ( ; btw; wbuff += wcnt, fp->fptr += wcnt, fp->obj.objsize = (fp->fptr > fp->obj.objsize) ? fp->fptr : fp->obj.objsize, *bw += wcnt, btw -= wcnt) { if (fp->fptr % SS(fs) == 0) { /* On the sector boundary? */ csect = (UINT)(fp->fptr / SS(fs)) & (fs->csize - 1); /* Sector offset in the cluster */ /****** 1.如果当前簇已经写完了,那么就要再找出一个空闲的簇 ********/ if (csect == 0) { /* On the cluster boundary? */ if (fp->fptr == 0) { /* On the top of the file? */ clst = fp->obj.sclust; /* Follow from the origin */ if (clst == 0) { /* If no cluster is allocated, */ clst = create_chain(&fp->obj, 0); /* create a new cluster chain */ } } else { /* On the middle or end of the file */ if (fp->cltbl) { clst = clmt_clust(fp, fp->fptr); /* Get cluster# from the CLMT */ } else { clst = create_chain(&fp->obj, fp->clust); /* Follow or stretch cluster chain on the FAT */ } } if (clst == 0) break; /* Could not allocate a new cluster (disk full) */ if (clst == 1) ABORT(fs, FR_INT_ERR); if (clst == 0xFFFFFFFF) ABORT(fs, FR_DISK_ERR); fp->clust = clst; /* Update current cluster */ if (fp->obj.sclust == 0) fp->obj.sclust = clst; /* Set start cluster if the first write */ } if (fp->flag & FA_DIRTY) { /* Write-back sector cache */ if (disk_write(fs->drv, fp->buf, fp->sect, 1) != RES_OK) ABORT(fs, FR_DISK_ERR); fp->flag &= (BYTE)~FA_DIRTY; } sect = clust2sect(fs, fp->clust); /* Get current sector */ if (!sect) ABORT(fs, FR_INT_ERR); sect += csect; cc = btw / SS(fs); /* When remaining bytes >= sector size, */ /******** 2.先把整数倍扇区的内容,直接通过用户 buffer 直接写入磁盘,然后更新 ********/ if (cc) { /* Write maximum contiguous sectors directly */ if (csect + cc > fs->csize) { /* Clip at cluster boundary */ cc = fs->csize - csect; } if (disk_write(fs->drv, wbuff, sect, cc) != RES_OK) ABORT(fs, FR_DISK_ERR); if (fp->sect - sect < cc) { /* Refill sector cache if it gets invalidated by the direct write */ mem_cpy(fp->buf, wbuff + ((fp->sect - sect) * SS(fs)), SS(fs)); fp->flag &= (BYTE)~FA_DIRTY; } wcnt = SS(fs) * cc; /* Number of bytes transferred */ continue; } if (fp->sect != sect && /* Fill sector cache with file data */ fp->fptr < fp->obj.objsize && disk_read(fs->drv, fp->buf, sect, 1) != RES_OK) { ABORT(fs, FR_DISK_ERR); } /******* 3.更新 fp 的当前扇区的值 fp->sect *********/ fp->sect = sect; } wcnt = SS(fs) - (UINT)fp->fptr % SS(fs); /* Number of bytes left in the sector */ if (wcnt > btw) wcnt = btw; /* Clip it by btw if needed */ /****** 4.把不足 1 个扇区的内容写入到 fp->buf,然后标记 fp->flag“脏”,这样在有机会的时候就会把此扇区写入到磁盘 **********/ mem_cpy(fp->buf + fp->fptr % SS(fs), wbuff, wcnt); /* Fit data to the sector */ fp->flag |= FA_DIRTY; } fp->flag |= FA_MODIFIED; /* Set file change flag */ LEAVE_FF(fs, FR_OK); }
f_wirte()和否 _read()的过程大致相同:
- 先把整扇区的内容,直接通过用户的 buffer 直接写入磁盘。
- 再把不足整扇区的内容,写入 fp 对应的 buf。
- 最后调整 fp->sect 的值。
核心思想总结
先把整扇区的内容直接写入到磁盘,把剩余部分写入 fp 的 buf,在之后合适的时机就会写入到对应的扇区,同时更新 fp->sect 的值。总之,写文件描述符 fb 的 sect 也是指向当前扇区,buf 里面存储的是当前 sect 的内容。
至此,完成 FatFs 文件系统顶层函数接口的分析。
扫码关注尚为网微信公众号
原创文章,作者:sunev,如若转载,请注明出处:https://www.sunev.cn/embedded/944.html