FAT32概述
FAT32文件系統由DBR及其保留扇區,FAT1,FAT2 和 DATA 四個部分組成,其機構如下圖:
這些結構是在分區被格式化時創建出來的,含義解釋如下:
DBR及其保留扇區:DBR的含義是DOS引導記錄,也稱爲操作系統引導記錄,在DBR之後往往會有一些保留扇區。
FAT1:FAT的含義是文件分配表,FAT32一般有兩份FAT,FAT1是第一份,也是主FAT。
FAT2:FAT2是FAT32的第二份文件分配表,也是FAT1的備份。
DATA:DATA也就是數據區,是FAT32文件系統的主要區域,其中包含目錄區域。
引用https://www.cnblogs.com/naedzq/p/5877570.html
SD卡上內容
sdcard --------- aa.txt
bb ------------ bb.txt
FATFS數據結構
- FATFS描述了整個FAT32文件系統,主要是由DBR以及FSINFO兩個扇區內的數據解析而來。
- DIR描述了一個文件/目錄項,它描述了文件的名稱,大小,路徑,簇號,扇區號,所在目錄等等。
- FIL描述一個文件對象,操作文件其實就是操作這個文件對象,它包含了文件的讀指針,文件緩衝區buf,文件打開模式,文件的錯誤信息等等。
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 or 2) */
BYTE wflag; /* win[] flag (b0:dirty) */
BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */
WORD id; /* File system mount ID */
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
#if _MAX_SS != _MIN_SS
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 */
#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 volbase; /* Volume start sector */
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 file data at tiny cfg) */
} FATFS;
/* File object structure (FIL) */
typedef struct {
FATFS* fs; /* Pointer to the related file system object (**do not change order**) */
WORD id; /* Owner file system mount ID (**do not change order**) */
BYTE flag; /* File status flags */
BYTE err; /* Abort flag (error code) */
DWORD fptr; /* File read/write pointer (Zeroed on file open) */
DWORD fsize; /* File size */
DWORD sclust; /* File data start cluster (0:no data cluster, always 0 when fsize is 0) */
DWORD clust; /* Current cluster of fpter */
DWORD dsect; /* Current data sector of fpter */
#if !_FS_READONLY
DWORD dir_sect; /* Sector containing the directory entry */
BYTE* dir_ptr; /* Pointer to the directory entry in the window */
#endif
#if _USE_FASTSEEK
DWORD* cltbl; /* Pointer to the cluster link map table (Nulled on file open) */
#endif
#if _FS_LOCK
UINT lockid; /* File lock ID (index of file semaphore table Files[]) */
#endif
#if !_FS_TINY
BYTE buf[_MAX_SS]; /* File data read/write buffer */
#endif
} FIL;
/* Directory object structure (DIR) */
typedef struct {
FATFS* fs; /* Pointer to the owner file system object (**do not change order**) */
WORD id; /* Owner file system mount ID (**do not change order**) */
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 _FS_LOCK
UINT lockid; /* File lock ID (index of file semaphore table Files[]) */
#endif
#if _USE_LFN
WCHAR* lfn; /* Pointer to the LFN working buffer */
WORD lfn_idx; /* Last matched LFN index number (0xFFFF:No LFN) */
#endif
} DIR;
DBR和f_mount
FAT32第0個扇區存的是DBR,裏面主要是包含了文件系統的描述信息。FATFS中的f_mount函數就是對DBR的解析,並且將相應的信息存到數據結構FATFS中。
DBR結構
下圖就是這張SD卡的DBR。
表1解析了上述DBR中的數據。除了表1中給出的數據定義,DBR中其他的數據是跟引導相關的,在這裏我們並不關心。
表1
字節偏移 | 字節長度(字節) | 字段內容及含義 | 值 |
---|---|---|---|
0x0B | 2 | 每扇區字節數 | 0x0200: 512 |
0x0D | 1 | 每簇扇區數 | 0x08 |
0x0E | 2 | 保留扇區 | 0x09CE:2510 |
0x10 | 1 | FAT表個數 | 0x02 |
0x11 | 2 | 保留 | 0x0000 |
0x13 | 2 | 保留 | 0x0000 |
0x15 | 1 | 介質描述符 | 0xF8:硬盤 |
0x16 | 2 | 保留 | 0x0000 |
0x18 | 2 | 每磁道扇區數 | 0x003F |
0x1A | 2 | 磁頭數 | 0x00FF |
0x1C | 4 | 隱藏扇區 | 0x0 |
0x20 | 4 | 該分區扇區總數 | 0x00ECE000:15523840 |
0x24 | 4 | 每FAT扇區數 | 0x3B19:15129 |
0x28 | 2 | 標記 | 0x0000 |
0x2A | 2 | 版本 | 0x0000 |
0x2C | 4 | 根目錄首簇號 | 0x02 |
0x30 | 2 | 文件系統信息扇區號 | 0x0001 |
0x32 | 2 | DBR備份扇區號 | 0x06 |
0x34 | 12 | 保留 | 0x0 |
0x40 | 1 | BIOS驅動器號 | 0x80:無效 |
0x41 | 1 | 保留 | 0x0 |
0x42 | 1 | 擴展引導標記 | 0x29 |
0x43 | 4 | 卷序列號 | 0x66A4EA5B |
0x47 | 11 | 卷標 | NO NAME |
0x52 | 8 | 文件系統類型 | FAT32:0x3233544146 |
FATFS定義
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 or 2) */
BYTE wflag; /* win[] flag (b0:dirty) */
BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */
WORD id; /* File system mount ID */
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
#if _MAX_SS != _MIN_SS
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 */
#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 volbase; /* Volume start sector */
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 file data at tiny cfg) */
} FATFS;
f_mount實現
f_mount實現其實很簡單,就是從存儲介質上讀出DBR解析然後將相應的數據填到FATFS結構體中。 以下代碼我刪掉了一些跟同步的代碼以及一些錯誤判斷,保留最主要的流程。並加入了中文註釋,
FRESULT f_mount (
FATFS* fs, /* Pointer to the file system object (NULL:unmount)*/
const TCHAR* path, /* Logical drive number to be mounted/unmounted */
BYTE opt /* 0:Do not mount (delayed mount), 1:Mount immediately */
)
{
FATFS *cfs;
int vol;
FRESULT res;
const TCHAR *rp = path;
vol = get_ldnumber(&rp); //根據路徑獲取物理卷號,這裏假設只有一張sd卡設備,這裏爲0
if (vol < 0) return FR_INVALID_DRIVE;
cfs = FatFs[vol]; //根據物理卷號獲取FATFS對象指針,static FATFS *FatFs[_VOLUMES];
if (cfs) { //第一次mount的時候,該指針肯定爲空,所以不會走到這裏。
cfs->fs_type = 0; //當重複掛在同一個設備時候,需要將該FatFs對象中的字段清0.
}
if (fs) {
fs->fs_type = 0; //同上
}
FatFs[vol] = fs; //把新的FATFS對象fs註冊到FatFs數組中,以便後面的函數使用。fs是上層傳過來的。
if (!fs || opt != 1) return FR_OK; /* Do not mount now, it will be mounted later */
res = find_volume(&fs, &path, 0); //掛載函數的真正實現
LEAVE_FF(fs, res);
}
從上面代碼可以看到,mount函數真正的實現是在find_volume中實現,下面來看一下該函數的實現。 同樣的,我只保留了一些關鍵流程代碼,將一些錯誤判斷就刪掉了。 雖然代碼看上去比較長,但實際上實現的功能很簡單。
static
FRESULT find_volume ( /* FR_OK(0): successful, !=0: any error occurred */
FATFS** rfs, /* Pointer to pointer to the found file system object */
const TCHAR** path, /* Pointer to pointer to the path name (drive number) */
BYTE wmode /* !=0: Check write protection for write access */
)
{
BYTE fmt;
int vol;
DSTATUS stat;
DWORD bsect, fasize, tsect, sysect, nclst, szbfat;
WORD nrsv;
FATFS *fs;
/* 根據路徑獲取物理卷號 */
*rfs = 0;
vol = get_ldnumber(path);
/* 根據vol從FatFs中取出FATFS對象指針,這個指針在f_mount中註冊了 */
fs = FatFs[vol]; /* Get pointer to the file system object */
*rfs = fs; /* Return pointer to the file system object */
if (fs->fs_type) { /* 這裏判斷文件系統是否已經掛載,因爲find_volume不只是mount函數調用
... 還會被其他函數調用,mount這一級不會走到這裏,因爲此時文件系統還沒有掛載*/
}
fs->fs_type = 0; /* 將fs_type清0 */
fs->drv = LD2PD(vol); /* 將邏輯卷號填入到drv字段 */
stat = disk_initialize(fs->drv); /* 初始化設備,這個函數需要不同的設備驅動來實現,這裏就是初始化SD卡 */
/* Find an FAT partition on the drive. Supports only generic partitioning, FDISK and SFD. */
bsect = 0;
fmt = check_fs(fs, bsect); /*check_fs會從SD卡中讀取0個扇區,並檢查第0扇區是否是DBR*/
/************************************check_fs實現*************************************************
static
BYTE check_fs ( /* 0:FAT boor sector, 1:Valid boor sector but not FAT, 2:Not a boot sector, 3:Disk error */
FATFS* fs, /* File system object */
DWORD sect /* Sector# (lba) to check if it is an FAT boot record or not */
)
{
fs->wflag = 0; fs->winsect = 0xFFFFFFFF; /* Invaidate window */
if (move_window(fs, sect) != FR_OK) /* Load boot record */
return 3;
if (LD_WORD(&fs->win[BS_55AA]) != 0xAA55) /* Check boot record signature (always placed at offset 510 even if the sector size is >512) */
return 2;
if ((LD_DWORD(&fs->win[BS_FilSysType]) & 0xFFFFFF) == 0x544146) /* Check "FAT" string */
return 0;
if ((LD_DWORD(&fs->win[BS_FilSysType32]) & 0xFFFFFF) == 0x544146) /* Check "FAT" string */
return 0;
return 1;
}
************************************************************************************************/
/* An FAT volume is found. Following code initializes the file system object */
if (LD_WORD(fs->win+BPB_BytsPerSec) != SS(fs)) /* 從DBR中讀取扇區大小 */
return FR_NO_FILESYSTEM;
/* 從DBR中讀取每一個FAT大小並存入fsize字段 */
fasize = LD_DWORD(fs->win+BPB_FATSz32);
fs->fsize = fasize;
fs->n_fats = fs->win[BPB_NumFATs]; /* 從DBR中讀取FAT個數,這裏爲2*/
fasize *= fs->n_fats; /* 計算FAT算佔用扇區 */
fs->csize = fs->win[BPB_SecPerClus]; /* 計算每一個cluster所佔用的扇區數,這裏爲8 */
fs->n_rootdir = LD_WORD(fs->win+BPB_RootEntCnt); /* 這個字段在FAT32中沒有,忽略 */
tsect = LD_DWORD(fs->win+BPB_TotSec32); /* 從DBR中讀取改分區所有的扇區數總和 */
nrsv = LD_WORD(fs->win+BPB_RsvdSecCnt); /* 從DBR中讀取保留扇區個數 */
/* 計算DATA區的起始扇區=保留扇區個數+FAT扇區個數 */
sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZ_DIR); /* RSV+FAT+DIR */
nclst = (tsect - sysect) / fs->csize; /* 計算一共有多少個cluster */
fmt = FS_FAT12;
if (nclst >= MIN_FAT16) fmt = FS_FAT16;
if (nclst >= MIN_FAT32) fmt = FS_FAT32; /*這裏fmt是FAT32*/
/* Boundaries and Limits */
fs->n_fatent = nclst + 2; /* 計算FAT區中entry的個數 */
fs->volbase = bsect; /* SD卡起始扇區地址 */
fs->fatbase = bsect + nrsv; /* FAT扇區起始地址*/
fs->database = bsect + sysect; /* DATA扇區起始地址*/
if (fmt == FS_FAT32) {
if (fs->n_rootdir) return FR_NO_FILESYSTEM; /* (BPB_RootEntCnt must be 0) */
fs->dirbase = LD_DWORD(fs->win+BPB_RootClus); /* 從DBR中讀取根目錄起始扇區地址 */
szbfat = fs->n_fatent * 4; /* (Needed FAT size) */
}
/* Initialize cluster allocation information */
fs->last_clust = fs->free_clust = 0xFFFFFFFF;
fs->fsi_flag = 0x80;
if (fmt == FS_FAT32 /*從FSInfo區讀出空閒扇區個數和最後一個分配的扇區地址*/
&& LD_WORD(fs->win+BPB_FSInfo) == 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)
{
fs->free_clust = LD_DWORD(fs->win+FSI_Free_Count);
}
}
fs->fs_type = fmt; /* FAT3 */
fs->id = ++Fsid; /* File system mount ID */
fs->cdir = 0; /* Set current directory to root */
return FR_OK;
}
f_mount函數基本沒有分支,循環,都是順序執行,其邏輯非常簡單,就是從DBR中順序讀出相應數據即可。這裏就不再畫出流程圖。
測試
下面加一些測試代碼來看一看調用f_mount之後FATFS結構是怎麼樣的
void dump_fatfs(FATFS *fatfs_p)
{
printf("fs_type: %x \n", fatfs_p->fs_type);
printf("drv: %x \n", fatfs_p->drv);
printf("csize: %x \n", fatfs_p->csize);
printf("n_fats: %x \n", fatfs_p->n_fats);
printf("wflag: %x \n", fatfs_p->wflag);
printf("fsi_flag: %x \n", fatfs_p->fsi_flag);
printf("id: %x \n", fatfs_p->id);
printf("n_rootdir: %x \n", fatfs_p->n_rootdir);
printf("last_clust:%x \n", fatfs_p->last_clust);
printf("free_clust:%x \n", fatfs_p->free_clust);
printf("n_fatent:%x \n", fatfs_p->n_fatent);
printf("fsize:%x \n", fatfs_p->fsize);
printf("volbase:%x \n", fatfs_p->volbase);
printf("fatbase:%x \n", fatfs_p->fatbase);
printf("dirbase:%x \n", fatfs_p->dirbase);
printf("winsect:%x \n", fatfs_p->winsect);
}
int main(void)
{
FATFS fs;
f_mount(&fs,"0:",1);
dump_fatfs(&fs);
}
打印結果,dump出來的信息和表1所列出的信息一致
fs_type: 3 FAT32
drv: 0 0號設備
csize: 8 每個cluster佔用8個sector
n_fats: 2 一共有兩個FAT,一個FAT作爲備份FAT
wflag: 0
fsi_flag: 0
id: 1
n_rootdir: 0
last_clust:c 最後一個使用的cluster是編號爲c的cluster
free_clust:1d8bf6 空閒cluster個數爲:1d8bf6
n_fatent:1d8c02 FAT表中entry個數爲1d8c02
fsize:3b19 一個FAT佔用3b19個sector
volbase:0 分區起始sector爲0
fatbase:9ce FAT起始sector是9ce
dirbase:2 根目錄起始簇號是2
winsect:1
f_open與文件/目錄項
文件/目錄項
根目錄的數據如下
從中可以看到在根目錄下方有兩個文件/目錄項,aa.txt和bb目錄
aa.txt
目錄bb
從該兩個目錄項中解析得表2和表3
表2
字節偏移 | 字節長度(字節) | 字段內容及含義 | 值 |
---|---|---|---|
0x0 | 8 | 文件名 | AA |
0x8 | 3 | 擴展名 | TXT |
0xB | 1 | 文件屬性 | 0x20->存檔 |
0xC | 1 | 系統保留 | 0x18 |
0xD | 1 | 校驗值 | 0x86 |
0xE | 2 | 文件創建時間 | 0x8C44 |
0x10 | 2 | 文件創建日期 | 0x4E5E |
0x12 | 2 | 文件最後訪問日期 | 0x4CA3 |
0x14 | 2 | 文件起始簇號高16位 | 0x0 |
0x16 | 2 | 文件最近修改時間 | 0x8C4A |
0x18 | 2 | 文件最近修改日期 | 0x4CA3 |
0x1A | 2 | 文件開始簇號低16位 | 0x0006 |
0x1C | 4 | 文件長度 | 0x00000008 |
表3
字節偏移 | 字節長度(字節) | 字段內容及含義 | 值 |
---|---|---|---|
0x0 | 8 | 文件名 | BB |
0x8 | 3 | 擴展名 | |
0xB | 1 | 文件屬性 | 0x10->子目錄 |
0xC | 1 | 系統保留 | 0x08 |
0xD | 1 | 校驗值 | 0xB6 |
0xE | 2 | 文件創建時間 | 0x8C4B |
0x10 | 2 | 文件創建日期 | 0x4CA3 |
0x12 | 2 | 文件最後訪問日期 | 0x4CA4 |
0x14 | 2 | 文件起始簇號高16位 | 0x0 |
0x16 | 2 | 文件最近修改時間 | 0x8C4C |
0x18 | 2 | 文件最近修改日期 | 0x4CA3 |
0x1A | 2 | 文件開始簇號低16位 | 0x0007 |
0x1C | 4 | 文件長度 | 0x00000000 |
f_open
f_open函數比較繁瑣,由於mode的原因,它有許多分支。本文僅以f_open(&file, “0:/aa.txt”, FA_READ)爲例,將f_open函數流程理一遍。這種情況是最簡單的一種情況,文件位於根目錄下,打開選項爲只讀。
FRESULT f_open (
FIL* fp, /* Pointer to the blank file object */
const TCHAR* path, /* Pointer to the file name */
BYTE mode /* Access mode and file open mode flags */
)
{
FRESULT res;
DIR dj;
BYTE *dir;
DEF_NAMEBUF;
/* 根據文件路徑解析出文件系統,文件路徑爲"0:/aa.txt",所以這裏邏輯卷是0盤,0盤的FATFS對象已經存在
* 了FatFs數組中,find_volume會把FatFs[vol]取出來賦值給目錄項結構體中的fs對象dj.fs。
*/
mode &= FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW;
res = find_volume(&dj.fs, &path, (BYTE)(mode & ~FA_READ));
if (res == FR_OK) {
INIT_BUF(dj);
/*這個函數會根據path解析出DIR目錄項所需要的數據,follow path在下面分析,這個函數是解析文件信息最重要的函數
*它將文件/目錄項的信息存放到DIR結構體中。 只要搞懂了follow_path的實現,就搞明白了f_open的實現。
*/
res = follow_path(&dj, path);
/* Create or Open a file */
if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) {
/*這裏以FA_READ爲例,所以創建文件的這個分支沒走到*/
...
else { /* Open an existing file */
/*這些代碼是一些邊界條件的判斷,在這個例子中,不會走到*/
if (res == FR_OK) { /* Follow succeeded */
if (dir[DIR_Attr] & AM_DIR) { /* It is a directory */
res = FR_NO_FILE;
} else {
if ((mode & FA_WRITE) && (dir[DIR_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__WRITTEN;
fp->dir_sect = dj.fs->winsect; /* Pointer to the directory entry */
fp->dir_ptr = dir;
}
/*最後就是把相應的字段填入到fp中*/
if (res == FR_OK) {
fp->flag = mode; /* mode是傳入的參數 */
fp->err = 0; /* error flag打開時清除 */
fp->sclust = ld_clust(dj.fs, dir); /* 從目錄項dj中獲取起始的cluster號*/
fp->fsize = LD_DWORD(dir+DIR_FileSize); /* 從目錄項dj中獲取文件大小 */
fp->fptr = 0; /* 文件指針在打開時爲0 */
fp->dsect = 0;
fp->fs = dj.fs; /* 將文件系統相關的信息賦值給文件對象 */
fp->id = fp->fs->id;
}
}
下面分析f_open中的關鍵函數follow_path,該函數將從文件系統中DATA區查找到相應的文件/目錄項,將其參數填入到DIR結構中。
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 *dir, ns;
/*走到這裏的時候,path已經是去掉盤符的path了,以"0:/aa.txt"爲例
*到這裏的時候,傳入的path是"/aa.txt"
*/
if (*path == '/' || *path == '\\') /* Strip heading separator if exist */
path++;
dp->sclust = 0; /* 從根目錄開始查找文件/目錄項 */
if ((UINT)*path < ' ') { /* 如果是根目錄,就走這裏,本例中不會走到這個分支 */
res = dir_sdi(dp, 0);
dp->dir = 0;
} else { /* Follow path */
for (;;) {
/* create_name這個函數主要是處理字符串,根據path解析出短文件名,並保存到DIR中fn數組。
* 還是以aa.txt爲例,這個函數走完之後,DIR dp中fn文件名數組如下
* FN|0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|
* |A|A| | | | | | | | | | |T |X |T | |
* 這個函數就不展開了,裏面都是一些關於字符串處理的代碼。對理解文件系統並沒有大礙。
*/
res = create_name(dp, &path); /* Get a segment name of the path */
/* dir_find這個函數也非常重要,這個函數根據文件名從根目錄開始查找相應的文件的entry。
* 然後將entry中的數據填充到DIR dp中。
* 這個函數在下面展開。
*/
res = dir_find(dp); /* Find an object with the sagment name */
ns = dp->fn[NS];
dir = dp->dir; /* Follow the sub-directory */
if (!(dir[DIR_Attr] & AM_DIR)) { /* It is not a sub-directory and cannot follow */
/* 在這裏aa.txt不是目錄,所以會走到這個分支,至此,文件中的所需要的信息如開始簇,文件大小
文件名等都已經被解析到了DIR dp中*/
res = FR_NO_PATH; break;
}
dp->sclust = ld_clust(dp->fs, dir);
}
}
return res;
}
下面分析dir_find函數,這個函數在上面講過就從根目錄開始查找對應的目錄項,並將信息存到DIR中。
static
FRESULT dir_find (
DIR* dp /* Pointer to the directory object linked to the file name */
)
{
FRESULT res;
BYTE c, *dir;
/*dir_sdi會把根目錄相關的信息存到dp中,如根目錄起始扇區等等*/
res = dir_sdi(dp, 0); /* Rewind directory object */
if (res != FR_OK) return res;
do {
res = move_window(dp->fs, dp->sect);
/* 走到這,根目錄的第0扇區就被讀到dp->fs->win 緩衝區中
* 此時,dp->dir指向的就是dp->fs->win緩衝區
*/
dir = dp->dir;
c = dir[DIR_Name]; /*讀取目錄項的第一個字節*/
if (c == 0) { res = FR_NO_FILE; break; } /* 如果第一個字節是0,那麼就返回 */
if (!(dir[DIR_Attr] & AM_VOL) && !mem_cmp(dir, dp->fn, 11)) /* 然後比較entry中文件名和解析的文件名是否一致,
* 如果一致,那麼就找到了該文件/目錄項entry,返回
* 此時dp->dir指向的就是緩衝區中該文件的文件/目錄項
*/
break;
/* 如果發現該文件/目錄項不是要尋找的文件,那麼就增加0x20個byte,因爲一個目錄項entry就是20個byte
* 如果超過了一個扇區大小,那麼就增加扇區的位置,在下一次move_window的時候會讀取下一個扇區。
*/
res = dir_next(dp, 0); /* Next entry */
} while (res == FR_OK);
return res;
}
dir_find查找的過程如下圖所示
測試
測試代碼如下:
void dump_file(FIL *file_p)
{
printf("======dump_file======\n");
printf("fs%x \n", file_p->fs);
printf("id:%x \n", file_p->id);
printf("flag:%x \n", file_p->flag);
printf("err:%x \n", file_p->err);
printf("fptr:%x \n", file_p->fptr);
printf("fsize:%x \n", file_p->fsize);
printf("sclust:%x \n", file_p->sclust);
printf("clust:%x \n", file_p->clust);
printf("dsect:%x \n", file_p->dsect);
printf("dir_sect:%x \n", file_p->dir_sect);
printf("dir_ptr:%x \n", file_p->dir_ptr);
printf("cltbl:%x \n\n\n", file_p->cltbl);
}
f_open(&file, "0:/aa.txt", FA_READ);
dump_file(&file);
打印如下:
======dump_file======
fs2000a628 //文件系統對象指針
id:1 //文件系統id
flag:1 //文件打開模式
err:0
fptr:0
fsize:8 //文件大小
sclust:6 //文件開始簇號
clust:0 //文件讀寫指針當前簇號
dsect:0 //文件讀寫指針當前扇區號
dir_sect:8000 //文件目錄項所在扇區
dir_ptr:2000a738
cltbl:0
f_read
在FIL結構體中存在一個buf[512],如果每次讀寫數據的大小都是512(一個扇區)的整數倍,那麼f_read不會將數據緩存。如果一次讀取的數據小於512字節,那麼f_read會讀取一個扇區到FIL的buf中,那麼下一次讀取的時候如果數據落在這個buf中,f_read是不會從磁盤上直接讀,而是直接從buf中將數據copy到用戶buf中。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;
DWORD clst, sect, remain;
UINT rcnt, cc;
BYTE csect, *rbuff = (BYTE*)buff;
*br = 0; /* Clear read byte counter */
remain = fp->fsize - fp->fptr; /* 計算文件未讀數據大小 */
if (btr > remain) btr = (UINT)remain; /* 如果要讀的數據大於剩餘帶下,那麼要讀的數據就等於剩餘數據大小 */
for ( ; btr; rbuff += rcnt, fp->fptr += rcnt, *br += rcnt, btr -= rcnt) {
if ((fp->fptr % SS(fp->fs)) == 0) { /* 判斷讀取的指針是否在sector頭上 */
csect = (BYTE)(fp->fptr / SS(fp->fs) & (fp->fs->csize - 1)); /* 根據fptr計算sector在cluster中的偏移 */
if (!csect) { /* 是否在cluster的頭上 */
if (fp->fptr == 0) { /* 是否在文件頭上 */
clst = fp->sclust; /* clst就等於文件開始簇號 */
} else { /* fptr在文件的中間 */
clst = get_fat(fp->fs, fp->clust); /* 從FAT表中讀取fptr的簇號 */
}
fp->clust = clst; /* 把文件的當前簇號clust更新一下 */
}
sect = clust2sect(fp->fs, fp->clust); /*根據簇號獲取該簇的起始扇區號*/
sect += csect; /*根據偏移算出簇中的扇區偏移,也就是實際要讀取的扇區號*/
cc = btr / SS(fp->fs); /* When remaining bytes >= sector size, */
if (cc) { /* 如果要讀的數據大小大於一個扇區大小 */
if (csect + cc > fp->fs->csize) /* Clip at cluster boundary */
cc = fp->fs->csize - csect;
/*從磁盤上直接讀取數據存到用戶buf中*/
if (disk_read(fp->fs->drv, rbuff, sect, cc))
ABORT(fp->fs, FR_DISK_ERR);
if ((fp->flag & FA__DIRTY) && fp->dsect - sect < cc)
mem_cpy(rbuff + ((fp->dsect - sect) * SS(fp->fs)), fp->buf, SS(fp->fs));
rcnt = SS(fp->fs) * cc; /* Number of bytes transferred */
continue;
}
if (fp->dsect != sect) {
/*如果要讀取的數據小於一個扇區大小(512),那麼從磁盤讀一個扇區到文件buf中,以便下一次讀取使用*/
if (disk_read(fp->fs->drv, fp->buf, sect, 1)) /* Fill sector cache */
ABORT(fp->fs, FR_DISK_ERR);
}
fp->dsect = sect;
}
rcnt = SS(fp->fs) - ((UINT)fp->fptr % SS(fp->fs)); /* Get partial sector data from sector buffer */
if (rcnt > btr) rcnt = btr;
/*從文件buf中讀取數據到用戶buffer中*/
mem_cpy(rbuff, &fp->buf[fp->fptr % SS(fp->fs)], rcnt); /* Pick partial sector */
}
}
f_write
f_write代碼跟f_read很相近,基本上就是f_read的逆操作,最大的差別就是如果f_write寫的數據超過了已有文件的大小,那麼需要在FAT表建立一個新的entry。