<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
八、读文件执行流程分析
l 输入读命令fread 0:/111/aaa.txt
在我的SD卡,根目录下有两个可见文件aaa.txt和ccc.txt,两个目录111和222。目录111下面有三个文件aaa.txt、bbb.txt、ccc.txt。上面这个命令的作用是读取111目录下aaa.txt的内容,这个文件的大小是0x0564,里面都是12345这些数字。
在我的程序的开头,首先执行了这么一个函数:
FATFS FileSys;
res=f_mount(0,&FileSys); //这个函数的作用是将ff.c内定义的FATFS *FatFs[_DRIVES]这个文件系统结构指针指向用户定义的文件系统结构。初始化程序填充这个结构,而其它函数可以使用里面的参数。
程序对输入命令进行解析,得到argc=2, argv[0]=”fread”, argv[1]=”0:/111/aaa.txt”。系统调用UartCmdFRead函数。
l 文件执行到f_open(&FileInf,argv[1],FA_READ | FA_OPEN_ALWAYS);
FileInf是前面定义的一个文件信息结构体,将指针传递给这个函数,是程序获得argv[1]文件的信息后,填充该结构体的。
单步执行进去,首先定义了DIR dj,这是目录信息结构体,然后NAMEBUF(sfn, lfn);作用是为文件名准备空间。Sfn所指向的空间最后存放标准短文件名,比如aaa.txt,存进去应该是aaa txt。
u 然后执行res = chk_mounted(&path, &dj.fs, (BYTE)(mode & (FA_WRITE | FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW)));这个函数的主要作用是初始化磁盘、检查是否存在文件系统等,这里这个path= argv[1]= ”0:/111/aaa.txt”。
进入到这个函数,首先const XCHAR *p = *path;这个path= &argv[1],而p= argv[1],实际指向0:/中的0。然后执行
vol = p[0] - '0';
if (vol <= 9 && p[1] == ':') {
p += 2; *path = p; } 执行完这句以后*path=p指向111前面的/。
*rfs = fs = FatFs[vol];执行完这句,目录结构的指针指向用户定义的文件系统结构体
stat = disk_initialize(fs->drive); 这句对磁盘进行初始化,成功则磁盘可用。
u fmt = check_fs(fs, bsect = 0);这句是检查是否存在文件系统。进入这个函数,
if (disk_read(fs->drive, fs->win, sect, 1) != RES_OK) /*读取引导扇区 */
return 3; 这里读入缓冲区是文件系统结构体中自带的缓冲区,其地址为0x2000,1FB0。
if (LD_WORD(&fs->win[BS_55AA]) != 0xAA55) /* 检查引导标志 */
return 2;
if ((LD_DWORD(&fs->win[BS_FilSysType32]) & 0xFFFFFF) == 0x544146)
return 0; //这三个字符表式FAT
l 回到chk_mounted函数,
fsize *= fs->n_fats;
fs->fatbase = bsect + LD_WORD(fs->win+BPB_RsvdSecCnt);
fs->csize = fs->win[BPB_SecPerClus];
fs->n_rootdir = LD_WORD(fs->win+BPB_RootEntCnt); /* Nmub
这些实际上是利用引导扇区里的信息对文件系统结构体进行填充,这里再熟悉一下这个结构。
typedef struct _FATFS_ {
BYTE fs_type; /* FAT类型, FAT32的数值为3*/
BYTE csize; /* 每簇多少扇区 一般为8*/
BYTE n_fats; /* FAT表的数目,一般为2 */
BYTE wflag; /* 扇区缓冲内容是否修改过,为1时要写回磁盘) */
BYTE fsi_flag; /* FAT32特有,当空闲簇数目和当前可分配簇改变时,信息要写入FSI扇区,一般都是1号扇区 */
WORD n_rootdir ; /* FAT32没有用到,为0 */
DWORD last_clust; /* 可分配簇簇号*/
DWORD free_clust; /* 空闲簇的数量 */
DWORD fsi_sector; /* fsinfo扇区,为1 */
DWORD sects_fat; /* 每个分配表所占用的扇区,我的SD卡上为0x3cc */
DWORD max_clust; /* 最大簇号,可用簇=最大簇号-2,这个一般是根据磁盘最大扇区数-保留-FAT表*2后再除以每簇扇区数得到的,我的SD卡上是0x1E58B,这个数据也是区分FAT16和FAT32的依据。 */
DWORD fatbase; /* FAT表开始扇区,保留扇区后,一般为32 */
DWORD dirbase; /* 根目录开始扇区,根目录一般占用2号簇*/
DWORD database; /* 数据区开始簇号,就是根目录开始扇区,2号簇,紧跟在FAT表后,故其数值为32+2*0x3cc=0x7B8=1976号扇区 */
DWORD winsect; /* 当前缓冲区内容对应的扇区号 */
BYTE win[512];/* D目录和文件系统工作所用扇区缓冲区。 */
} FATFS;
l 回到f_open函数
如果chk_mounted返回FR_OK,则磁盘上文件系统可用。且文件系统的关键信息都已经写入结构体。
INITBUF(dj, sfn, lfn);这是对目录结构体的一次操作,有必要先了解一下:
typedef struct _DIR_ {
FATFS* fs; /* 指向文件系统 */
WORD index; /* 当前的目录项索引号,一项占32个字节 */
DWORD sclust; /* 当前目录的开始簇号 */
DWORD clust; /* 当前扇区对应的当前簇,因为一个目录可能占据多簇 */
DWORD sect; /* 当前扇区号 */
BYTE* dir; /* 指向文件系统缓冲扇区win[] 里短文件名对应的目录项*/
BYTE* fn; /* 指向标准短文件名数组 */
} DIR;
INITBUF(dj, sfn, lfn)执行完后,fn指向标准短文件名数组 。
res = follow_path(&dj, path);这个函数的作用是从文件路径中一层一层取出目录名和文件名。然后将最后一级目录扇区所在簇、扇区号、最后对应文件在目录扇区中的位置填入目录机构体。下面跟踪进去看一下。
u 进入函数follow_path(&dj, path)
现在这个path=”/111/aaa.txt”,path指向/。
if (*path == '/' || *path == '\\') /* Strip heading separator if exist */
path++; 执行完这句后,path=”111/aaa.txt”,前面的/已经跳过。
进入获取目录、文件名的循环:
for (;;) { res = create_name(dj, &path); 这个函数的作用从path中抽出第一层的名称111,调整path=“/aaa.txt”,处理111变为标准短文件名。
u 进入函数create_name(dj, &path)
sfn = dj->fn;
mem_set(sfn, ' ', 11); 将目录结构体指向的短文件名数组填充空格
si = i = b = 0; ni = 8;
p = *path; 现在p指向”111/aaa.txt”。
for (;;) {
c = p[si++];
if (c <= ' ' || c == '/' || c == '\\') break; 这句的作用现在可以看明白了,遇到111后面的/时,循环退出,本层的。
sfn[i++] = c; 取出路径名中的文件名存入目录的标准短文件名数组。}
*path = &p[si]; 现在*path直接指向“aaa.txt”,前面的/都已经去掉了。
c = (c <= ' ') ? NS_LAST : 0; 获取名字是否已经结束。
返回到函数follow_path后,马上又进入新的函数dir_find(dj),这个函数的作用是找到目录结构体dj->fn对应的dj->dir,使它直接指向目录项,并获取该目录项的起始簇号等信息。
u 进入函数dir_find(dj)
马上又进入函数res = dir_seek(dj, 0);这是从索引0开始。第二个参数是目录项索引号。
dj->index = idx;
clst = dj->sclust;
if (!clst && dj->fs->fs_type == FS_FAT32) /* Replace cluster# 0 with root cluster# if in FAT32 */ clst=0说明是第一层次的目录。
clst = dj->fs->dirbase;第一层次目录,从2号簇也就是根目录区簇开始。
ic = SS(dj->fs) / 32 * dj->fs->csize; /* 计算出一簇的最大索引号127<128 */
dj->sect = clust2sect(dj->fs, clst) + idx / (SS(dj->fs) / 32);将簇号和目录项索引号转换为对应扇区号,
dj->dir = dj->fs->win + (idx % (SS(dj->fs) / 32)) * 32;此时目录项所指只是指定索引号目录项对应的扇区缓冲中的地址。此时缓冲区还没有读入根目录扇区,所以此时指针指向的内容无效。
u 进入函数dir_find(dj)
do {
res = move_window(dj->fs, dj->sect); 执行完这句,文件系统缓冲区已经读入了根目录区第一扇区的内容。
if (res != FR_OK) break;
dir = dj->dir;
c = dir[DIR_Name]; 取得第一个字符,如果为0,则找不到文件
if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of table */
if (!(dir[DIR_Attr] & AM_VOL) && !mem_cmp(dir, dj->fn, 11))
break; 如果经比较就是该目录项,则index和dir指针保留。跳出循环表明已经找到目录或文件。
res = dir_next(dj, FALSE); /* 进入函数dir_next(dj, FALSE) */
} while (res == FR_OK);
u 进入函数dir_next(dj, FALSE)
i = dj->index + 1; 用于查找下一项。
dj->index = i;
dj->dir = dj->fs->win + (i % (SS(dj->fs) / 32)) * 32;
可以说这个函数主要是处理查找目录项递增时,如果扇区数和簇号发生变化时的处理。
u 进入函数dir_find(dj)
开始循环,又执行res = move_window(dj->fs, dj->sect);此时缓冲区扇区与所要读取扇区内容一致,所以这个函数直接返回。
当查找到索引11的时候,目录项被找到了,于是
if (!(dir[DIR_Attr] & AM_VOL) && !mem_cmp(dir, dj->fn, 11))
break;从这里跳出,回到上一层函数create_name(dj, &path),此时目录结构体中的sclust、clust、sect和dir三个存储了足够的读取该目录的信息。
l 回到函数follow_path(&dj, path)
res = create_name(dj, &path); /* Get a segment */
if (res != FR_OK) break;
res = dir_find(dj);
last = *(dj->fn+NS) & NS_LAST; 从这句开始进行,在create_name(dj, &path)执行时已经设置了路径是否结束的标志位,这里进行读取。
if (last) break; /* 如果有结束标志,说明最后的名字是个文件,dir已经指向该文件的目录项,所有的文件信息都可以由此而得,直接跳出循环。. */
dir = dj->dir; /* 如果没有结束,说明这可定是个目录 */
if (!(dir[DIR_Attr] & AM_DIR)) { /* 如果其性质不是目录,错误跳出循环 */
res = FR_NO_PATH; break; }
dj->sclust = ((DWORD)LD_WORD(dir+DIR_FstClusHI) << 16) | LD_WORD(dir+DIR_FstClusLO);
}
l 循环执行,又回到上面,进入函数create_name.
res = create_name(dj, &path);
这次执行时path=“aaa.txt”,转换成标准短文件名存入dj->fn所指向的缓冲区。
*path = &p[si]; /* Rerurn pointer to the next segment */
c = (c <= ' ') ? NS_LAST : 0; /* Set last segment flag if end of path */
在拷贝完成后path=null,并在dj->fn所指向的缓冲区设置了结束标志。
if ((b & 0x03) == 0x01) c |= NS_EXT; /* 扩展名都是小写*/
if ((b & 0x<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />0C) == 0x04) c |= NS_BODY; /*文件名都是小写 */
sfn[NS] = c; c包括结束和文件名的大小写信息。存入目录结构体信息区。
l 循环执行,又回到上面,进入函数dir_find(dj).
res = dir_find(dj); 这次开始执行时sclust指向6号簇,这是111目录所在的簇。
res = dir_seek(dj, 0);仍然是从索引0开始搜寻目录项。这个函数执行完成后clust也指向6号簇,sector等于此簇对应的第一个扇区,dir指向扇区缓冲对应索引位置。
这下在索引2位置就找到了该文件“aaa.txt”,索引很快返回
l 回到函数follow_path(&dj, path)
res = create_name(dj, &path); /* Get a segment */
if (res != FR_OK) break;
res = dir_find(dj); /* Find it */
last = *(dj->fn+NS) & NS_LAST; 这个标志位被设置。表明路径已经搜索结束
if (last) break; /*跳出循环. */
此时只要有目录结构体里的sect和dir两个参数就可以读取该文件的数据。
l 终于又回到f_open函数
接下来的工作就是填充文件结构体:
fp->dir_sect = dj.fs->winsect; /* 指向文件目录所在扇区2008 */
fp->dir_ptr = dj.dir; 指向文件所在目录扇区缓冲对应的文件目录项。
fp->flag = mode; 比如说0x11代表总是打开和读取模式
fp->org_clust = 文件的起始簇,通过文件目录项获得。
((DWORD)LD_WORD(dir+DIR_FstClusHI) << 16) | LD_WORD(dir+DIR_FstClusLO);
fp->fsize = LD_DWORD(dir+DIR_FileSize); /*文件的大小 */
fp->fptr = 0; 文件指针清0,指向文件的开始。
fp->csect = 255; /* 文件当前扇区 */
fp->dsect = 0; 文件的数据扇区
用户377235 2015-7-19 14:58
用户567466 2015-3-11 22:52
用户377235 2014-2-28 20:42
用户443625 2013-8-21 10:28