yaffs 文件系统
概述
Yaffs(Yet Another Flash File System)是专门为NAND Flash设计的文件系统,在GPL协议下发布,适用于大容量的存储设备的嵌入式系统,主要由Aleph One公司的Charles Manning开发。目前Yaffs文件系统有两个版本Yaffs1和Yaffs2。
一般来说对于small page的NAND Flash芯片,Yaffs1支持更好,对于现代大部分都是big page 的NAND Flash芯片的情况下,Yaffs2更加常用。
Yaffs文件系统针对NAND Flash特性做了很多针对性的优化,提高性能,延长芯片的使用寿命。
文件系统在整个应用体系中属于中间层,对下和操作系统、Flash对接,对上和应用程序对接。
其移植性非常好,对于没有操作系统的设备上,可以直接使用。
移植裸机
这里的实现平台是am335x,采用的驱动是am335x的ti-startware的NAND驱动程序,编译环境是CCS 9.3,有参考:Yaffs2 文件系统裸机移植。
源码获取
在其官网页面获取yaffs源码
- 通过git直接下载clone,下载成功文件夹是yaffs2
- 选择 access our Git repository over the web 选择最新版master的snapshot下载,下载成功文件夹是yaffs-xxxxxxxxx
在官网也可以下载一个指导文件,Yaffs Direct Interface。
进入下载好的文件夹,根目录下就有许多.c和.h文件,都很重要,在Yaffs Direct Interface中会有部分说明。
需要的源码文件
在direct文件夹下,执行handle_common.sh文件,参数为copy,这一点在该文件夹下的README文件有说明,
cd yaffs2/direct #如果不是clone下来的,通过snapshot下载的,则不是yaffs2
./handle_common.sh copy
基本上把根目录下的.c和对应的.h文件都复制了过来,这些在裸机工程中都需要,
额外需要的是测试文件夹下的 yaffs_osglue.c 、yaffs_nand_drv.c、yaffs_nand_drv.h,但是需要修改。
修改
-
ydirectenv.h
#define yaffs_trace(msk, fmt, ...) do { \ if (yaffs_trace_mask & (msk)) \ printf("yaffs: " fmt "\n", ##__VA_ARGS__); \ } while (0) //需要将printf替换成裸机所能支持的打印信息函数 //这个yaffs_trace_mask是需要打印的跟踪类型选择, //可以选择的类型的宏定义在yaffs_trace.h中
-
yaffs_osglue.c
这个文件在test测试文件夹中,实现操作系统交互接口,官方给出的内部有选择的,例如yaffsfs_Lock函数的选择宏定义就很简单粗暴,直接
#if 1
,那么这里直接将选择宏定义删除除掉,给出裸机的函数配置。yaffs_trace_mask 全局变量就定义在这里#include "yaffscfg.h" #include "yaffs_guts.h" #include "yaffsfs.h" #include "yaffs_trace.h" #include <assert.h> #include <errno.h> void *yaffsfs_malloc(size_t size) { return malloc(size); } void yaffsfs_free(void *ptr) { free(ptr); } unsigned yaffs_trace_mask = YAFFS_TRACE_SCAN | YAFFS_TRACE_GC | YAFFS_TRACE_ERASE | YAFFS_TRACE_ERROR | YAFFS_TRACE_TRACING | YAFFS_TRACE_ALLOCATE | YAFFS_TRACE_BAD_BLOCKS | YAFFS_TRACE_VERIFY | 0; /* * yaffsfs_SetError() and yaffsfs_GetError() * Do whatever to set the system error. * yaffsfs_GetError() just fetches the last error. */ static int yaffsfs_lastError; void yaffsfs_SetError(int err) { //Do whatever to set error yaffsfs_lastError = err; UARTprintf("yaffs error detected %d\n", err); } //这里添加了错误报错信息打印,注意打印函数替换 int yaffsfs_GetLastError(void) { return yaffsfs_lastError; } /* * yaffsfs_CheckMemRegion() * Check that access to an address is valid. * This can check memory is in bounds and is writable etc. * * Returns 0 if ok, negative if not. */ int yaffsfs_CheckMemRegion(const void *addr, size_t size, int write_request) { (void) size; (void) write_request; if(!addr) return -1; return 0; } u32 yaffsfs_CurrentTime(void) { //return SysTick_GetMS(); return time(NULL); } void yaffsfs_OSInitialisation(void) { } void yaffsfs_Lock(void) { } void yaffsfs_Unlock(void) { } //#if defined(__CC_ARM) /* ARMCC compiler */ int strnlen(const char *Str, int MaxLen) { int i; for (i=0;i<MaxLen; i++) { if(Str[i] == 0) { break; } } return i; } //#endif void yaffs_bug_fn(const char *file_name, int line_no) { /*plus_log(PLUS_LOG_ERR, "yaffs bug detected %s:%d", file_name, line_no); while(1);*/ UARTprintf("yaffs bug detected %s:%d\n", file_name, line_no); assert(0); } //yaffs_bug_fn函数中的UARTprintf打印信息函数依旧需要替换成裸机支持的。
-
yportenv.h
//添加选择宏定义 #define CONFIG_YAFFS_DIRECT //使用直接接口 #define CONFIG_YAFFSFS_PROVIDE_VALUES //一些报错类型、文件创建打开类型 #define CONFIG_YAFFS_DEFINES_TYPES //需要的变量类型声明缩写u8、u16等 #define CONFIG_YAFFS_PROVIDE_DEFS //一些文件类型、分配标签的定义 //在 CONFIG_YAFFS_DEFINES_TYPES 变量类型声明下添加 #ifdef CONFIG_YAFFS_DEFINES_TYPES typedef long loff_t; //维护当前读写位置 typedef long off_t; //偏移地址 typedef unsigned long mode_t; //文件打开权限 typedef unsigned long dev_t; //设备号 //以上四个是在linux系统中定义使用的,裸机中也要补充定义 //这个文件后面有个 BUG 的选择定义宏,视情况,在我的移植中,跟踪和报错都有显示了,够用(这个报错可以直接定位到某一语句)
-
yaffs_nand_drv.c
这个文件非常重要!!!移植yaffs文件处理到裸机工程上,说白了就是要完成yaffs的读写与NAND驱动读写的对接,至于yaffs完成的其他,例如分区、标签处理,内存分配等任务,都在yaffs源码内部完成,可以不用修改,原理用户也不需要完全理解,就可以移植使用,而实现基础就是完成这文件,让yaffs可以对NAND Flash进行操作。
#include "yaffs_guts.h" //#include "yaffs_nand_drv.h" #include "yaffs_trace.h" //#include "yportenv.h" #include "yaffs_guts.h" #include "yaffs_ecc.h" #include "yaffsfs.h" #include "nandlib.h" #include "include.h" extern NandInfo_t nandInfo; extern unsigned char eccData[]; //ecc校验缓冲区FIFO或者Buffer #define BAD_BLOCK_INFO_BITS 2 //坏块检测,这里实际没有用到 //每一页的大小(字节数) #define NANDFLASH_RW_PAGE_SIZE (NAND_PAGESIZE_4096BYTES) #define NANDFLASH_PAGE_PER_BLOCK 64 //每一块的页数 #define NANDFLASH_SPARE_SIZE 224 //每一页的Spare区域
yaffs访问NAND的接口,下面这个函数是其中的写操作。yaffs2非常灵活,在《Yaffs Direct Interface》的9.3节有描述,其不需要一个标准的spare区域分割规定和ECC校验方式,可以由NAND驱动提供,也可以在这个函数中使用yaffs的ECC处理函数。
spare中就有三个分割区,坏块标识、ECC、剩余的标签区,就是这个函数中的oob
static int yaffs_nand_drv_WriteChunk(struct yaffs_dev *dev, int nand_chunk, const u8 *data, int data_len, const u8 *oob, int oob_len) { unsigned int retVal; //yaffs上层函数传递的nand_chunk参数,就是要操作的页数, //这个页数是相对NAND总页数来讲的,不是每块的第几页,所以需要计算一下 u32 blkNum = nand_chunk / NANDFLASH_PAGE_PER_BLOCK; u32 pageNum = nand_chunk % NANDFLASH_PAGE_PER_BLOCK; u32 byteCnt; //检查是否坏块,NANDBadBlockCheck就是nand驱动里的坏块检测函数,直接使用 retVal = NANDBadBlockCheck(&nandInfo, blkNum); if( retVal == NAND_BLOCK_BAD ) return YAFFS_FAIL; //将ecc清零,ecc的计算也是在nand驱动中完成的,传入一个空的缓冲数组即可 for(byteCnt = 0; byteCnt < 128; byteCnt++) eccData[byteCnt] = 0; //nand驱动中写操作,稍微修改了一下原版的驱动, //在后面具体介绍,这里存在大坑,如果不能良好对接,包括参数传递和 //返回值的判定等,那么会造成根本性错误,而且移植的错误大概率是这里 //造成的 retVal= NANDPageWrite_Yaffs(&nandInfo, blkNum, pageNum, data, &eccData[0], oob, oob_len); //NAND_STATUS_PASSED这是ti-startware的nand驱动里的返回值 if( (retVal & NAND_STATUS_PASSED) ) { return YAFFS_OK; } else return YAFFS_FAIL; }
读操作接口函数
static int yaffs_nand_drv_ReadChunk(struct yaffs_dev *dev, int nand_chunk, u8 *data, int data_len, u8 *oob, int oob_len, enum yaffs_ecc_result *ecc_result_out) { unsigned int retVal; u32 blkNum = nand_chunk / NANDFLASH_PAGE_PER_BLOCK; u32 pageNum = nand_chunk % NANDFLASH_PAGE_PER_BLOCK; u32 byteCnt; retVal = NANDBadBlockCheck(&nandInfo, blkNum); if( retVal == NAND_BLOCK_BAD ) return YAFFS_FAIL; for(byteCnt = 0; byteCnt < 128; byteCnt++) eccData[byteCnt] = 0; //调用NAND驱动,这里也修改了 retVal= NANDPageRead_Yaffs(&nandInfo, blkNum, pageNum, data, &eccData[0],oob,oob_len); //超时读取超时反馈,ecc处理为未知错误 if( (retVal & NAND_STATUS_WAITTIMEOUT) ) { *ecc_result_out = YAFFS_ECC_RESULT_UNKNOWN; return YAFFS_FAIL; } else if( (retVal & NAND_STATUS_READWRITE_DMA_FAIL) ) {//NAND驱动采用DMA传输。传输失败,ecc处理为未知错误 *ecc_result_out = YAFFS_ECC_RESULT_UNKNOWN; return YAFFS_FAIL; } if( (retVal & NAND_STATUS_READ_ECC_UNCORRECTABLE_ERROR) ) { //ECC校验错误且更改失败,但这里问题,在后面问题总结中具体描述 *ecc_result_out = YAFFS_ECC_RESULT_UNFIXED; //ECC检验是内部的GPMC的校验,不能区别出空块(全是0xff,没有写 //入内容的块),在这里检查一下,是否是空块,空块也会反馈 //NAND_STATUS_READ_ECC_UNCORRECTABLE_ERROR的错误 //参考的文档中采用的是yaffs的ecc校验,没有这个问题 for(byteCnt = 0; byteCnt < 10; byteCnt++) if((eccData[byteCnt] != 0xff) || (eccData[byteCnt] != data[byteCnt])) return YAFFS_OK; //首先循环检测前十个读到的ECC和data,如果不全是0xff,那么就不是空块 if(yaffs_check_ff(data, NANDFLASH_RW_PAGE_SIZE)) *ecc_result_out = YAFFS_ECC_RESULT_NO_ERROR; //接着检测全部整块的值,是否是0xff,这里使用了yaffs_guts.c中的函数 } else if( (retVal & NAND_STATUS_READ_ECC_ERROR_CORRECTED) ) *ecc_result_out = YAFFS_ECC_RESULT_FIXED; if( (retVal & NAND_STATUS_PASSED) ) *ecc_result_out = YAFFS_ECC_RESULT_NO_ERROR; return YAFFS_OK; }
下面是块擦除、坏块检测、坏块标记函数,都可以直接使用nand驱动中对应的函数
static int yaffs_nand_drv_EraseBlock(struct yaffs_dev *dev, int block_no) { unsigned int retVal; //struct nand_chip *chip = dev_to_chip(dev); //if(nanddrv_erase(chip, block_no) == 0) //return YAFFS_OK; retVal = NANDBlockErase(&nandInfo, (unsigned int)block_no); if( retVal == NAND_STATUS_PASSED ) { return YAFFS_OK; } else return YAFFS_FAIL; } static int yaffs_nand_drv_MarkBad(struct yaffs_dev *dev, int block_no) { unsigned int retVal; retVal = NANDMarkBlockAsBad(&nandInfo, (unsigned int)block_no); if( retVal == NAND_STATUS_PASSED ) return YAFFS_OK; else return YAFFS_FAIL; } static int yaffs_nand_drv_CheckBad(struct yaffs_dev *dev, int block_no) { unsigned int retVal; retVal = NANDBadBlockCheck(&nandInfo, (unsigned int)block_no); if( retVal == NAND_BLOCK_GOOD ) return YAFFS_OK; else return YAFFS_FAIL; }
初始化和反初始化函数,并不需要yaffs完成这个操作,所以都直接返回成功YAFFS_OK
//初始化接口 static int yaffs_nand_drv_Initialise(struct yaffs_dev *dev) { return YAFFS_OK; } //反初始化接口 static int yaffs_nand_drv_Deinitialise(struct yaffs_dev *dev) { return YAFFS_OK; }
注册函数,在许多参考移植中是没有这个函数的,都是yaffs_StartUp函数,现在替换为了这个 yaffs_nand_install_drv 或者其他类似的设备注册函数(在direct\test-framework\yaffscfg2k.c中类似的,在《Yaffs Direct Interface》的10节有描述),他需要指明一些参数类型,参考配置如下。
struct yaffs_dev *yaffs_nand_install_drv(const YCHAR *dev_name, u32 dev_id, u32 blocks, u32 start_block, u32 end_block) { struct yaffs_dev *dev = NULL; struct yaffs_driver *drv = NULL; struct yaffs_param *param = NULL; dev = yaffsfs_malloc(sizeof(struct yaffs_dev)); if(dev == NULL) { yaffsfs_free(dev); return dev; } if((end_block == 0) || (end_block >= (start_block + blocks))) end_block = (start_block + blocks) - 1; memset(dev, 0, sizeof(struct yaffs_dev)); param = &(dev->param); drv = &(dev->drv); param->name = dev_name; param->total_bytes_per_chunk = NANDFLASH_RW_PAGE_SIZE; param->chunks_per_block = NANDFLASH_PAGE_PER_BLOCK; param->spare_bytes_per_chunk = NANDFLASH_SPARE_SIZE; param->n_reserved_blocks = 5; param->start_block = start_block; param->end_block = end_block; param->is_yaffs2 = 1; param->n_caches = 10; drv->drv_write_chunk_fn = yaffs_nand_drv_WriteChunk; drv->drv_read_chunk_fn = yaffs_nand_drv_ReadChunk; drv->drv_erase_fn = yaffs_nand_drv_EraseBlock; drv->drv_mark_bad_fn = yaffs_nand_drv_MarkBad; drv->drv_check_bad_fn = yaffs_nand_drv_CheckBad; drv->drv_initialise_fn = yaffs_nand_drv_Initialise; drv->drv_deinitialise_fn = yaffs_nand_drv_Deinitialise; yaffs_add_device(dev); return dev; }
应用
首先将NAND初始化配置好。
添加设备
添加设备,并定义设备名,这个设备名和下面的挂载路径、文件创建路径都要一致,
这里两个分区可以存在重复的区域,只要没有写入,就不会报错
yaffs_nand_install_drv("C:", 0, 1024, 10, 0);
yaffs_nand_install_drv("D:", 0, 1024, 1050, 0);
格式化分区
yaffs_format函数格式化分区,就是清空分区,后面三个参数分别是:
unmount_flag;force_unmount_flag;remount_flag
如果首个参数,即路径名没有错误,设备也添加了,则会检测是否有挂载,
状态 | unmount | force_unmount | remount | 返回值 | 描述 |
---|---|---|---|---|---|
挂载 | 0 | 0 | 0 | -1,失败 | 设备繁忙 |
1 | 0 | 0 | -1,失败 0,成功 | -1:因为设备有处理项目,不能取消挂载, 0:设备没有处理项目,可以取消挂载 | |
1 | 0 | 1 | -1,失败 0,成功 | -1:因为设备有处理项目,不能取消挂载,如果能取消挂载,则是在重新挂载时出错,报错没有内存空间 0:设备没有处理项目,可以取消挂载,且重新挂载成功。 | |
1 | 1 | 0 | -1,失败 0,成功 | 0,格式化成功。注意:此时取消挂载了 -1,设备未添加,路径名出错等 | |
1 | 1 | 1 | -1,失败 0,成功 | 1,不管有没有处理项目,强迫取消挂载,在重新挂载时出错,报错没有内存空间 0,重新挂载成功。 | |
非挂载 | x | x | x | 0:成功 -1,失败 | 0,格式化成功。 -1,设备未添加,路径名出错等 |
y=yaffs_format("C:",0,0,0);
if(y!=0)
{
UARTPuts("\r\nyaffs2 format error\r\n",-1);
while(1);
}
else
UARTPuts("\r\nyaffs2 format nand\r\n",-1);
挂载分区
挂载分区之后就可以就可以进行各种操作了。注意:如果想要挂载创建的D:分区,需要先取消挂载C:分区,否则,报错,内存不足
y=yaffs_mount("C:");
if(y<0)
UARTPuts("\r\nyaffs2 Mount C:/ error\r\n",-1);
else
UARTPuts("\r\nyaffs2 Mount C:/ succeeded\r\n",-1);
空闲区域大小
y=yaffs_freespace("C:");//读取分区中的空余空间大小(字节数)
if(y<0)
UARTPuts("\r\nyaffs2 freespace error\r\n",-1);
else
UARTprintf("\r\nyaffs2 freespace %x,%d\r\n",y,y);
除了上面这个函数,还有两个空间计算函数:yaffs_totalspace(path) 和 yaffs_inodecount(path) ,后者返回的是路径下的文件数
创建文件夹
yaffs_mkdir函数创建文件夹,第二个参数是mode,创建文件夹一定是S_IFDIR,这个参数有超多选择,将在后面Yaffs Direct Interface中重要内容记录一部分。
y=yaffs_mkdir("C:/test",S_IFDIR);
if(y<0)
UARTPuts("\r\nyaffs2 make directory C:/test error\r\n",-1);
else
UARTPuts("\r\nyaffs2 make directory C:/test succeeded\r\n",-1);
打开文件
yaffs_open函数打开文件,yaffs_open函数返回的就是handle,具体在后面的Yaffs Direct Interface记录解释。
第二个参数是oflag,访问文件的类型
oflag | 描述 |
---|---|
O_CREAT | 如果文件不存在,创建一个文件,返回成功;如果存在,则不操作,且返回成功 |
O_EXCL | 仅仅配合O_CREAT标志使用,如果文件存在,则返回失败 |
O_TRUNC | 如果文件存在,打开并且写访问,然后删减文件到0字节长( then truncate the file to zero bytes long) |
O_APPEND | 不管handle的位置,总是要写到文件的末尾,handle有保存文件的读写位置指针。 |
O_RDWR | 打开文件进行读写操作 |
O_WRONLY | 打开文件进行写操作,不能读 |
0(ZERO) | 如果O_RDWR和O_WRONLY没有设置,那么这个文件将会打开并只读访问 |
有比较典型的应用:
-
O_CREAT | O_TRUNC | O_RDWR :
创建一个文件如果它不存在,如果它存在,则删减到0长度,通常被用来覆盖一个文件或者创建一个不存在的文件。
-
O_CREAT | OEXCL | O_WRONLY :
创建一个新文件,仅仅为了写操作打开它,如果文件存在,则失败
第三个参数是mode,在文件打开这个操作中有三个mode可以控制或者设置,上面文件夹建立有用到一个S_IFDIR。
mode | 描述 |
---|---|
S_IREAD | 文件可能打开为了进行读操作 |
S_IWRITE | 文件可能打开为了进行写操作 |
S_IEXEC | 文件可能打开为了执行,这个选项没有被yaffs强制执行 |
handle = yaffs_open("C:/test/test.txt", O_CREAT | O_RDWR | O_APPEND, S_IREAD | S_IWRITE);
if(handle<0)
UARTPuts("\r\nyaffs2 open C:/test/test.txt failed \r\n",-1);
else
UARTPuts("\r\nyaffs2 open C:/test/test.txt succeeded\r\n",-1);
读写文件
有了打开文件产生的handle,就可以进行yaffs_close(handle) 、yaffs_read(handle, buffer, nbytes)、yaffs_write(handle, buffer, nbytes)等函数操作,其他部分在Yaffs Direct Interface记录解释。
uint8 yaffsReadFIFO[17];
uint8 yaffsWriteFIFO[17];
for(i=0;i<17;i++)
{
yaffsReadFIFO[i]=0;
yaffsWriteFIFO[i]=i;
}
UARTPuts("\r\nyaffs2 write",-1);
yaffs_write(handle,yaffsWriteFIFO,17);
UARTPuts("\r\nyaffs2 read:",-1);
yaffs_pread(handle,yaffsReadFIFO,17,0);
for(i=0;i<17;i++)
UARTprintf(" %x ",yaffsReadFIFO[i]);
关闭文件
y=yaffs_close(handle);
if(y==0)
UARTPuts("\r\nyaffs2 close C:/test/test1.txt succeeded \r\n",-1);
else
UARTPuts("\r\nyaffs2 close C:/test/test1.txt failed \r\n",-1);
删除文件
yaffs_unlink函数可以删除文件,注意如果在“关闭文件”之前调用,那么文件打开的handle还存在,那么即使删除文件,依旧能够访问文件通过这个handle,读写都没问题。在文件关闭之后,handle就被关闭,文件被真正删除。
y=yaffs_unlink("C:/test/test.txt");
if(y<0)
UARTPuts("\r\nyaffs2 remove C:/test/test.txt failed \r\n",-1);
else
UARTPuts("\r\nyaffs2 remove C:/test/test.txt succeeded\r\n",-1);
handle = yaffs_open("C:/test/test.txt", O_RDWR | O_APPEND, S_IREAD | S_IWRITE);
if(handle<0)
UARTPuts("\r\nyaffs2 RE open C:/test/test.txt failed \r\n",-1);
else
UARTPuts("\r\nyaffs2 RE open C:/test/test.txt succeeded\r\n",-1);
还有其他的操作可以自行了解,部分记录在Yaffs Direct Interface中,但文中的解释也不是很全,还需要自己实际操作理解。
问题:
内存堆(heap)不足
在挂载时,有两个地方会使用到分配内存函数yaffsfs_malloc()->malloc(),
-
首先是yaffs_mount->yaffs_mount_common->yaffs_guts_initialise->yaffs_guts_ll_init-> yaffs_init_tmp_buffers 函数,这个函数暂时分配了操作缓存区,要能成功分配出6个临时内存操作区,
memset(dev->temp_buffer, 0, sizeof(dev->temp_buffer)); //yaffs设置的YAFFS_N_TEMP_BUFFERS=6 //配置的设备total_bytes_per_chunk,每一页4096字节即0x1000 for (i = 0; buf && i < YAFFS_N_TEMP_BUFFERS; i++) { dev->temp_buffer[i].in_use = 0; buf = kmalloc(dev->param.total_bytes_per_chunk, GFP_NOFS); dev->temp_buffer[i].buffer = buf; }
我采用的CCS运行环境,heap分配是在工程里的CMD文件中
-stack 0x0008 /* SOFTWARE STACK SIZE 0x0008 */ -heap 0x2000 /* HEAP AREA SIZE 0x2000 */ -e Entry
那么,分配6个临时内存操作区,需要堆大小是0x6000,配置稍微扩大一点0x8000,那么这个函数里的内存分配不在出错。(我这里基础需要0x2000的堆大小,其他函数需要)
这里记录一个编译器的警告:缺少.sysmem段,在CMD文件中添加了
.sysmem : load > DDR_MEM
但是报错位置改变,依旧内存不够。
-
yaffs_mount->yaffs_mount_common->yaffs_guts_initialise->yaffs_cache_init
u32 cache_bytes = mgr->n_caches * sizeof(struct yaffs_cache); mgr->cache = kmalloc(cache_bytes, GFP_NOFS); for (i = 0; i < mgr->n_caches && buf; i++) { struct yaffs_cache *cache = &mgr->cache[i]; cache->object = NULL; cache->last_use = 0; cache->dirty = 0; cache->data = buf = kmalloc(dev->param.total_bytes_per_chunk, GFP_NOFS); }
这个结构体yaffs_cache有点复杂,嵌套了结构体还有选择宏,这里直接读出它的大小是28字节,n_caches是初始化分区时设置的值为10
则是10*28=0x118
后面的是10个4096=40960=0xA000,一共是A118,
加上之前的0x8000,一共是0x12118,跟踪报错地方改变
-
yaffs_mount->yaffs_mount_common->yaffs_guts_initialise->yaffs_init_blocks
这个函数里首先分配了n_blocks * sizeof(struct yaffs_block_info)大小的区域
n_blocks就是定义分区的末端块和起始块的差,就是分区的总块数,这里是最大可以分配4096,为了充分使用NAND Flash,我们就按照最大来配置heap的大小。
结构体 yaffs_block_info 是8字节,所以总共所需要的大小是
4096*8=0x8000
yaffs_block_info 是10个32位的变量,应该是40字节,但是这里yaffs使用了位域定义,就是结构体变量后面添加了冒号:
u32 block_state:4
对应的实际只有4位,4bit
如果内存空间够大,分配没有出错,紧接着又分配了dev->chunk_bit_stride * n_blocks
chunk_bit_stride =(chunks_per_block+7)/8=(64+7)/8=8
4096*8=0x8000
/* If the first allocation strategy fails, thry the alternate one */ dev->block_info = kmalloc(n_blocks * sizeof(struct yaffs_block_info), GFP_NOFS); /* Set up dynamic blockinfo stuff. Round up bytes. */ dev->chunk_bit_stride = (dev->param.chunks_per_block + 7) / 8; dev->chunk_bits = kmalloc(dev->chunk_bit_stride * n_blocks, GFP_NOFS);
一共加起来是0x10000,
加上之前的0x12118,一共是0x22118,跟踪报错地方再次改变。
-
yaffs_mount->yaffs_mount_common->yaffs_guts_initialise -> yaffs_init_tnodes_and_objs
allocator = kmalloc(sizeof(struct yaffs_allocator), GFP_NOFS);
yaffs_allocator这个结构体也比较复杂,嵌套结构体,直接sizeof计算出大小是36,0x24
加上之前的0x22118,是0x2213c
-
yaffs_mount->yaffs_mount_common->yaffs_guts_initialise -> yaffs_create_initial_dir -> yaffs_create_fake_dir (这个函数进行了4次) -> yaffs_new_obj -> yaffs_get_tnode-> yaffs_alloc_raw_tnode -> yaffs_create_tnodes
这个地方也会有创建tnode
new_tnodes = kmalloc(n_tnodes * dev->tnode_size, GFP_NOFS);
但是根据yaffs_new_obj 传递参数,yaffs_create_fake_dir执行的四次当中,会有执行yaffs_get_tnode1次
yaffs_create_fake_dir (这个函数进行了4次) -> yaffs_new_obj -> yaffs_alloc_empty_obj -> yaffs_alloc_raw_obj -> yaffs_create_free_objs
这里会报错 :Could not allocate more objects 4次
new_objs = kmalloc(n_obj * sizeof(struct yaffs_obj), GFP_NOFS); list = kmalloc(sizeof(struct yaffs_obj_list), GFP_NOFS); //n_obj=YAFFS_ALLOCATION_NOBJECTS=100 //sizeof(struct yaffs_obj)=144 //sizeof(struct yaffs_obj_list)=8
(100*144+8) *1=14408 *1=0x3848 *1
加上之前的0x2213c,一共是0x25984
这里为什么是乘1,且执行yaffs_get_tnode1次所申请的内存堆空间也没有计算,因为第四项allocator里面有一个tnode和obj空间,所以只需要额外再申请3个obj空间。但是,执行一次yaffs_create_free_objs,会申请100个obj的内存空间,后面两个也就不需要申请了。
-
yaffs_mount->yaffs_mount_common->yaffs_guts_initialise ->yaffs_summary_init
sum_bytes = dev->param.chunks_per_block * sizeof(struct yaffs_summary_tags); //64*3*4=768 yaffs_summary_tags结构体是3个unsign chunks_used = (sum_bytes + dev->data_bytes_per_chunk - 1)/ (dev->data_bytes_per_chunk - sizeof(struct yaffs_summary_header)); //(768+4096-1)/{4096-(4*4)}=1 yaffs_summary_header结构体是4个unsign dev->chunks_per_summary = dev->param.chunks_per_block - chunks_used; //64-1=63 sum_tags_bytes = sizeof(struct yaffs_summary_tags) * dev->chunks_per_summary; //3*4*63=756 dev->sum_tags = kmalloc(sum_tags_bytes, GFP_NOFS); dev->gc_sum_tags = kmalloc(sum_tags_bytes, GFP_NOFS);
这里两个是分配756 *2=0x2F4 *2=0x5E8
加上之前的0x25984,一共是0x25F6C
-
yaffs_mount->yaffs_mount_common->yaffs_guts_initialise -> yaffs2_checkpt_restore -> yaffs2_rd_checkpt_data -> yaffs2_checkpt_open
if (!dev->checkpt_buffer) dev->checkpt_buffer = kmalloc(dev->param.total_bytes_per_chunk, GFP_NOFS); //4096=0x1000 if (!dev->checkpt_block_list) dev->checkpt_block_list = kmalloc(sizeof(int) * dev->checkpt_max_blocks, GFP_NOFS); //4*257=1028=0x404
一共是0x1404,
加上之前的0x25F6C,一共是0x27370
-
yaffs_mount->yaffs_mount_common->yaffs_guts_initialise -> yaffs_deinit_blocks
这里释放了前面的第三项:block_info和chunk_bits,一共是0x10000
if (dev->block_info_alt && dev->block_info) vfree(dev->block_info); else kfree(dev->block_info); if (dev->chunk_bits_alt && dev->chunk_bits) vfree(dev->chunk_bits); else kfree(dev->chunk_bits);
-
yaffs_mount->yaffs_mount_common->yaffs_guts_initialise -> yaffs_deinit_tnodes_and_objs
这里释放了tnodes和objs,前面的第4项: allocator,是 0x24,还有第五项的tnodes和objs
-
接下来,又运行了3-5项yaffs_init_blocks、yaffs_init_tnodes_and_objs和yaffs_create_initial_dir,重复申请3-5项内存堆空间。
-
yaffs_mount->yaffs_mount_common->yaffs_guts_initialise->yaffs2_scan_backwards
这个函数中是,n_blocks * sizeof(struct yaffs_block_index)
yaffs_block_index是一个结构体有2个32为变量,就是8字节
4096*8=0x8000
block_index = kmalloc(n_blocks * sizeof(struct yaffs_block_index), GFP_NOFS);
之后的yaffs_release_temp_buffer函数释放了第一项创建的临时缓冲区(部分)
那么这第11项,是先要求0x8000的,后面释放的空间并不能减少总体需求(如果是先释放,再要求申请0x8000则可以减少总体需求)。
加上之前的0x27370,一共是0x2F370,
最终测试0x2F300也可以,因为起始点没有确定(0x2000),当然不排除中间还可能有其他的分配。这些分配的意义也似懂非懂…,而且实际工程中和其他功能搭配时也需要更多的内存堆,比如lwip网络。省事情的话其实直接调大就行了。
刚开始调的时候这些问题还是挺麻烦的,因为yaffs本身的跟踪打印(tracing)和报错处理会很重复,某个错误可能会有许多问题造成的,需要在额外添加打印信息定位。
如果更加理解yaffs的分配内存原理,就可以更快定位问题并解决,添加打印信息来定位问题,也行也很快,哪一个更好的呢?如果官方提供指导文件更详细更好,自己看代码理解无疑会很耗费时间。
驱动
NAND驱动采用的是ti-startware的,略作修改,是能支持yaffs2的oob存储区,整个spare区域分配的策略是:坏块检测位(BBM,2位)+oob(长度oob_len)+ECC,后两个的位置也可以改变。如果要改变BBM,那么对应的坏块检测和标记函数也要更改。
有两个问题:
- ti-startware的NAND驱动不识别地址为空(NULL)的数据缓存RX_Buffer和TX_Buffer数组,传进来空的,NAND读写命令会直接卡死。此时yaffs不会报错,直接停止运行,需要添加识别是否为空数组,如果为空,则就读写数据,仅读写ECC和oob
- ti-startware的NAND驱动对未写入的块进行读操作时(根据NAND Flash的特性,这些块的内容全是0xFF),会出现返回是ECC报错且无法纠正。但是,yaffs中需要读未写入的块,那么解决办法有两个,一是采用yaffs的ECC校验方法,它可以识别;二是在出现返回是“ECC报错且无法纠正”时,附加识别是否是块内容全部为0xFF,来确定是否要更改返回值。
还有就是,我依旧觉得单单看源码是不能够看出这种问题的,或者可能是需要看的特别细致,上下文全部参透才能够完全掌握它的能力,它能完成什么,不能完成什么。官方完整的注意事项也是需要的。