原创 [转]文件系统(一)~(四)

2010-6-8 18:33 3982 15 15 分类: MCU/ 嵌入式

上一个名字用的时间太长了,换个名字吧。

一、通过格式化命令-看磁盘文件系统的建立过程

 

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 < MIN_SECTOR)

return FR_MKFS_ABORTED;这个函数调用后,n_part=0x000F,3400 = 996 352,这是SD的总块数。

 

allocsize /= SS(fs); 等于8/* Number of sectors per cluster */

n_clst = n_part / allocsize; //等于0x1E680 = 124 544 簇。

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; /* FATs start sector 32扇区*/

b_dir = b_fat + n_fat * N_FATS; /* Directory start sector 0x3EE =1006,由于FAT表个数设为1个,所以目录区=FAT起始+FAT占用扇区数*/

b_data = b_dir + n_dir; /* Data start sector */

以上三项确定 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; /* Clear buffer */

mem_set(tbl, 0, SS(fs)); 清零文件系统缓冲区。

 

mem_set(tbl, 0, SS(fs));

ST_DWORD(tbl+BS_jmpBoot, 0x90FEEB); /* Boot code (jmp $, nop) */

ST_WORD(tbl+BPB_BytsPerSec, SS(fs)); /* Sector size */

tbl[BPB_SecPerClus] = (BYTE)allocsize; /* Sectors per cluster */

ST_WORD(tbl+BPB_RsvdSecCnt, n_rsv); /* Reserved sectors */

上面的工作主要是填充 引导扇区缓冲区,也就是常说的DBR扇区缓冲,等所有的参数写好,就可以写回磁盘。

ST_WORD(tbl+BS_55AA, 0xAA55); /* Signature */

if (disk_write(drv, tbl, b_part+0, 1) != RES_OK)

return FR_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 < N_FATS; m++) {

mem_set(tbl, 0, SS(fs)); /* 1st sector of the FAT */

if (fmt != FS_FAT32) {

n = (fmt == FS_FAT12) ? 0x00FFFF00 : 0xFFFFFF00;

n |= partition;

ST_DWORD(tbl, n); /* Reserve cluster #0-1 (FAT12/16) */

} else {

ST_DWORD(tbl+0, 0xFFFFFFF8); /* Reserve cluster #0-1 (FAT32) */

ST_DWORD(tbl+4, 0xFFFFFFFF);

ST_DWORD(tbl+8, 0x0FFFFFFF); /* Reserve cluster #2 for root dir */ } //簇0和簇1保留,簇2分配给根目录区。

if (disk_write(drv, tbl, b_fat++, 1) != RES_OK)

return FR_DISK_ERR;

 

mem_set(tbl, 0, SS(fs)); /* Following FAT entries are filled by zero */ //接下来所有的扇区都清0,表示该簇未被占用。

for (n = 1; n < n_fat; n++) {

if (disk_write(drv, tbl, b_fat++, 1) != RES_OK)

return FR_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)

return FR_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) {

DWORD n_disk = b_part + n_part;

mem_set(fs->win, 0, SS(fs));

tbl = fs->win+MBR_Table; //分区表从0x1BE开始。

ST_DWORD(tbl, 0x00010180); /* Partition start in CHS */ Table[0x1BE] = 0x80,表明该分区是活动扇区00表示开始柱面,01、01表示开始扇区、开始磁头。

if (n_disk < 63UL * 255 * 1024) { /* Partition end in CHS */

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) /* System ID */

tbl[4] = (n_part < 0x10000) ? 0x04 : 0x06;

else

tbl[4] = 0x0c; // 表示该分区类型为win95 FAT32

ST_DWORD(tbl+8, 63); /*起始扇区 0x3F in LBA */

ST_DWORD(tbl+12, n_part); /*分区的大小,总扇区数减去MBR及其占据的一个柱面。 */

ST_WORD(tbl+64, 0xAA55); /* Signature */

if (disk_write(drv, fs->win, 0, 1) != RES_OK)

return FR_DISK_ERR;

partition = 0xF8; //MBR标志。

} else {

partition = 0xF0;

}

3、观察在有MBR区域的情况下,如何检查文件系统

fmt = check_fs(fs, bsect = 0); /* 检查0扇区的时候,没有发现FAT文件系统扇区,但是有0x55 0xAA标志,说明这是有效磁盘,但是返回1. */

if (fmt == 1) { /* 表明可能存在分区 */

/* Check a partition listed in top of the partition table */

tbl = &fs->win[MBR_Table + LD2PT(vol) * 16]; /* Partition table */

if (tbl[4]) { 实际这里应该是0x0c,表示FAT32系统。

bsect = LD_DWORD(&tbl[8]); /* 这个是文件系统 引导扇区的号码。 */

fmt = check_fs(fs, bsect); /* 再到这个扇区检查是否存在 FAT文件系统标志。 */ } }

 

执行过后,仍然能够建立完整的 文件系统信息 结构体,只是里面的 FAT分配起始扇区、数据区起始扇区地址相对 没有MBR的时候改变了,其它都差不多。

二、将SD卡格式化成具有两个分区的磁盘。

1、目的

(1)深入理解MBR、DPT等概念。

(2)修改ff.c中的f_mkfs函数,得到一个新函数,f_format(u8 partition,u16 allocsize),前一个参数是指磁盘等分的个数,接受 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 同样的宏相互之间是不影响的。说明预处理的时候是一个一个文件处理的,不检查相互之间的关联。但同一个文件中,一个宏不能两次定义。

#ifndef NULL

#define NULL 0

#endif //采取这种方式,主要是防止其他 包含的头文件也对NULL进行了定义。

函数中主要修改的地方就是:

n_part = n_part /drv; //进行drv等分。每个磁盘的扇区总数就是这么多。

