nandflash文件系统

转自: http://jnds.yo2.cn/articles/%E5%9C%A8nand-flash%E4%B8%8A%E5%BB%BA%E7%AB%8Byaffs2%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%EF%BC%88%E4%B8%80%EF%BC%89.html
在NAND FLASH上建立YAFFS2文件系统(一)

经过了半个多月的努力,终于搞定nandflash的mtd驱动和上层的yaffs2文件系统。这半个多月来几乎每天都要和挫败感斗争,每天都要忍受这个方面,那个方面的bug。想想自己这半个多月来,也算看不少资料,得到不少人的帮助,总算是有点心得。鉴于国内搞yaffs2文件系统方面的资料还是很少,就把自己的心得拿出来与大家共享。
      不说闲话了,先介绍一些背景资料    一. 闪存
我们常说的闪存其实只是一个笼统的称呼,准确地说它是非易失随机访问存储器(NVRAM)的俗称,特点是断电后数据不消失,因此可以作为外部存储器使用。而所谓的内存是挥发性存储器,分为DRAM和SRAM两大类,其中常说的内存主要指DRAM,也就是我们熟悉的DDR、DDR2、SDR、EDO等等。闪存也有不同类型,其中主要分为NOR型和NAND型两大类。
闪存的分类
  NOR型与NAND型闪存的区别很大,打个比方说,NOR型闪存更像内存,有独立的地址线和数据线,但价格比较贵,容量比较小;而NAND型更像硬盘,地址线和数据线是共用的I/O线,类似硬盘的所有信息都通过一条硬盘线传送一般,而且NAND型与NOR型闪存相比,成本要低一些,而容量大得多。因此,NOR型闪存比较适合频繁随机读写的场合,通常用于存储程序代码并直接在闪存内运行,手机就是使用NOR型闪存的大户,所以手机的“内存”容量通常不大;NAND型闪存主要用来存储资料,我们常用的闪存产品,如闪存盘、数码存储卡都是用NAND型闪存。
  这里我们还需要端正一个概念,那就是闪存的速度其实很有限,它本身操作速度、频率就比内存低得多,而且NAND型闪存类似硬盘的操作方式效率也比内存的直接访问方式慢得多。因此,不要以为闪存盘的性能瓶颈是在接口,甚至想当然地认为闪存盘采用USB2.0接口之后会获得巨大的性能提升。
  前面提到NAND型闪存的操作方式效率低,这和它的架构设计和接口设计有关,它操作起来确实挺像硬盘(其实NAND型闪存在设计之初确实考虑了与硬盘的兼容性),它的性能特点也很像硬盘:小数据块操作速度很慢,而大数据块速度就很快,这种差异远比其他存储介质大的多。这种性能特点非常值得我们留意。
NAND型闪存的技术特点
  内存和NOR型闪存的基本存储单元是bit,用户可以随机访问任何一个bit的信息。而NAND型闪存的基本存储单元是页(Page)(可以看到,NAND型闪存的页就类似硬盘的扇区,硬盘的一个扇区也为512字节)。每一页的有效容量是512字节的倍数。所谓的有效容量是指用于数据存储的部分,实际上还要加上16字节的校验信息,因此我们可以在闪存厂商的技术资料当中看到“(512+16)Byte”的表示方式。目前2Gb以下容量的NAND型闪存绝大多数是(512+16)字节的页面容量,2Gb以上容量的NAND型闪存则将页容量扩大到(2048+64)字节。
  NAND型闪存以块为单位进行擦除操作。闪存的写入操作必须在空白区域进行,如果目标区域已经有数据,必须先擦除后写入,因此擦除操作是闪存的基本操作。一般每个块包含32个512字节的页,容量16KB;而大容量闪存采用2KB页时,则每个块包含64个页,容量128KB。
  每颗NAND型闪存的I/O接口一般是8条,每条数据线每次传输(512+16)bit信息,8条就是(512+16)×8bit,也就是前面说的512字节。但较大容量的NAND型闪存也越来越多地采用16条I/O线的设计,如三星编号K9K1G16U0A的芯片就是64M×16bit的NAND型闪存,容量1Gb,基本数据单位是(256+ 8) ×16bit,还是512字节。
  寻址时,NAND型闪存通过8条I/O接口数据线传输地址信息包,每包传送8位地址信息。由于闪存芯片容量比较大,一组8位地址只够寻址256个页,显然是不够的,因此通常一次地址传送需要分若干组,占用若干个时钟周期。NAND的地址信息包括列地址(页面中的起始操作地址)、块地址和相应的页面地址,传送时分别分组,至少需要三次,占用三个周期。随着容量的增大,地址信息会更多,需要占用更多的时钟周期传输,因此NAND型闪存的一个重要特点就是容量越大,寻址时间越长。而且,由于传送地址周期比其他存储介质长,因此NAND型闪存比其他存储介质更不适合大量的小容量读写请求。

