深入理解文件系统.docx
- 文档编号:30474602
- 上传时间:2023-08-15
- 格式:DOCX
- 页数:28
- 大小:349.93KB
深入理解文件系统.docx
《深入理解文件系统.docx》由会员分享,可在线阅读,更多相关《深入理解文件系统.docx(28页珍藏版)》请在冰豆网上搜索。
深入理解文件系统
深入理解文件系统1
一、通过格式化命令-看磁盘文件系统的建立过程
1、添加format命令,单步调试
所有的底层驱动函数都已经准备好。
添加格式化命令format后,编译下载。
Format命令的执行主要是调用f_mkfs()函数,下面进行单步调试。
以下主要列出函数的主要执行步骤:
res=f_mkfs(0,1,4096);//1表示不需要引导扇区。
4096是8个扇区。
进入f_mkfs()函数,这里只列出主要执行步骤:
if(disk_ioctl(drv,GET_SECTOR_COUNT,&n_part)!
=RES_OK||n_part returnFR_MKFS_ABORTED;这个函数调用后,n_part=0x000F,3400=996352,这是SD的总块数。 allocsize/=SS(fs);等于8/*Numberofsectorspercluster*/ n_clst=n_part/allocsize;//等于0x1E680=124544簇。 if(n_clst>=0xFFF5)fmt=FS_FAT32;所以文件系统确定为FAT32类型。 n_fat=((n_clst*4)+8+SS(fs)-1)/SS(fs);等于0x3CE=974,表示FAT要占据974个扇区。 n_rsv=33-partition;保留扇区32个。 n_dir=0; b_fat=b_part+n_rsv;/*FATsstartsector32扇区*/ b_dir=b_fat+n_fat*N_FATS;/*Directorystartsector0x3EE=1006,由于FAT表个数设为1个,所以目录区=FAT起始+FAT占用扇区数*/ b_data=b_dir+n_dir;/*Datastartsector*/ 以上三项确定FAT区域、根目录区、数据区的起始扇区。 disk_ioctl(drv,GET_BLOCK_SIZE,&n)! =RES_OK,这个函数调用没有正确返回可擦出扇区的总数。 接下来程序会出错,因此退出,修改disk_ioctl()函数后,再次分析。 把这个函数返回值直接改为32。 并且把FAT表的个数定义为2. N_FATS改为2后,根目录区、数据区的起始扇区的起始扇区变为0x7BC=1980扇区。 继续往下执行。 n=(b_data+n-1)&~(n-1); n_fat+=(n-b_data)/N_FATS;这两句话对fat所占扇区数进行了修正,保证擦除时,以32个扇区为一个单位。 n_clst=(n_part-n_rsv-n_fat*N_FATS-n_dir)/allocsize;=0x1E588。 tbl=fs->win;/*Clearbuffer*/ mem_set(tbl,0,SS(fs));清零文件系统缓冲区。 mem_set(tbl,0,SS(fs)); ST_DWORD(tbl+BS_jmpBoot,0x90FEEB);/*Bootcode(jmp$,nop)*/ ST_WORD(tbl+BPB_BytsPerSec,SS(fs));/*Sectorsize*/ tbl[BPB_SecPerClus]=(BYTE)allocsize;/*Sectorspercluster*/ ST_WORD(tbl+BPB_RsvdSecCnt,n_rsv);/*Reservedsectors*/ 上面的工作主要是填充引导扇区缓冲区,也就是常说的DBR扇区缓冲,等所有的参数写好,就可以写回磁盘。 ST_WORD(tbl+BS_55AA,0xAA55);/*Signature*/ if(disk_write(drv,tbl,b_part+0,1)! =RES_OK) returnFR_DISK_ERR;//这就是在写有效引导标志sec[510]=0x55,sec[511]=0xAA。 if(fmt==FS_FAT32) disk_write(drv,tbl,b_part+6,1);//FAT32在第六扇区有个备份引导扇区。 for(m=0;m mem_set(tbl,0,SS(fs));/*1stsectoroftheFAT*/ if(fmt! =FS_FAT32){ n=(fmt==FS_FAT12)? 0x00FFFF00: 0xFFFFFF00; n|=partition; ST_DWORD(tbl,n);/*Reservecluster#0-1(FAT12/16)*/ }else{ ST_DWORD(tbl+0,0xFFFFFFF8);/*Reservecluster#0-1(FAT32)*/ ST_DWORD(tbl+4,0xFFFFFFFF); ST_DWORD(tbl+8,0x0FFFFFFF);/*Reservecluster#2forrootdir*/}//簇0和簇1保留,簇2分配给根目录区。 if(disk_write(drv,tbl,b_fat++,1)! =RES_OK) returnFR_DISK_ERR; mem_set(tbl,0,SS(fs));/*FollowingFATentriesarefilledbyzero*///接下来所有的扇区都清0,表示该簇未被占用。 for(n=1;n if(disk_write(drv,tbl,b_fat++,1)! =RES_OK) returnFR_DISK_ERR; }//第一次是写从0x21扇区开始,总共0x3CF个扇区(第一个扇区已经写了)。 第二次开始时是备份引导扇区,0x20+0x3D0=0x3F0。 同理,第一扇区与0x21扇区相同,后面都清零。 Format命令要执行较长的时间,主要就是FAT表接近2000个扇区要清零。 m=(BYTE)((fmt==FS_FAT32)? allocsize: n_dir);m=8,每簇8扇区。 do{ if(disk_write(drv,tbl,b_fat++,1)! =RES_OK) returnFR_DISK_ERR; }while(--m);//以上部分是将根目录区的8个扇区清零,表示目录项未被占用。 if(fmt==FS_FAT32){ ST_WORD(tbl+BS_55AA,0xAA55); ST_DWORD(tbl+FSI_LeadSig,0x41615252); ST_DWORD(tbl+FSI_StrucSig,0x61417272); ST_DWORD(tbl+FSI_Free_Count,n_clst-1);//根目录区已经用掉了一簇。 ST_DWORD(tbl+FSI_Nxt_Free,0xFFFFFFFF); disk_write(drv,tbl,b_part+1,1);//分别写入1号和7号扇区。 disk_write(drv,tbl,b_part+7,1); }//这是写FAT32文件系统的FSI扇区,包括空闲簇总数和上次分配的簇号。 FAT32文件系统的格式化到此完成。 2、再以Fdisk的方式格式化一次,这时候格式系统要写MBR扇区,FAT引导扇区不在0号扇区了。 同时MBR要建立分区表,以找到DBR引导扇区。 其过程与上述差不多,只是多执行了以下代码,详细过程就不叙述了。 if(! partition){ DWORDn_disk=b_part+n_part; mem_set(fs->win,0,SS(fs)); tbl=fs->win+MBR_Table;//分区表从0x1BE开始。 ST_DWORD(tbl,0x00010180);/*PartitionstartinCHS*/Table[0x1BE]=0x80,表明该分区是活动扇区。 00表示开始柱面,01、01表示开始扇区、开始磁头。 if(n_disk<63UL*255*1024){/*PartitionendinCHS*/ n_disk=n_disk/63/255; tbl[7]=(BYTE)n_disk;//表示结束的柱面。 tbl[6]=(BYTE)((n_disk>>2)|63);//结束的扇区。 }else{ ST_WORD(&tbl[6],0xFFFF);// } tbl[5]=254;//结束的磁头。 if(fmt! =FS_FAT32)/*SystemID*/ tbl[4]=(n_part<0x10000)? 0x04: 0x06; else tbl[4]=0x0c;//表示该分区类型为win95FAT32 ST_DWORD(tbl+8,63);/*起始扇区0x3FinLBA*/ ST_DWORD(tbl+12,n_part);/*分区的大小,总扇区数减去MBR及其占据的一个柱面。 */ ST_WORD(tbl+64,0xAA55);/*Signature*/ if(disk_write(drv,fs->win,0,1)! =RES_OK) returnFR_DISK_ERR; partition=0xF8;//MBR标志。 }else{ partition=0xF0; } 3、观察在有MBR区域的情况下,如何检查文件系统 fmt=check_fs(fs,bsect=0);/*检查0扇区的时候,没有发现FAT文件系统扇区,但是有0x550xAA标志,说明这是有效磁盘,但是返回1.*/ if(fmt==1){/*表明可能存在分区*/ /*Checkapartitionlistedintopofthepartitiontable*/ tbl=&fs->win[MBR_Table+LD2PT(vol)*16];/*Partitiontable*/ if(tbl[4]){实际这里应该是0x0c,表示FAT32系统。 bsect=LD_DWORD(&tbl[8]);/*这个是文件系统引导扇区的号码。 */ fmt=check_fs(fs,bsect);/*再到这个扇区检查是否存在FAT文件系统标志。 */}} 执行过后,仍然能够建立完整的文件系统信息结构体,只是里面的FAT分配起始扇区、数据区起始扇区地址相对没有MBR的时候改变了,其它都差不多。 -------------------------------------------------- 深入理解文件系统 (二) 注: 本文转自nthq的博客 二、将SD卡格式化成具有两个分区的磁盘。 1、目的 (1)深入理解MBR、DPT等概念。 (2)修改ff.c中的f_mkfs函数,得到一个新函数,f_format(u8partition,u16allocsize),前一个参数是指磁盘等分的个数,接受1、2、3、4四个参数,默认为1,最大为4。 后一个参数是指每簇占用的字节数。 (3)添加命令fdisk,调用上述函数。 执行完成后,用读卡器在PC上读取该SD卡,应该显示两个可移动磁盘。 2、f_format()函数的编写 首先新建一个文件fext.c,该文件就实现一个函数f_format.c,首先将f_mkfs()函数复制过来,在此基础上修改。 编译后,首先解决警告和错误: 包含头文件ff.h和diskio.h。 引用了ff.c中的静态函数mem_set()和mem_clr(),复制过来。 定义Null为0。 将FATFS*FatFs[_Drives]做外部声明。 同时发现,不同c源文件中#define同样的宏相互之间是不影响的。 说明预处理的时候是一个一个文件处理的,不检查相互之间的关联。 但同一个文件中,一个宏不能两次定义。 #ifndefNULL #defineNULL0 #endif//采取这种方式,主要是防止其他包含的头文件也对NULL进行了定义。 函数中主要修改的地方就是: n_part=n_part/drv;//进行drv等分。 每个磁盘的扇区总数就是这么多。 在DPT对应增加的分区部分,填好分区表16个字节。 特别重要的是四个地方: 0字节写为00或0x80,第四字节写入0xc0表示FAT32系统。 (第一次调试找不到新磁盘,就是由于这个字节默认为0)。 第8-11写入分区引导扇区的线性扇区地址。 第12-15写入该磁盘分区的大小。 for(i="0";i b_part+=i*n_part;//调整该分区引导扇区和FAT表起始地址。 b_fat=b_part+n_rsv; 3、其它修改的地方 定义_DRIVE为2,定义_MULTI_PARTITION为1,表示支持多分区。 同时初始化磁盘物理驱动与分区号转换结构体(每个逻辑磁盘对应一个结构体)。 主程序中也要定义两个文件系统结构体,每个对应一个磁盘。 然后分别调用f_mount()函数。 4、调用执行 voidUartCmdFdisk(u8argc,void**argv) { FRESULTres; res=f_format(2,2048);/2表示格式化成两个磁盘。 一簇是4个扇区、2048字节。 执行完这个函数后,磁盘被分成两个分区。 实际结果是,命令界面上可以查询到两个分区。 但是利用读卡器放到PC上,却只看到250M的第一分区,不知什么原因。 -------------------------------------------------- 深入理解文件系统3 三、添加命令fchdrive、fmove。 1、目的 (1)fchdrive: 在支持相对目录的情况下改变当前逻辑磁盘。 (2)fmove: 两个参数,将文件移动到另一个位置,可以改名,也可以保留原名。 2、fchdrive命令的实现 (1)添加命令支持 这个不再赘述。 (2)实现代码 voidUartCmdFChDrive(u8argc,void**argv){ BYTEDrive; FRESULTres="FR"_INVALID_DRIVE; if(*((BYTE*)argv[1]+1)==': '){检查参数,是不是1: 类似格式。 Drive=*((BYTE*)argv[1])-'0';取出磁盘号 res=f_chdrive(Drive);} if(res! =FR_OK){Uart_PutString("Invalidpath! \r\n");return;} Uart_PutString("Currentdiskischanged! \r\n"); } *((BYTE*)argv[1])这个意思是先将argv[1]转换为BYTE指针,再取出里面的数据(BYTE型,一个字节。 ) 以下是实现的截图: 3、fmove命令的实现 (1)这个命令带两个参数: 第一个是文件的原有路径,第二个是新路径,同时可以更改文件名。 (2)实现的原理 以创建新文件的方式打开新路径的文件。 这样会创建一个目录项,填充一个文件信息结构体。 然后打开原有路径的文件,也会得到一个文件信息结构体。 将旧结构体的文件大小、起始簇号复制到新的结构体,然后更新新文件的信息。 然后删除原有路径的文件。 移动操作完成。 (3)代码编写 需要两次打开文件,所以需要两个文件信息结构体。 res=f_open(&FileOld,(constchar*)argv[1],FA_WRITE); res=f_open(&FileNew,(constchar*)argv[2],FA_CREATE_NEW); FileNew.fsize=FileOld.fsize;//改变新文件的大小等于原文件 FileNew.org_clust=FileOld.org_clust;//新文件的簇等于原文件的簇。 FileNew.flag|=FA__WRITTEN;//文件属性已改变,需要更新 res=f_close(&FileNew);//关闭时进行更新。 res=f_unlink((constchar*)argv[1]);//删除原文件 编译,下载测试,功能正常。 以下是实现的截图。 -------------------------------------------------- 深入理解文件系统(四) 注: 本文转自nthq的博客 四、添加命令fcopy、fpath。 1、目的 (1)fcopy: 复制功能,原文件保留,新位置建立文件。 (2)fpath: 在支持相对路径的情况下,显示当前磁盘的当前目录。 2、fcopy命令的实现 (1)复制命令的实现思路 首先要打开两个文件,原文件以可读FA_READ的方式打开,新文件以FA_CREATE_NEW和FA_WRITE的方式打开。 然后建立一个循环,每次从原文件读出一个扇区的数据,写入新文件。 然后检查原文件结束标志,已到结尾,则跳出循环。 关闭两个文件,更新文件信息。 (2)代码实现 res=f_open(&FileOld,(constchar*)argv[1],FA_READ); res=f_open(&FileNew,(constchar*)argv[2],FA_CREATE_NEW|FA_WRITE); for(;;){ res=f_read(&FileOld,(void*)FileBuf,512,&ByteRead); res=f_write(&FileNew,(constvoid*)FileBuf,ByteRead,&ByteWrite); if(FileOld.fptr==FileOld.fsize))break; } f_close(&FileOld); f_close(&FileNew); 编译,下载,功能正确。 3、fpath命令的实现 (1)这个命令实现起来稍微有些难度 首先要获取当前磁盘和当前磁盘的当前目录所在簇,也就是FatFs[Drive]->cdir,如果为0,表明是在磁盘根目录,显示0: 或者1: 。 如果该簇号等于根目录所在簇号,当前目录也是在跟目录。 如果不是在根目录,那就要逐层往上搜索了。 根据f_opendir(DIR,..目录)可以回溯到上层目录,DIR结构体得到了上层目录(X+1)的起始簇号,目录项指针指向第0项。 用f_readdir()逐步读出目录项属性,得到FILINFO结构体。 如果其簇号等于文件系统的当前搜索簇号,则其名称就是所要得到的本层目录名。 如果是根目录下则为0: /X目录名。 当前搜索簇号设为(X+1)的起始簇号。 再次调用f_opendir(DIR,..目录),此次得到(X+2)的起始簇号,判断是否根目录。 查找当期搜索簇号在(X+2)目录层中对应的目录项,并获取X+1层的目录名。 如果是在根目录,则为0: /X+1目录名/X目录名。 循环直到到达根目录跳出搜索。 目录名缓冲区的处理方法。 定义总长度为100字节。 Path[99]=0;初始化时先让指针指向Path【99】。 得到一个目录,向前移动名称那么长,复制名称。 再往前移动1,添加’/’标志。 如果是根目录添加’: ’,往前移动1,在添加‘驱动号’,然后可以显示整个字符串。 (2)代码实现 for(;;){ if(CurClust==0||CurClust==CurFileSys->dirbase){ PathPtr--; *PathPtr--=': ';/ *PathPtr=CurDrive+'0';//如果当前簇号对应根目录。 break;} res=f_opendir(&DirInf,"..");//第一次执行时,DIR结构体的开始簇号变为X+1层目录的簇号//第二次时,变为X+2层目录的簇号。 if(res! =FR_OK)break; do{ res=f_readdir(&DirInf,&FileAttrib); TempClust=FileAttrib.sclust; if(TempClust==CurClust)break;//如果该目录项的簇号等于当前搜索簇号,它的名称就是当前需要的目录名 }while(res==FR_OK); if(res! =FR_OK)break; DirLen=Str_Length((constchar*)FileAttrib.fname);//这里要得到字符串的长度。 PathPtr-=DirLen;//文件指针往后退目录名长度 mem_cpy((void*)PathPtr,(constvoid*)FileAttrib.fname,DirLen); PathPtr--; *PathPtr='/';//添加文件间隔/符号。 CurClust=DirInf.sclust;//当前搜索簇号跟着上移。 } Uart_PutString(PathPtr);//这是显示的当前目录。 Uart_PutString("\r\n"); 编译,下载,显示两层目录是正确的。 但是目录一旦到第三层,就进入死循环。 还要接着调试。 (3)调试 经调试发现res=f_opendir(&DirInf,"..")调用后,并不能每次都自动回溯到上一层目录。 所以改为: f_opendir(&DirInf,(constchar*)ddPtr),ddPtr初始化成“..”,然后每循环一次,前面加上”../”,第二次变为”../..”,第三次变为“../../..“,这样目录不断回溯。 以下是该命令实现的截图。 -------------------------------------------------- 深入理解文件系统(五) 注: 本文转自nthq的博客 五、使用文件字符串处理的功能 1、目的 验证f_printf()函数的功能,学习变参数函数的编写和使用方法。 掌握格式化字符串的一些定义和用途。 2、有关格式控制串 格式控制串总是以%开始,后跟一些参数: %【flags】【宽度】【精度控制】type。 前面三个是可选,而type是必须
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 深入 理解 文件系统