标签:linux yaffs
10年时候同事做的一个PPT, 把它整理上来.
Outline
NAND Flash介绍
YAFFS2 Overview
Tnode Structure
YAFFS2 Object
YAFFS2 Misc
Reference
Nand Flash
页(page)
页是Nand Flash的写入操作的最小单位
○现在常见的flash page size是2KB,老的nand flash多为512B
○每一个页,对应还有一块区域,叫做spare area,Linux系统中一般称之为OOB(Out of Band)。这块区域用于存放数据的校验值,文件系统也经常放些标志数据在里面。 2KB/page的flash一般spare area为64B,512B/page为16B
块(block)
块是Nand Flash的擦除操作的最小单位
○块由多个页组成,ICE用的flash为64pages/block
写/擦特性
Flash的擦除操作是以block块为单位的,一次性地把整个块擦除为1,也就是里面的内容全部都是0xFF了,这个过程是充电过程
Write时,以页为单位,将对应的存储单元放电,所以才说,write过程只能从1变成0
Overwrite / Partial Page Programming
某些flash支持修改Page中的Data时可以更改部份的Bit而不需要将Page Erase后才更改。如Page中的Data为10111010,要将该Data改成10100010则只需将其中两个Bit更改为0即可,不需先Erase再写入。
这个功能常会发生这样的问题:在更改Bit时可能会有部分bit保持不变
这个功能会给硬件设计增加难度,新的flash已不提供这个功能
Flash和普通设备相比所具有的特殊性
|
普通设备(硬盘/内存等)
|
Flash
|
读取/写入的叫法
|
读取/写入
|
读取/编程(Program)
|
读取/写入的最小单位
|
Bit/位
|
Page/页
|
擦除(Erase)操作的最小单位
|
Bit/位
|
Block/块
|
擦除操作的含义
|
将数据删除/全部写入0
|
将整个块都擦除成全是1,也就是里面的数据都是0xFF
|
对于写操作
|
直接写即可
|
在写数据之前,要先擦除,然后再写
|
Nand Flash对FS的要求
Nand Flash可能发生bit位翻转
FS需要有ECC机制。因为一般NAND都自带ECC,所以这点不是必备
Nand Flash出厂就有坏块,使用时也可能出现坏块
FS需要能识别/标记坏块
Nand Flash寿命有限(SLC约10万次擦写)
FS需要尽量平均的使用各block
FS需要提供buffer机制
Nand Flash的Partial Page Programming特性
FS需要设计机制去区分一个数据是有效还是无效
Nand Flash的擦/写特性
FS需要针对这个特性做特殊设计,如何实现garbage collection,如何提高performance
YAFFS2 Overview
是什么?
YAFFS(Yet Another Flash File System) 是专门为NandFlash设计的文件系统
○JFFS/JFFS2文件系统是为NorFlash设计的
主要由Aleph One公司的Charles Manning开发
以GNU GPL协议开放源码
YAFFS目前有YAFFS1、YAFFS2两个版本
○一般来说,YAFFS对小页面(512B+16B/页)有很好的支持,YAFFS2对更大的页面(2K+64B/页)支持更好。
○但两个版本有很多设计上的不同,不只是简单大小区别。Android中使用的是YAFF2,本文以YAFFS2为主
YAFFS1是不完整的LFS( log-structured file system),YAFFS2是真正的LFS
特点
针对Nand特性优化,提高performance及延长Nand寿命
○加载速度快,耗用内存少
○Wear Levelling延长Nand寿命
○Error Correction 提高了可靠性,尤其是使用时断电情形
极好的可移植性
○YAFFS采用模块化设计,虽然最初是用在linux系统上的,但是也已经移植到其他系统比如wince。甚至可以使用在没有操作系统的设备上(“YAFFS/Direct”)
不断的发展
○能支持很多新的flashIt now handles larger page sizes of 2KB
○正在开发对MLC NAND的支持
YAFFS concept
Object
is anything that is stored in the file system:
○Regular data files
○Directories
○Hard-links
○Symbolic links
○Special objects (pipes, devices etc).
All objects are identified by a unique integer objectId
Chunk
the unit of allocation is the chunk
Typically a chunk will be the same as a NAND page(may map to multiple pages)
How YAFFS2 Stores Files (1/2)
YAFFS2 is a true LFS
A true log structured file system only ever writes sequentially
○减少随机读写,提高效率
○对于Nand Flash,可避免overwrite,迎合擦/写特性,并能平均使用各块
YAFFS的最小操作单位是chunk
YAFFS2的策略—write once
Zero overwrites.
○YAFFS1 only ever overwrites a single byte in the spare area of the chunks to set the deletion marker. More modern NAND devices are less tolerant of overwrites and YAFFS2 performs no overwrites at all.
Sequential chunk writing within a block.
○More modern NAND reliability specifications tend to assume sequential writing. Since YAFFS2 does not write deletion markers, the writing of chunks within a block is strictly sequential.
Chunk类型
Data chunk: A chunk holding regular data file contents
Object Header: A descriptor for an object, holding details such as the identifier for the parent directory, object name, etc.
Tags in Chunk
ObjectId: Identifies which object the chunk belongs to.
ChunkId: Identifies where in the file this chunk belongs.
○ChunkId==0: this chunk contains an objectHeader.
○ChunkId==1: the first chunk in the file (ie. At file offset 0),
○ChunkId==2: the next chunk
Byte Count: Number of bytes of data if this is a data chunk.
Sequence Number:
○As each block is allocated, the file system‘s sequence number is incremented and each chunk in the block is marked with that sequence number.
○So it provides a way of organizing the log in chronological order.
Shrink Header Marker:
○The shrink header marker is used to mark object headers that are written to shrink the size of a data file..
Indicates that a file has shrunken in size
Prevent those object headers from being deleted by garbage collection.
Example
假设对某文件依次进行如下操作
○Create file.
○Write 5MB of data.
○Truncate/resize file to 1MB.
○Seek to 2MB
○Write 1MB of data.
○Close file.
此刻文件为3MB,即resize后的1MB + 空的1MB + 最后写进去的1MB
YAFFS在Nand上留下如下数据:
○1. Object header for file creation (file length = 0)
○2. 5MB worth of data chunks (0 to 5MB)
○3. Object header for truncation (file length = 1MB)
○4. 1MB worth of data chunks (2MB to 3MB)
○5. Object header for close (file length = 3MB)
现在Nand上有用的数据如下:
○The first 1MB of data created in step 2 above.
○The 1MB of data created in step 4 above.
○The object header created in step 5 above.
第3步中截掉的4MB数据依然在flash上,必需有个标识来标记。所以YAFFS截掉数据后的object header里设计有一个shrink header marker。
这对GC的影响是
○碰到含有shrink flag的block,只有把在之前分配的所有block均erase了,才erase它。
○也不能延长它的生存期,因为如果延长,则无法确定什么时候应该erase。
小结
YAFFS2在分配chunk的时候遵循两个原则:
一是在block内部严格从低地址的chunk向高地址的chunk按次序分配
二是一定要将一个block内的page全部分配完毕后才另行选择block进行分配。而且在分配的时候每挑选一个block,Sequence Number就会递增一个序号。这样我们从序号就可以推断出该block的分配顺序。
Object header和data:
yaffs2会在应用程序作clsoe()系统调用的时候将新的object header写入flash。因此,可以有这样的推论:
○header所在block的序号,一定大于等于data所在block的序号。
○如果一个block信息结构内的hasShrinkHeader字段为1,并且该block的序号在系统中最小,我们就可以认为该block上的所有header对应的object已经没有残余信息留在flash上了——这些残余信息如果存在,它们所在block的序号一定更小。
Tnode Structure
Tnode的由来
在YAFFS2中,Object的内容是被存储在flash的chunk中,它们通常有一个实际储存地址Physical Address
程序要读取Data时是以Logical Address(相对于该Object所产生出来的偏移位址)的方式到所指定的位址进行存取
这时就需要有一个机制将Logical Address转换为Physical Address,才可从存储设备上读出Data。
○这个机制最简单的方法就是在RAM上建立一个Logical Address到Physical Address的映射表格(Mapping Table)。直接将Logical Address对映至Physical Address。
YAFFS中这个转换机制是由Tnode机制实现的,具体来说Tnode机制把chunk组织起来,便于访问。
什么是Tnode
YAFFS使用的转换机制全称是Tnode Structure或Tree Node Structure,它是由多个Tnode(或Tree Node)组成。
Flash Memory挂载时,YAFFS会为每个Object在RAM中建立一个tree,当有一个要求要访问Object的某个Chunk(Logical Address)时,则会经由Tree Node Structure的转换方式,找到Chunk在flash上对应的page。
为何使用tree来实现
○Object的大小不固定,而且随时会增减。所以无法使用数组来记录实际chunk的地址
○使用tree的优点是转换迅速,可提升整体的存取速度
ü如果使用mapping table,则地址转换是一个遍历过程,这种线性搜索的时间复杂度为O(N)
üTree的时间复杂度是O(log N)
Tnode Structure结构
Tnode根据level的不同分成两种
Lowest-Level Tnode(Level 0 Tnode):每个Lowest-Level Tnode由16个Entries所组成。
Internal Tnode:Level 0往上的Tnode,每个Internal Tnode由8个Pointer组成,pointer指向下一级的Tnode,或者为Null
Tnode Structure Traverse
假设已经建立好一个tree
假设系统要求访问Object的某个chunk,比如chunk 0x235,步骤如下:
○将十六进制转化为二进制,0x235 -> 0000001000110101
○将二进制按照3n+4的形式分割,即分成000.000.100.011.0101,每一个切割区代表一个Level,由于Lowest-Level包含有16个Entries,因此分割出4个Bit(bit0~3)。Internal Level的Tnode均包含8个Pointer,因此其余皆分割为3个Bit。
○根据所分割出来的Bit整理成下表
Level
|
Bits
|
Selected Value
|
3 or more if they exist
|
>=10
|
Zero
|
2
|
9 to 7
|
100 binary = 4
|
1
|
6 to 4
|
011 binary = 3
|
0
|
3 to 0
|
0101 binary = 5
|
Tnode Structure 的建立
建立过程:
当Tree Node Structure刚开始建立时,仅会建立Lowest-Level Tnode
当File所配置的Chunk数超过16个时,则此Tree会建立一个internal Tnode,并将其第0个Pointer指向原本的Lowest-Level Tnode
当读取到的Chunk数愈来愈多时,会一直新增Tnode,且Tree亦会愈来愈高,其Max-Level最大为6
Tnode Structure 的增长
使用情景:
在File已在RAM建立好Tree Node Structure后,若此File的Size变大时,则会再多配置Chunk给此File,在将Data写入至Page后,则会去更新File的 Tree Node Structure,将新配置到的Chunk加入至Tree Node Structure中
更改步骤:
1. 搜寻新配置到的Chunk ID。
2. 若在Internal-Level搜寻的过程中,发现没有Tnode,则建立一个Internal-Level的Tnode。
3. 当搜寻到Lowest-Level时,若也无Tnode存在,则建立一个Lowest-Level的Tnode。
4. 根据Chunk ID中Level 0所代表的值x,到Lowest-Level Tnode中的第x个Entry中检查有无该Chunk的实体位置存在,若没有则将实体位置填入该Entry,若已存在则将较新的Chunk的实体位置写入该Entry中。
Tnode Structure 的缩减
resize
根据新的object大小计算出,需要释放的chunk的logical chunk index 的范围,随后在Tree中将相应的physical chunk index置为0,最后根据 physical chunk index将chunk从flash上“删除”
delete
用递归的方法来遍历tree。递归时,如果以考察的Tnode为顶点,把它以及它下面所有的Tnode看作一个tree
○退出递归的条件:当当前Tnode中没有包含数据并且可删除时,删除Tnode并返回NULL,否则返回当前Tnode。
○判断是否可删除:假设进入递归传进来的del0等于0,那么从最高的Tnode到level0 Tnode中的所有路径中,只有在最左边的那条路径上的Tnode对应的del0等于0,其它Tnode对应的del0均为 1。
○Tnode是否包含数据:
ü如果考察的Tnode为level 0 Tnode,看Tnode是否包含physical chunk index,有的话则该Tnode包含数据;
ü如果考察的Tnode为internal Tnode,则看其包含的下一层Tnode是否包含数据,只要有一个包含数据,则考察的Tnode包含数据。如此递归下去
ü当该Tnode Tree的所有level 0 Tnode不包含physical chunk index,则考察的Tnode不包含数据。
○总结一下:经过resize后,Tnode Tree的某些level 0 Tnode不包含physical chunk index,此时 delete函数会在Tnode Tree中将这些Tnode从树中“剪下来”。整个过程从level 0向上扫描,以考察的 Tnode为顶点,把它以及它下面所有的Tnode(包含被剪掉的)看作一个Tnode Tree,当该 Tnode Tree 的所有level 0 Tnode(包含被剪掉的)不包含physical chunk index,将考察的Tnode从树中剪下来。而在最左边路径中的Tnode除外。
单独处理最左边的路径
Level 0 Tnode的Entry
Level 0均有16个entry,记载着chunk在磁盘上的位置。
ICE中的flash为512MB,但我们分partition使用,其中最大的是userdata 207MB (2KB * 64chunks * 1656blocks),有105984个chunk
Entry需要能索引所有chunk,则至少需要log2105984 ≈ 16.7,即17bit的空间
YAFFS2规定Tnode的大小tnodeSize必须为4Byte的整数倍,而Tnode有16个entry,所以entry大小为偶数bit数就可以了,下面表达式里的tnodeWidth即为entry的大小, YAFFS_NTNODES_LEVEL0为level 0的entry数,取16
○tnodeSize = (dev->tnodeWidth * YAFFS_NTNODES_LEVEL0)/8;
故对于userdata而言,每个entry占用18bit,这样每个level 0的Tnode占用36Bytes。
因为tnodeWidth不一定为byte倍数,所以无法使用数组”[]”的方式访问。
对于比较小的partition,为了减少复杂度,tnodeWidth还是取16bit,这时每个level 0的Tnode占用32Bytes。16bit能索引最多64K chunks,因为每个block有64个chunk,相当于能覆盖1024个block,每个chunk 2k的话,相当于128MB的flash空间。
访问Level 0 Tnode
读:
pos 为 physical chunk index 数组的索引,取0到15。YAFFS_TNODES_LEVEL0_MASK等于0xf,用于约束pos为合法值。
下面以tnodeWidth=18bit为例,tnodeMask为0x3ffff,假设访问的是第4个physical chunk index,即pos等于3,bitInMap等于54,wordInMap为1,bitInWord为22。
map[]为level 0以int表示的数组,那么会占用map[1]的高10bit,以及map[2]的低8bit。所以val=tnodeMask&((map[1]>>(32-22))+(map[2]<<(32-(32-22)))
写:
和上页一样以tnodeWidth=18bit为例,tnodeMask为0x3ffff,假设即pos等于3,bitInMap等于54,wordInMap为1,bitInWord为22。
mask=0xffc00000,掩住32bit中的高10bit,然后先将map[1]中的高10位清零,再将val的低10位放到map[1]的高10位上
新bitInWord为10,mask为0xff,将map[2]的低8位清零,再将val的高8位放到map[2]的低8位上
关于chunk group
前面提到为了能寻址大的flash空间,Level 0的entry使用了超过16个bit来表示
还有一种方法即是chunk group,通过将若干个chunk合成一组用同一个id来表示,也可以增加系统所能寻址的chunk范围
YAFFS2 Object
Object in RAM
Create
YAFFS2每次在需要创建新的object时,总是每次创建YAFFS_ALLOCATION_NOBJECTS(100)个object,并将这存放着100个object的内存块,如左下图放在dev->allocatedObjectList中,这样方便unmout时释放内存
此外新创建的object被插入到单向链表dev->freeObjects中。当需要使用一个object时,则dev->freeObjects中取出一个object;当object被使用完毕时,又被归还到该链表中
Object Hash Table
正在被使用的object的指针被放入hashtabledev->objectBucket[YAFFS_NOBJECT_BUCKETS]中,其中YAFFS_NOBJECT_BUCKETS等于256。
根据obj->objectId来索引该hash
Object ID分配
Object Header
Object Header
绝大多数Object均有objectheader, objectheader只存在于Flash中,在内存中没有镜像。
header一旦被写入chunk,就不能再修改,只能在另一个chunk中写入一个新的objectheader。
新header中shrink flag、shadow object、parent、file size是不继承于上一个object header
没有object header的Object
directory有两种类型
○一种是普通的directory,具有object header
○另外一种是fake directory,没有object header,只存在于内存中。它们是“root”、“lost+found”、“deleted”与“unlinked”
YAFFS2 Misc
Garbage Collection
Why
快没有可用block时,已经使用过的block会有很多无效数据。通过尽量将正在使用的chunk挪到同一个block上,来腾出block。
解决soft delete chunk、soft delete file的遗留问题
处理坏块
How
根据策略找到最适合做gc的block,如果找不到则退出
遍历该block中的每个chunk,如果chunk数据有用,则复制到新block上,并且在RAM中的数据结构里做对应修改
该block中所有chunk都处理完后,erase这个block以重新使用
When
尽量推迟gc,以降低对性能的影响
YAFFS2会在下列动作时检查是否需要gc:
○将Data写入Flash Memory
○更新yaffs_ObjectHeader
两种情况
○Passive:如果flash上有很多空block,只需要简单的对只有少数chunk在使用的block进行gc
○Aggressive:如果flash上没几块空的了,需要尽量找更多的block进行gc
Example
从currentDirtyChecker到End Block之间寻找Dirtiest Block(包含最多Invalid Chunk的Block
将currentDirtyChecker重置至所发现的Dirtiest Block的位置
将Dirtiest Block中的Valid Page复制至其他的Empty Chunk中。
再将Dirtiest Block erase成Empty Block。
Checkpoint
Why:
mount scanning takes quite a lot of time and slows mounting.
How
Take a snapshot of the YAFFS runtime state at unmount and then reconstitute the runtime state on re-mounting.
Only the state needed to reconstitute the structure of the runtime data will be saved to a set of blocks.
Scan Flash
Why?
YAFFS没有super block,需要动态维护runtime context
如果 YAFFS2 正常关闭,则它会将runtime context作为check point数据记录到flash上。而YAFFS2启动时,会扫描 flash,找到记录着check point数据的 block,并尝试着恢复runtime context。
但是如果恢复出了问题,或者压根就没有正常关闭,则YAFFS2不得不扫描整个 flash,根据读出来的数据来尝试恢复 runtime context
扫描策略
yaffs_Scan()
○传统的扫描策略,并用它解释了为什么要shrink flag。不过该策略已经被最新版本的YAFFS2 放弃了,转而使用了下面的策略。
yaffs_ScanBackwards()
○在扫描了整个flash后,得到需要扫描的block,按照bi->sequenceNumber排序。
○然后从seqence number最大的block开始往sequence number减小的方向扫描;
○在单个block上该策略先扫描physical chunk index大的chunk,并向减小的方向扫描。即,按照chunk的分配顺序反过来扫描
比如扫描已删除文件
○顺着扫描先扫描到data chunk并将它们放入内存中的tnode tree,最后扫描到object header看到shrink flag为true,再将之前扫描到的chunk从tnode tree中“剪”下来。
○倒着扫描先扫描到object header看到shrink flag为true,之后扫描到的相关的data chunk直接被delete chunk。这样确实会提高效率
Internal Cache
Why:
减少NAND的访问次数,尤其防止设计得比较烂的程序
提高performance
How:
仅当page alignment不满足的时候才会使用cahe
设计有5~20个cache slot,每个cache slot包含下面这些信息:
○object id, chunk id, nbytes (the tag info)
○the actual data bytes
○cache slot status (last used counter, dirty,...)
使用least-recently-used (LRU)策略来轮流使用各slot
Reference
How YAFFS Works (by Charles Manning)
YAFFS 介绍
标签:linux yaffs
原文地址:http://blog.csdn.net/jackjones_008/article/details/44236301