文件系统分析了两天,自己都不知道入没入门,现在就把这两天分析的结果总结一下吧!
一、res = f_mount(0,&fs);
首先是挂接根文件系统,为什么要挂接根文件系统内容?因为根文件系统里面会对我们的SD卡进行初始化,除此之外
f_mount函数可以实现在FatFs模块上注册/ 注销一个工作区。 在使用任何其他文件函数之前,必须使用该函数为每个
卷注册一个工作区。要注销一个工作区,只要指定FileSystemObject 为NULL即可,然后该工作区可以被丢
弃。 该函数只初始化给定的工作区,以及将该工作区的地址注册到内部表中,不访问磁盘I/O 层。卷装入过程是在
f_mount函数后或存储介质改变后的第一次文件访问时完成的。
接着分析这个函数是怎么定义的:
/******************************************************************************** * 函数名称: f_mount * 函数说明: 用于挂载/卸载磁盘驱动器 * 输入参数: vol:Logical drive number to be mounted/unmounted * fs:Pointer to new file system object (NULL for unmount) * 返回参数: 挂载标识FR_OK表示成功! * 注意事项: 无 *********************************************************************************/ FRESULT f_mount ( BYTE vol, /* Logical drive number to be mounted/unmounted */ FATFS *fs /* */ ) { FATFS *rfs; if (vol >= _VOLUMES) /* Check if the drive number is valid */ //如果 return FR_INVALID_DRIVE; rfs = FatFs[vol]; /* Get current fs object */ if (rfs) { #if _FS_LOCK clear_lock(rfs); #endif #if _FS_REENTRANT /* Discard sync object of the current volume */ if (!ff_del_syncobj(rfs->sobj)) return FR_INT_ERR; #endif rfs->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(vol, &fs->sobj)) return FR_INT_ERR; #endif } FatFs[vol] = fs; /* Register new fs object */ return FR_OK; }
1、vol = 0,fs = &fs 首先看这个rfs = FatFs[vol];/* Get current fs object */
2、接着我们看到这个 FATFS *FatFs[_VOLUMES],从而知道了FatFs[vol]指向的是个 FATFS类型的结构体
那么到底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,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) */ DWORD last_clust; /* Last allocated cluster */ DWORD free_clust; /* Number of free clusters */ DWORD fsi_sector; /* fsinfo sector (FAT32) */ 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) */ //存放的就是我们读SD卡第一扇区的内容 } FATFS;
到这里我们就可以推断出rfs也应该是FATFS*类型的,所以可以通过rfs ->drv的方法去访问文件系统的各项参数了。下面验证一下:
3、确实在ff.c文件的f_mount函数里面定义了一个这样的变量
FATFS *rfs;
从而验证了我们的猜想。总结一下这几句话的作用
if (vol >= _VOLUMES)/* Check if the drive number is valid */
return FR_INVALID_DRIVE;
rfs = FatFs[vol];/* Get current fs object */
我的理解:挂载的目的就是为了给rfs分配地址,假如没有使用到内存管理malloc的话,使用这种方法也可以的。但是这里的rfs还是NULL的。
4、接着我们可以看看下面的代码,这也是去除了相关宏定义之后的代码
if (rfs) { rfs->fs_type = 0; /* Clear old fs object */ } if (fs) { fs->fs_type = 0; /* Clear new fs object */ } FatFs[vol] = fs; /* Register new fs object */ return FR_OK;
分析:
a、非零为真,那么在这里rfs这个地址肯定是空的,因为给rfs赋值就是NULL。
b、那么fs又是什么东西呢?看传入参数可以知道:fs = &fs ,因为之前这个是定义过了的:FATFS *rfs;。
明显的知道我们传入的fs不为NULL的,所以自然执行了fs->fs_type = 0;将类型清零,只不过,这个类型是
我在主函数里面使用的(新的),那么清零到底表示什么意思呢?其实是有说明的FAT sub-type (0:Not mounted)
这就是表示卸载的意思!
c、好的,卸载掉旧的,那么就可以注册新的了, FatFs[vol] = fs;/* Register new fs object */
总结一下这上面几行代码的作用:
我的理解:就是实现了挂载文件系统的目标文件,也就是给它分配了一个地址空间。而FatFs[vol]这个指针会被其他的ff.c里面的函数调用的
二、接着我们分析一下如何实现打开一个文件的
1、首先看一看main里面的调用
res = f_open(&fdst, "0:/test.txt", FA_CREATE_ALWAYS | FA_WRITE);
然后查看函数定义:这是去除无关宏定义之后的代码:
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; if (!fp) return FR_INVALID_OBJECT; fp->fs = 0; /* Clear file object */ mode &= FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW; res = chk_mounted(&path, &dj.fs, (BYTE)(mode & ~FA_READ)); if (res == FR_OK) { INIT_BUF(dj); res = follow_path(&dj, path); /* Follow the file path */ dir = dj.dir; } /* Create or Open a file */ if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)) { DWORD dw, cl; if (res != FR_OK) { /* No file, create new */ if (res == FR_NO_FILE) /* There is no file to open, create a new entry */ res = dir_register(&dj); mode |= FA_CREATE_ALWAYS; /* File is created */ dir = dj.dir; /* New entry */ } else { /* Any object is already existing */ if (dir[DIR_Attr] & (AM_RDO | AM_DIR)) { /* Cannot overwrite it (R/O or DIR) */ res = FR_DENIED; } else { if (mode & FA_CREATE_NEW) /* Cannot create as new file */ res = FR_EXIST; } } if (res == FR_OK && (mode & FA_CREATE_ALWAYS)) { /* Truncate it if overwrite mode */ dw = get_fattime(); /* Created time */ ST_DWORD(dir+DIR_CrtTime, dw); dir[DIR_Attr] = 0; /* Reset attribute */ ST_DWORD(dir+DIR_FileSize, 0); /* size = 0 */ cl = ld_clust(dj.fs, dir); /* Get start cluster */ st_clust(dir, 0); /* cluster = 0 */ dj.fs->wflag = 1; if (cl) { /* Remove the cluster chain if exist */ dw = dj.fs->winsect; res = remove_chain(dj.fs, cl); if (res == FR_OK) { dj.fs->last_clust = cl - 1; /* Reuse the cluster hole */ res = move_window(dj.fs, dw); } } } } 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; if (res == FR_OK) { /* Follow succeeded */ dir = dj.dir; if (!dir) { /* Current dir itself */ res = FR_INVALID_NAME; } else { if (dir[DIR_Attr] & AM_DIR) /* It is a directory */ res = FR_NO_FILE; } } FREE_BUF(); if (res == FR_OK) { fp->flag = mode; /* File access mode */ fp->sclust = ld_clust(dj.fs, dir); /* File start cluster */ fp->fsize = LD_DWORD(dir+DIR_FileSize); /* File size */ fp->fptr = 0; /* File pointer */ fp->dsect = 0; fp->fs = dj.fs; fp->id = dj.fs->id; /* Validate file object */ } } LEAVE_FF(dj.fs, res); }
2、 if (!fp) return FR_INVALID_OBJECT;
fp->fs = 0;
/* Clear file object */
判断我们传入的参数fp是否为NULL,fp = &fdst 显然我们开始有这个定义FIL fsrc, fdst; 所以不存在为空的情况,谈到这里,我们必定想知道FIL是什么,
下面是精简后的FIL结构体
typedef struct { FATFS* fs; /* Pointer to the related file system object */ WORD id; /* File system mount ID of the related file system object */ BYTE flag; /* File status flags */ BYTE pad1; DWORD fptr; /* File read/write pointer (0ed 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 */ } FIL; fp->fs = 0; /* Clear file object */这里就把fs清零了。
所以得出结论:我们的fp是一个指向FIL;类型结构体的指针。然后把fs清零了。
3、接着分析咯!
mode &= FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW;
这一句是获取权限的问题,没什么好说的,就是一些位操作的问题,得到的权限就是我们传入的权限值
res = chk_mounted(&path, &dj.fs, (BYTE)(mode & ~FA_READ));
重点来分析一下如何实现check mount的,代码比较啰嗦有170行左右。懒得贴。函数原型是这个
static FRESULT chk_mounted ( /* FR_OK(0): successful, !=0: any error occurred */ const TCHAR **path, /* Pointer to pointer to the path name (drive number) */ FATFS **rfs, /* Pointer to pointer to the found file system object */ BYTE wmode /* !=0: Check write protection for write access */ )
4、首先看传入的参数,path路径是个二级指针,和我们预想的一样,最初传入的path是"0:/test.txt",然后取它的地址传入chk_mounted就是一个二级指针了
类似的我们还出现了这个&dj.fs,,f_open中之前是这样定义的DIR dj;所以就不能忽略这个结构体了。
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;
很明朗,&dj.fs也是一个二级指针。然后权限这个参数就不多说了。
接下来就是在check函数里面分析路径了,首先明确我们的路径是"0:/test.txt"
/* Get logical drive number from the path name */ vol = p[0] - '0'; /* Is there a drive number? */ if (vol <= 9 && p[1] == ':') { /* Found a drive number, get and strip it */ p += 2; *path = p; /* Return pointer to the path name */ } else { /* No drive number is given */ vol = 0; /* Use drive 0 */ }
5、看了上面的代码,可以得出如下信息:
a、路径中的0指定的是哪一个磁盘驱动器,但是这里我们只管理一个,那就是SD卡。
b、假如我们没有指定哪一个磁盘驱动器的话,那么就是默认的使用0号磁盘驱动器
c、假如我们指定哪一个磁盘驱动器的话,那么,绝对路径就向右偏移两个位置也就是“/test.txt”
OK路径部分解决,接着看:又是检查磁盘驱动器是否有效了
*rfs = 0; if (vol >= _VOLUMES) /* Is the drive number valid? */ return FR_INVALID_DRIVE; fs = FatFs[vol]; /* Get corresponding file system object */ if (!fs) return FR_NOT_ENABLED; /* Is the file system object available? */ ENTER_FF(fs); /* Lock file system */ *rfs = fs; /* Return pointer to the corresponding file system object */ 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 (has not been changed), */ if (!_FS_READONLY && wmode && (stat & STA_PROTECT)) /* Check write protection if needed */ return FR_WRITE_PROTECTED; return FR_OK; /* The file system object is valid */ } }
看到了吧, fs = FatFs[vol];果然验证了我们之前的说法,在这个文件里面就用到了这个指针
这个宏定义暂时不明白有什么作用,标志性的还是什么?不理解 ENTER_FF(fs);/* Lock file system */
6、下面那段代码使用来检测挂载状态的,涉及到写保护,初始化的问题,不用管,其实我也不是很了解。现在只是在分析代码,接下来的代码就是
进行磁盘的初始化了。
fs->fs_type = 0; /* Clear the file system object */ fs->drv = LD2PD(vol); /* Bind the logical drive and a physical drive */ 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 */
这里的LD2PD(vol)其实就是vol,只不过类型不一样,所以自然而然的,传入的drv也就是0了。由于初始化disk之前已经分析过,其实就是初始化SD卡
所以这里就不分析了。
接着就是这个函数
fmt = check_fs(fs, bsect = 0);/* Load sector 0 and check if it is an FAT-VBR (in SFD) */
跟踪进去,可以看到这个函数的具体定义如下
s
tatic BYTE check_fs ( /* 0:FAT-VBR, 1:Any BR but not FAT, 2:Not a BR, 3:Disk error */ FATFS *fs, /* File system object */ DWORD sect /* Sector# (lba) to check if it is an FAT boot record or not */ ) { if (disk_read(fs->drv, fs->win, sect, 1) != RES_OK) /* Load boot record */ return 3; if (LD_WORD(&fs->win[BS_55AA]) != 0xAA55) /* Check 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) return 0; return 1; }
分析函数可以总结一下几点信息:
a、这是一个检测磁盘文件系统的函数,首先检测的是读取第0扇区的内容是否成功,并且将内容存入win[]里面
b、然后检测的是签名是否为0xAA55,这个信息在我的文件系统1那个博客里面能够看到。
c、接着就是检测win[54]、win[82]中的内容是否为0x544146。从我前一个博客的内容中的分析可以知道这个正好是检测文件系统类型的
这里win[54]表示了FAT16的文件系统类型。顿悟了吧?
7、还有更精彩的,继续吧,接下来就是计算偏移地址啦!
关于这一部分的代码,必需得参考之前的那篇博文才能有所感悟的。
if (LD_WORD(fs->win+BPB_BytsPerSec) != SS(fs)) /* (BPB_BytsPerSec must be equal to the physical sector size) */ return FR_NO_FILESYSTEM;
可以发现这里用到了一系列的类似于LD_WORD(ptr)这样的数据类型,这都是对地址进行强制转换的宏。挑一个来分析一下
#define LD_WORD(ptr)(WORD)(((WORD)*((BYTE*)(ptr)+1)<<8)|(WORD)*(BYTE*)(ptr))
假如ptr = 0x20000378+0x0b = 0x20000383 那么
a、首先将ptr转化为BYTE类型指针(ptr指向数据类型就是BYTE),然后取指针里面的值(BYTE类型),最后将值转化为WORD类型
b、接着就是前面部分是将(ptr+1)里面的值(BYTE)转化为WORD类型,然后左移8位。(*的优先级比++的高)
c、然后是将两个WORD类型的内容进行或操作;
d、最后将或操作的结果转换为WORD类型。
总结这个宏定义的作用如下:
将(ptr)里面内容的和(ptr+1)进行或操作,然后转化为WORD类型数据。也就是取地址里面的内容!!
if (LD_WORD(fs->win+BPB_BytsPerSec) != SS(fs))
根据之前的那个表格可以知道这里的 win表示的是我们读取第一扇区的首地址,那么加上0x0b的话,那么此时的
ptr里面的内容就是00 (ptr+1)里面的内容就是02,经过转换,那么内容就变成了0x0200 = 512这样就得到我们想要的结果了,所以这就是判断每一个
扇区是否就是512字节
8、继续之前的分析
fasize = LD_WORD(fs->win+BPB_FATSz16); if (!fasize) fasize = LD_DWORD(fs->win+BPB_FATSz32); fs->fsize = fasize; fs->n_fats = b = fs->win[BPB_NumFATs]; /* Number of FAT copies */ if (b != 1 && b != 2) return FR_NO_FILESYSTEM; /* (Must be 1 or 2) */ fasize *= b; /* Number of sectors for FAT area */ fs->csize = b = fs->win[BPB_SecPerClus]; /* Number of sectors per cluster */ if (!b || (b & (b - 1))) return FR_NO_FILESYSTEM; /* (Must be power of 2) */ tsect = LD_WORD(fs->win+BPB_TotSec16); /* Number of sectors on the volume */ if (!tsect) tsect = LD_DWORD(fs->win+BPB_TotSec32); nrsv = LD_WORD(fs->win+BPB_RsvdSecCnt); /* Number of reserved sectors */ if (!nrsv) return FR_NO_FILESYSTEM; /* (BPB_RsvdSecCnt must not be 0) */
这又是一个类型转换:
#define LD_DWORD(ptr)(DWORD)(((DWORD)*((BYTE*)(ptr)+3)<<24)|((DWORD)*((BYTE*)(ptr)+2)<<16)|((WORD)*((BYTE*)(ptr)+1)<<8)|*(BYTE*)(ptr))
类似的可以得出下列信息:
a、表示每个FAT占有扇区数,这里为0xf3 = 243 secs
b、两个FAT表,共486扇区
c、每簇扇区数为32个
d、根目录项数为512
e、容量(总扇区数)为0x001E5C00 = 971.5M
f、保留扇区数为2
9、好的,我们继续看代码
/* Determine the FAT sub type */ sysect = nrsv + fasize + fs->n_rootdir / (SS(fs) / SZ_DIR); /* RSV+FAT+DIR */ if (tsect < sysect) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ nclst = (tsect - sysect) / fs->csize; /* Number of clusters */ if (!nclst) return FR_NO_FILESYSTEM; /* (Invalid volume size) */ fmt = FS_FAT12; if (nclst >= MIN_FAT16) fmt = FS_FAT16; if (nclst >= MIN_FAT32) fmt = FS_FAT32;
a、sysect = 保留扇区+FAT表总扇区+根目录扇区 = 520这就是系统扇区数
b、nclst表示有62159个簇
c、根据簇的大小可以区别出是FAT16的文件系统
10、看到这里,对照着之前的那篇博文,越看越有意思了。
/* Boundaries and Limits */ fs->n_fatent = nclst + 2; /* Number of FAT entries */ fs->database = bsect + sysect; /* Data start sector */ fs->fatbase = bsect + nrsv; /* FAT start sector */
信息如下:
a、FAT表的入口簇数,记住我们数据从第2簇开始的
b、数据区起始位置,当然是从我们的系统区之后的扇区数开始,也就是520扇区之后
c、FAT表的其实位置就是在我们的保留区之后,记住启动区是第0扇区的
11、继续看地址是如何确定的
fs->dirbase = fs->fatbase + fasize; /* Root directory start sector */ szbfat = (fmt == FS_FAT16) ? /* (Required FAT size) */
fs->n_fatent * 2 : fs->n_fatent * 3 / 2 + (fs->n_fatent & 1);
a、表示根目录的地址就是在fat表起始扇区(保留扇区的末尾)+fat表的大小
12、初始化可用的数据簇
/* Initialize cluster allocation information */ fs->free_clust = 0xFFFFFFFF; fs->last_clust = 0;
至此我们就初步完成了对一些信息地址的初始化。也就是完成了对文件系统描述符fs结构体的填充。
13、继续看打开文件的函数
INIT_BUF(dj); //初始化了 res = follow_path(&dj, path); /* Follow the file path */ dir = dj.dir;
这里就需要看几个宏
static WCHAR LfnBuf[_MAX_LFN+1]; #define DEF_NAMEBUF BYTE sfn[12] #define INIT_BUF(dobj) { (dobj).fn = sfn; (dobj).lfn = LfnBuf; }
大概知道了这是文件名的格式,一个是短文件名8.3.1,另外一个是长文件名有256个字节的空间,所以我们大概可以猜出,文件名最长为32个字
上面5.c提到绝对路径是这个 path = “/test.txt”那么我们就好分析res = follow_path(&dj, path)这行代码了,跟踪进去。
可以看到:
if (*path == '/' || *path == '\\') /* Strip heading separator if exist */ path++; dj->sclust = 0; /* Start from the root dir */
明显第一个内容就是‘/’那么接着就只想系一个字符,同时把起始簇清零
14、接着就是判断路径的问题了
if ((UINT)*path < ' ') { /* Nul path means the start directory itself */ res = dir_sdi(dj, 0); dj->dir = 0;
这里判断的是'/'字符后面第一个字符是否为空,显然这里不为空,到这里路径名称就变为"test.txt"
15、接下来又是一个大头的函数,这个函数只是大概分析一下,C语言知识不够,到这里,就有些晕乎乎的了。
res = create_name(dj, &path);/* Get a segment */
从名字上来看这是获取是个段名(理解为文件名),这个函数的作用从path中抽出第一层的名称test.txt,
如果有子目录就调整path=“/xxx.txt”,处理test变为标准短文件名。
16、进入create_name函数可以看到这样一个循环函数
for (;;) { w = p[si++]; /* Get a character */ if (w < ' ' || w == '/' || w == '\\') break; /* Break on end of segment */ if (di >= _MAX_LFN) /* Reject too long name */ return FR_INVALID_NAME; #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 */ if (!IsDBCS2(b)) return FR_INVALID_NAME; /* Reject invalid sequence */ w = (w << 8) + b; /* Create a DBC */ } 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)) /* Reject illegal chars for LFN */ return FR_INVALID_NAME; lfn[di++] = w; /* Store the Unicode char */ }
从上面的函数我们可以提取下面几点信息
a、当我们的path中出现'/'、"\\"、' '的时候就会跳出,所以是剥离第一层目录。
b、检查长文件名中是否存在不合法的字符,显然这会和路径相冲突的。
c、最后把长文件名存放在lfn这个数组里面
17、两行代码
*path = &p[si]; /* Return pointer to the next segment */ cf = (w < ' ') ? NS_LAST : 0; /* Set last segment flag if end of path */
*path = &p[si]; 如果有下一子目录,那么现在*path直接指向“xxx.xxx”,前面的/都已经去掉了。
c = (c <= ' ') ? NS_LAST : 0;获取名字是否已经结束。
18、到这里我们就返回到follow_path函数吧
继续又是一个函数
res = dir_find(dj);/* Find it */
找目录呗!进去
res = dir_sdi(dj, 0); /* Rewind directory object */ static FRESULT dir_sdi ( DIR *dj, /* Pointer to directory object */ WORD idx /* Index of directory table */ ) { DWORD clst; WORD ic; dj->index = idx; clst = dj->sclust; if (clst == 1 || clst >= dj->fs->n_fatent) /* Check start cluster range */ return FR_INT_ERR; if (!clst && dj->fs->fs_type == FS_FAT32) /* Replace cluster# 0 with root cluster# if in FAT32 */ clst = dj->fs->dirbase; if (clst == 0) { /* Static table (root-dir in FAT12/16) */ dj->clust = clst; if (idx >= dj->fs->n_rootdir) /* Index is out of range */ return FR_INT_ERR; dj->sect = dj->fs->dirbase + idx / (SS(dj->fs) / SZ_DIR); /* Sector# */ } dj->dir = dj->fs->win + (idx % (SS(dj->fs) / SZ_DIR)) * SZ_DIR; /* Ptr to the entry in the sector */ return FR_OK; /* Seek succeeded */ }
提取一下信息:
a、这是在第一层次的目录里面,FAT16/FAT12、和FAT32是不同的计算方法
b、第一层次目录,从2号簇也就是根目录区簇开始。
c、计算出一簇的最大索引号127<128
d、dj->sect = clust2sect(dj->fs, clst) + idx / (SS(dj->fs) /SZ_DIR);将簇号和目录项索引号转换为对应扇区号,
e、dj->dir = dj->fs->win + (idx % (SS(dj->fs) / SZ_DIR)) * SZ_DIR;此时目录项所指只是指定索引号目录项对应的扇区
缓冲中的地址。此时缓冲区还没有读入根目录扇区,所以此时指针指向的内容无效。
19、我们返回到dir_find(dj)这个函数
res = move_window(dj->fs, dj->sect);执行完这句,文件系统缓冲区已经读入了 根目录区第一扇区的内容。
跟踪进去可以看到:
if (sector) { if (disk_read(fs->drv, fs->win, sector, 1) != RES_OK) return FR_DISK_ERR; fs->winsect = sector;
实现了对SD卡的读操作
20、接着又是进入一个函数
res = dir_next(dj, 0);/* Next entry */
下一个入口,可以说这个函数主要是处理查找目录项递增时,如果扇区数和簇号发生变化时的处理。进去吧,精简之后的代码
static FRESULT dir_next ( /* FR_OK:Succeeded, FR_NO_FILE:End of table, FR_DENIED:EOT and could not stretch */ DIR *dj, /* Pointer to directory object */ int stretch /* 0: Do not stretch table, 1: Stretch table if needed */ ) { DWORD clst; WORD i; stretch = stretch; /* To suppress warning on read-only cfg. */ i = dj->index + 1; if (!i || !dj->sect) /* Report EOT when index has reached 65535 */ return FR_NO_FILE; dj->index = i; dj->dir = dj->fs->win + (i % (SS(dj->fs) / SZ_DIR)) * SZ_DIR; return FR_OK; }
这样看起来是还是比较简单的。
21、返回到open函数里面看这样一段代码
if (res == FR_OK && (mode & FA_CREATE_ALWAYS)) { /* Truncate it if overwrite mode */ dw = get_fattime(); /* Created time */ ST_DWORD(dir+DIR_CrtTime, dw); dir[DIR_Attr] = 0; /* Reset attribute */ ST_DWORD(dir+DIR_FileSize, 0); /* size = 0 */ cl = ld_clust(dj.fs, dir); /* Get start cluster */ st_clust(dir, 0); /* cluster = 0 */ dj.fs->wflag = 1; if (cl) { /* Remove the cluster chain if exist */ dw = dj.fs->winsect; res = remove_chain(dj.fs, cl); if (res == FR_OK) { dj.fs->last_clust = cl - 1; /* Reuse the cluster hole */ res = move_window(dj.fs, dw); } } } }
这是对标志位以及其他功能的填充,看这里有个获取时间的函数,这就是我们移植的时候需要添加上get_fattime函数的原因了。
22、这都是填充结构体
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; #if _FS_LOCK fp->lockid = inc_lock(&dj, (mode & ~FA_READ) ? 1 : 0); if (!fp->lockid) res = FR_INT_ERR; #endif } #else /* R/O configuration */ if (res == FR_OK) { /* Follow succeeded */ dir = dj.dir; if (!dir) { /* Current dir itself */ res = FR_INVALID_NAME; } else { if (dir[DIR_Attr] & AM_DIR) /* It is a directory */ res = FR_NO_FILE; } } #endif FREE_BUF(); if (res == FR_OK) { fp->flag = mode; /* File access mode */ fp->sclust = ld_clust(dj.fs, dir); /* File start cluster */ fp->fsize = LD_DWORD(dir+DIR_FileSize); /* File size */ fp->fptr = 0; /* File pointer */ fp->dsect = 0; #if _USE_FASTSEEK fp->cltbl = 0; /* Normal seek mode */ #endif fp->fs = dj.fs; fp->id = dj.fs->id; /* Validate file object */
就是将一些打开的文件的信息填充进去而已,方便我们之后的读写操作。
至此,打开函数终于是分析完了,一个这样的函数花了我一天一夜。纵然是这样,我还是有很多不是很了解的地方,也只好先这样了。
其实我觉得是这样的,打开函数分析完了,那么其他的读写、关闭函数都是很多地方相似的。
三、看写函数吧110行代码。不列举。
res = f_write(&fdst, Test_Buffer, sizeof(Test_Buffer), &bw); /* Write it to the dst file */
函数功能解析:将Test_Buffer里面sizeof(Test_Buffer)大小的内容写到&fdst目标文件中去,而bw指向的是这个大小的指针
这个函数就不会那么具体的分析了。实在是太累。学习进度比较慢,然后知识水平又不够,准备弄完文件系统,又要恶补一下C了。
进去后单步调试能看到这个
if (disk_write(fp->fs->drv, wbuff, sect, (BYTE)cc) != RES_OK)
ABORT(fp->fs, FR_DISK_ERR);
这就是将临时缓冲区里面的内容写入到当前扇区。注意这里可不是启动扇区,这是我们打开文件对应的扇区。
//当写的内容超过32个扇区后,要重新分配新的簇,可以从下面的代码中看出
if ((fp->fptr % SS(fp->fs)) == 0) {/* On the sector boundary? */
怎么找新的簇呢?就是利用FAT表来找的。
四、接着是关闭文件
我们得明确一点,加入我们没有执行关闭文件的话,那么写入的信息就得不到更新。
那么这个关闭文件的函数就有如下两个作用:
1、更新信息
2、关闭文件,斩断文件和程序的联系
看代码
f_close(&fdst);
做个代码精简就是这样的:很简单的呀
FRESULT f_close ( FIL *fp /* Pointer to the file object to be closed */ ) { FRESULT res; res = f_sync(fp); /* Flush cached data */ if (res == FR_OK) fp->fs = 0; /* Discard file object */ return res; }
果然,flush DATA 太平年公布数据 然后将文件符清零。赋一个NULL
五、接下来看看读文件的流程,肯定不会那样子系分析了。
1、代开文件和关闭文件都还是一样,不祥述。只是注意打开方式
2、 res = f_read(&fsrc, buffer, sizeof(Test_Buffer), &br); /* Read a chunk of src file */
看看这个读文件的函数吧,这个相对来说还简单一些的
底层接口代码在这里
if (disk_read(fp->fs->drv, fp->buf, sect, 1) != RES_OK) /* Fill sector cache */
就是将文件对应的扇区里面的内容读到临时缓存里面去。
文件读写流程就初步分析到这里,总之还有很多不完善的地方,等以后再说吧!
之后就是一些自己要添加的类似于uboot命令的功能,按个人需要添加,改天移植一个功能强大的文件系统,那就爽到了。