二.MTD
MTD是memory technology Device的缩写。MTD支持类似于内存的存储器,它是底层硬件和上层软件之间的桥梁。对底层来说,它无论对nor型或是nandflash都有很好的驱动支持,对上层来说,它抽象出文件系统所需要的接口函数。同时由于flash自身的特别之处(既有类似块设备的特点,又有类似字符设备的特点),MTD可以把flash同时为块设备和字符设备。有了MTD,编写flash的驱动变得十分轻松,因为上层的架构都已经做好,我们只用看看flash的datasheet,写最底层的控制时序即可。 
以下内容为翻译自mtd官方网站http://www.linux-mtd.infradead.org/archive/index.html
mtd致力于为存储器,尤其是flash,设计一个通用的linux下的子系统。
设计这个系统的目标在于,通过这个系统所提供的硬件驱动和上层系统之间的接口,我们可以方便的为新的硬件编写驱动。
对于底层的硬件驱动来说,它们所以提供是读,写,擦除的流程。而文件的存储形式是和他们无关的(如FTL,FFS2等等),用恰当的形式存储用户的数据那时上层系统关注的事情。
MTD的用户模块
MTD为用户提供五种可以直接在用户空间使用的模块
字符设备
块设备
flash转换层
nandflash转换层
JFFS2文件系统