在DPT对应增加的分区部分,填好分区表16个字节特别重要的是四个地方0字节写为00或0x80第四字节写入0xc0表示FAT32系统。(第一次调试找不到新磁盘,就是由于这个字节默认为0)。第8-11写入分区引导扇区的线性扇区地址。第12-15写入该磁盘分区的大小。

for ( i="0"; i<drv; i++){ //每个分区都要做一次,DBR的写入,FAT分配表初始化,根目录初始化。

b_part+= i*n_part; //调整该分区引导扇区和FAT表起始地址。

b_fat=b_part + n_rsv;

 

3、其它修改的地方

定义 _DRIVE为2,定义_MULTI_PARTITION为1,表示支持多分区。同时初始化磁盘 物理驱动与分区号转换结构体(每个逻辑磁盘对应一个结构体)。

主程序中也要定义两个文件系统结构体,每个对应一个磁盘。然后分别调用f_mount()函数。

 

 

4、调用执行

void UartCmdFdisk(u8 argc,void **argv)

{

FRESULT res;

res=f_format( 2, 2048 ); /2表示格式化成两个磁盘。一簇是4个扇区、2048字节。

执行完这个函数后,磁盘被分成两个分区。

 

实际结果是,命令界面上可以查询到两个分区。但是利用读卡器放到PC上,却只看到250M的第一分区,不知什么原因。

深入理解文件系统(三)

2010-04-24 10:57:56 发表      系统分类:嵌入式系统      自定义分类:嵌入式文件系统标签:stm32;FatFS;FAT32

三、添加命令fchdrive、fmove。

 

1、目的

(1)fchdrive:在支持相对目录 的情况下改变当前逻辑磁盘。

(2)fmove:两个参数,将文件移动到另一个位置,可以改名,也可以保留原名。

2、fchdrive 命令的实现

(1)添加命令支持

这个不再赘述。

(2)实现代码

void UartCmdFChDrive(u8 argc,void **argv){

BYTE Drive;

FRESULT res="FR"_INVALID_DRIVE;

if ( *( (BYTE*)argv[1]+1 )==':' ){ 检查参数,是不是1:类似格式。

Drive =*( (BYTE*)argv[1]) - '0'; 取出磁盘号

res=f_chdrive( Drive ); }

if ( res!=FR_OK) { Uart_PutString( "Invalid path!\r\n");return;}

Uart_PutString( "Current disk is changed!\r\n");

}

*( (BYTE*)argv[1])这个意思是先将argv[1]转换为BYTE指针,再取出里面的数据(BYTE型,一个字节。)

以下是实现的截图:

3、fmove命令的实现

(1)这个命令带两个参数:第一个是文件的原有路径,第二个是新路径,同时可以更改文件名。

(2)实现的原理

以创建新文件的方式 打开新路径的文件。这样会创建一个目录项,填充一个文件信息结构体。然后打开原有路径的文件,也会得到一个文件信息结构体。将旧结构体的文件大小、起始簇号复制到新的结构体,然后更新新文件的信息。然后删除原有路径的文件。移动操作完成。

(3)代码编写

需要两次打开文件,所以需要两个文件信息结构体。

res = f_open ( &FileOld, (const char *)argv[1], FA_WRITE );

res = f_open ( &FileNew, (const char *)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 ( (const char *)argv[1] ); //删除原文件

编译,下载测试,功能正常。以下是实现的截图。


深入理解文件系统(四)

2010-04-24 11:10:40 发表      系统分类:嵌入式系统      自定义分类:嵌入式文件系统标签:stm32;FatFS;FAT32

四、添加命令fcopy、fpath。

1、目的

(1)fcopy:复制功能,原文件保留,新位置建立文件。

(2)fpath:在支持相对路径的情况下,显示当前磁盘的当前目录。

 

2、fcopy命令的实现

(1)复制命令的实现思路

首先要打开两个文件,原文件以可读FA_READ的方式打开,新文件以 FA_CREATE_NEW和FA_WRITE的方式打开。

然后建立一个循环,每次从原文件读出一个扇区的数据,写入新文件。然后检查原文件 结束标志,已到结尾,则跳出循环。

关闭两个文件,更新文件信息。

(2)代码实现

res = f_open ( &FileOld, (const char *)argv[1], FA_READ );

res = f_open ( &FileNew, (const char *)argv[2], FA_CREATE_NEW | FA_WRITE );

for ( ; ; ){

res = f_read ( &FileOld, (void*)FileBuf, 512, &ByteRead );

res = f_write ( &FileNew, ( const void *)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 ( (const char*)FileAttrib.fname ); //这里要得到字符串的长度。

PathPtr -= DirLen; //文件指针往后退目录名长度

mem_cpy ( (void *)PathPtr, ( const void*)FileAttrib.fname, DirLen);

PathPtr--;

*PathPtr='/'; //添加文件间隔/符号。

 

CurClust = DirInf.sclust; //当前搜索簇号跟着上移。

}

Uart_PutString( PathPtr); //这是显示的当前目录。

Uart_PutString( "\r\n");

 

编译,下载,显示两层目录是正确的。但是目录一旦到第三层,就进入死循环。还要接着调试。

 

(3)调试

经调试发现res = f_opendir ( &DirInf, ".." )调用后,并不能每次都自动回溯到上一层目录。

所以改为:f_opendir ( &DirInf, (const char*)ddPtr),ddPtr初始化成“..”,然后每循环一次,前面加上”../”,第二次变为 ”../..”,第三次变为“../../.. “,这样目录不断回溯。

以下是该命令实现的截图。


文章评论0条评论)

登录后参与讨论
我要评论
0
15
关闭 站长推荐上一条 /2 下一条