三.yaffs2文件系统
针对于flash的文件系统有很多,据我了解有jffs(1,2,3),yaffs(1,2)。还有商业的三星开发的RFS(健壮文件系统),专门针对三星自己的nand和onenand,从底层驱动到上层文件系统一条龙服务,而且号称和fat格式100%兼容。当时看得我直流口水,心里把三星恨的咬牙切齿。 下面主要介绍一下开源的yaffs文件系统。
Yaffs(Yet Another Flash File System)文件系统是专门针对NAND闪存设计的嵌入式文件系统,目前有YAFFS和YAFFS2两个版本,一般说来,YAFFS对512byte/page以下都有很好的支持,而更大的页就需要YAFFS2了,如2K/page。
Yaffs文件系统有些类似于JFFS/JFFS2文件系统,与之不同的是JFFS1/2文件系统最初是针对NOR FLASH的应用场合设计的,而NOR FLASH和NAND FLASH本质上有较大的区别,所以尽管JFFS1/2 文件系统也能应用于NAND FLASH,但由于它在内存占用和启动时间方面针对NOR的特性做了一些取舍,所以对NAND来说通常并不是最优的方案。
Yaffs对文件系统上的所有内容(比如正常文件,目录,链接,设备文件等等)都统一当作文件来处理,每个文件都有一个页面专门存放文件头,文件头保存了文件的模式、所有者id、组id、长度、文件名、Parent Object ID等信息。因为需要在一页内放下这些内容,所以对文件名的长度,符号链接对象的路径名等长度都有限制。
前面说到对于NAND FLASH上的每一页数据,都有额外的空间用来存储附加信息,通常NAND驱动只使用了这些空间的一部分,YAFFS正是利用了这部分空间中剩余的部分来存储文件系统相关的内容。同时由于支持的page变大,YAFFS2使用更多的spare space来存储这些信息。在结构上YAFFS和YAFFS2有一定的不同,具体结构可以去看一看这篇文档http://www.aleph1.co.uk/node/38
那么这个文件系统是如何运作起来呢。
操作文件系统的第一步自然是取得SuperBlock了,Yaffs文件系统本身在NAND Flash上并不存在所谓的SuperBlock块,完全是在文件系统mount的过程中由read_super函数填充的,不过有意思的一点是,由于物理上没有存储superblock块,所以NAND Flash上的yaffs文件系统本身没有存储filesystem的魔数(MagicNum),在内存中superblock里的s_magic参数也是直接赋值的,所以存储在NAND FLASH上的任何文件系统都能被当作yaffs文件系统mount上来,只是数据都会被当作错误数据放在lost+found目录中,不知道这算不算yaffs文件系统的一个bug。
通常一个具体的文件系统在VFS的Super_block结构中除了通用的数据外,还有自己专用的数据,Yaffs文件系统的专用数据是一个yaffs_DeviceStruct结构,主要用来存储一些相关软硬件配置信息,相关函数指针和统计信息等。
在mount过程执行read_super的过程中,Yaffs文件系统还需要将文件系统的目录结构在内存中建立起来。由于没有super块,所以需要扫描Yaffs分区,根据从OOB中读取出的yaffs_tags信息判断出是文件头page还是数据page。再根据文件头page中的内容以及数据page中的ObjectID/ChunkID/serial Number等信息在内存中为每个文件(Object)建立一个对应的yaffs_object对象。
在yaffs_object结构中,主要包含了:
    如修改时间,用户ID,组ID等文件属性;
    用作yaffs文件系统维护用的各种标记位如脏(dirty)标记,删除标记等等;
    用作组织结构的,如指向父目录的Parent指针,指向同级目录中其他对象链表的     siblings双向链表头结构
此外根据Object类型的不同(目录,文件,链接),对应于某一具体类型的Object,在Yaffs_object中还有其各自专有的数据内容
       普通文件:文件尺寸,用于快速查找文件数据块的yaffs_Tnode 树的指针等
       目录:目录项内容双向链表头(children)
       链接:softlink的alias,hardlink对应的ObjectID
除了对应于存储在NAND FLASH上的object而建立起来的yaffs_object以外,在read_super执行过程中还会建立一些虚拟对象(Fake Object),这些Fake Object在NAND FLASH上没有对应的物理实体,比如在建立文件目录结构的最初,yaffs会建立四个虚拟目录(Fake Directory):rootDir, unlinkedDir, deleteDir, lostNfoundDir分别用作根目录,unlinked对象挂接的目录,delete对象挂接的目录,无效或零时数据块挂接的目录。
通过创建这些yaffs_object,yaffs文件系统就能够将存储在NAND FLASH上数据系统的组织起来,在内存中维护一个完整的文件系统结构
另外,我在看YAFFS2的源代码的时候发现,YAFFS2再mount和umount和YAFFS有所区别,增加一个checkpoint机制,每次在umount的时候,YAFFS2会开辟专门几个block用来存取一些信息,等待下次mount的时候就不需要扫描整个flash,而只有找出这几个块就可以了,这样可以大大加速mount的时间。不过仔细的原理我也没有研究过。有兴趣的话可以看一看这封邮件http://www.aleph1.co.uk/pipermail/yaffs/2006q2/002019.html

累了,先到这里,下次写写移植的流程




在NAND FLASH上建立YAFFS2文件系统(二)

我们用的硬件是omap平台,跑得是linux-2.4.21。其实在这个版本的源代码里已经有了mtd的支持。不过看一下代码可以发现,这个版本mtd驱动对nandflash的支持并不好,而且支持的最大的page不过512byte,可能是代码比较早的原因吧。因此移植的第一步是更新mtd驱动。
首先从mtd官方网站下载最新的cvs(好像只有支持ipV6的,才可以直接要连到cvs库。或者去ftp下载)。不过好像最新的cvs代码有一点问题,我用得是mtd-snapshot20050519。解压这个snapshot,然后是打补丁。相信这一步应该都什么问题。打完补丁后,可以去drivers/mtd/nand/看一看,会发现多了好多文件,其中最最重要的是nand_base.c,关于读写擦的流程都是这里实现的。
接下来的事情比较关键,就是定义底层的读写端口,读写控制。我们可以仿照驱动里提供spia.c,针对自己的硬件写一个XXX.C。上层的代码都已经做好,都是一些函数指针,我们只要把我们自己特定一些函数挂上去就可以了。这一步说难也难,说简单也简单。一开始可能觉得无从下手,其实只要多读几遍nand目录下提供的很多的例子程序,就会有些思路了。写完这个,就修改一下nand目录下的MakeFile.common,如果你懒一点,可以把spia替换你的XXX.o,到时候记得配置内核在mtd的nand下选择spia支持就行。你要想做得规范一点,其实也简单,首先在MakeFile里加上obj-$(CONFIG_MTD_NAND_XXX) += XXX.o。然后修改Config.in,加上对XXX的配置支持就可以了。
接下来就是按部就班的配置内核,编译。如果没错,那么恭喜你。不过一般会有错,我碰到的是提示少了几个头文件。我在snapshot里可以找到,然后一一拷贝的include/linux下就可以了,^_^
开始测试:
首先在内核启动的时候,mtd会检测硬件系统中是否有flash存在,这一步是通过读取flash的ID来判断。如果这一步是正常的,那么说明底层驱动的流程和硬件上是没有错误了。很不幸,我在这里就挂了。系统提示can‘t kernel paging request at virtual address 0x0c000000,显示了一大堆寄存器和堆栈就死在那里了。好不郁闷。google了好久,发现这一个是一个地址映射的错误。因为linux是分为内核空间和用户空间的。对于arm来说,内核空间的地址应该是3G-4G处,3G以下是用户空间。而我在写我自己的xxx.c文件的时候,IO端口是这样设置的
this->IO_ADDR_R = 0x0c000000;
this->IO_ADDR_W = 0x0c000000;
显然0x0c000000是不属于内核空间的,因此造成了上述问题。
因此我们要把读写命令端口映射到内核空间去,这便是虚拟地址。
this->IO_ADDR_R = (unsigned long)ioremap(0x0c000000, SZ_1K);
this->IO_ADDR_W = (unsigned long)ioremap(0x0c000000, SZ_1K);
因此要用ioremap()函数。这些函数都定义在include/arch/asm-arm/io.h
至于物理地址和虚拟地址应是如何映射的,mm.c有定义
好了,现在内核可以顺利启动了,flash可以顺利的认出来了。嘿嘿,扫描了一下,还找出了几个坏块。

现在flash现在已经成为linux一种设备了。下面这两条命令
mknod /dev/mtd0 c 90 0
mknod /dev/mtdblock0 b 31 0
把flash的第一个分区映射为字符设备和块设备。mtd/util/提供了许多有用的工具,比如nandwrite,flash_erase等等。交叉编译后就可以使用了。
flash_eraseall /dev/mtd0 把分区零的内容全部删除。
随便新建一个文件xxx,然后nandwrite -p /dev/mtd0 xxx。其中参数p表示允许填充。因为文件的大小不一定是flash的page的整数倍。
如果提示写进去了,那么基本没问题了。我不放心,又用自己的测试程序在ccs里测了一下,果然写进去了。那真叫一个开心啊。迫不及待的mount -t yaffs2 /dev/mtdblock0 /mnt
提示:NAND:geometry problem chunk size is 2048 type is yaffs2
然后退出了mount进程。一盘冷水浇了下来,告诉我苦难的里程刚刚开始咯……

待续

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值