yaffs2文件系统结构及应用(转载+总结)

文件系统是包括在一个磁盘(包括光盘、软盘、闪盘及其它存储设备)或分区中的目录结构;一个可应用的磁盘设备可以包含一个或多个文件系统;如果您想进入一个文件系统,首先您要做的是挂载(mount)文件系统;为了挂载(mount)文件系统,您必须指定一个挂载点(所挂载的目录)。

mount –t filesystemtype filesystem mountpoint

e.g mount -t yaffs2 /dev/mtdblock1 /mnt

 

1. Yaffs用到的专用术语

Page : NAND里可访问的最小存储单元,是实际资源存储区,需要跟文件系统中"页"的概念区别开来.具有的属性:读,写;能被标记,表明其是否损坏;还有用于存放校验数据的oob 单元. Page大小有512字节的,也有2K字节的;针对此,oob区域也有不同大小,512B/Page的oob有16个字节,2KB/Page的oob就是前者的4倍,也就是说64个字节.

Block : NAND实现了快速的擦,就是因为有了它. 块结构有两种:小块和大块,小块NAND FLASH包含32个页,每页512+16字节;大块NAND FLASH包含64页,每页2048+64字节。

OOB :备用空间(SpareData,OOB),用来存储ECC校验/坏块标志等信息,每个Page都有自己的oob.

Chunk :Chunk是Yaffs文件系统里的概念,是文件访问的基本单元,表示的是yaffs_object所配置到的逻辑资源存储区。在yaffs中大小与page相同。

Object :构成Yaffs文件系统的各种元素,比如文件,目录,链接,设备等等.

 

 

2. yaffs文件系统介绍

2.1 简介

2.1.1应用场合

Yaffs(Yet Another FlashFile System)文件系统是专门针对NAND闪存设计的嵌入式文件系统,目前有YAFFS和YAFFS2两个版本,两个版本的主要区别之一在于YAFFS2能够更好的支持大容量的NAND FLASH芯片。

 

2.1.2 NOR和NAND的比较

基本上NOR比较适合存储程序代码,其容量一般较小(比如小于32MB),价格较高;而NAND容量可达1GB以上,价格也相对便宜,适合存储数据。一般来说,128MB以下容量NAND FLASH 芯片的一页大小为528字节,用来存放数据,另外每一页还有16字节的备用空间(SpareData,OOB),用来存储ECC校验/坏块标志等信息,再由若干页组成一个块,通常一块为32页16K.

与NOR相比,NAND不是完全可靠的,每块芯片出厂时都有一定比例的坏块存在,对数据的存取不是使用地址映射而是通过寄存器的操作,串行存取数据。

 

2.2 Yaffs文件系统数据在NAND上的存储方式

Yaffs对文件系统上的所有内容(比如正常文件,目录,链接,设备文件等等)都统一当作文件来处理,每个文件都有一个页面专门存放文件头,文件头保存了文件的模式,所有者id,组id,长度,文件名,Parent Object ID等信息。因为需要在一页内放下这些内容,所以对文件名的长度,符号链接对象的路径名等长度都有限制。

 

2.2.1 yaffs1文件系统

前面说到对于NAND FLASH上的每一页数据,都有额外的空间用来存储附加信息,通常NAND驱动只使用了这些空间的一部分,yaffs正是利用了这部分空间中剩余的部分来存储文件系统相关的内容。以512+16B为一个PAGE的NAND FLASH芯片为例,yaffs文件系统数据的存储布局如下所示:

 

0..511

数据区域

512..515

YAFFS TAG

516

Data status byte

517

Block status byte坏块标志位

518..519

YAFFS TAG

520..522

后256字节数据的ECC校验结果

523..524

YAFFS TAG

525..527

前256字节数据的ECC校验结果

 

可以看到在这里YAFFS一共使用了8个BYTE用来存放文件系统相关的信息(yaffs_Tags)。这8个Byte的具体使用情况按顺序如下:

Bits

Content

20

ChunkID,该page在一个文件内的索引号,所以文件大小被限制在2^20PAGE即512Mb

2

2 bits serial number

10

ByteCount该page内的有效字节数

18

ObjectID对象ID号,用来唯一标示一个文件

12

Ecc,Yaffs_Tags本身的ECC校验和

2

unused

其中Serial Number在文件系统创建时都为0,以后每次写具有同一ObjectID和ChunkID的page的时候都加一,因为yaffs在更新一个PAGE的时候总是在一个新的物理Page上写入数据,再将原先的物理page删除,所以该Serial Number可以在断电等特殊情况下,当新的page已经写入但老的page还没有被删除的时候用来识别正确的page,保证数据的正确性。

ObjectID号为18bit,所以文件的总数限制在256k即26万个左右。

对于yaffs2因为针对chunk size大于1k的NAND FLASH,在tags各分量及总体尺寸上都做了修改,以便更快更好的处理大容量的NAND FLASH 芯片。由于Tag尺寸的增大,在512+16B类型的NAND FLASH 上就一个chunk对应一个page的情况,目前就无法使用yaffs2文件系统了(硬件存储方面)。

Yaffs2兼容yaffs1(注册yaffs2文件系统可以操作yaffs1的文件系统)。YAFFS文件系统会根据NANDFlash的页面的大小来主动的配置是使用YAFFS1还是YAFFS2。

 

2.2.2 yaffs2文件系统

设计yaffs2文件系统的初衷是对2kB大小每页,并且严格按照页顺序写的方式的NAND FLASH的支持。

yaffs2文件系统相对yaffs1具有下列优势:

·zero page rewrites means fasteroperation. (YAFFS1 uses a single rewrite in the spare area to delete a page).

·ability to exploit simultaneous pageprogramming on some chips.

·improves performance relative toYAFFS1 speed(write:1.5x to 5x, delete: 4x, garbage collection: 2x)

·lower RAM footprint (approx. 25% to50% of YAFFS1).

·Can support Toshiba/Sandisk MLCparts.

YAFFS2文件系统在设计时就充分考虑了大页NAND FLASH的结构,根据大页NAND FLASH以页面为单位存取的特点,将文件组织成固定大小的页,利用大页NAND FLASH 提供的每个面(2112B,其中前2048B存储数据)64B的备用空间(SpareData,OOB)来存放ECC和文件系统的组织信息,这样不仅能够实现错误检测和坏块处理,还能够提高文件系统的加载速度。

YAFFS2文件系统数据在NAND FLASH 的备用空间内的存储布局:

Field

Comment

Size for 2kb chunks

blockState

Block state. Non-0xFF for bad block

1byte

chunkID

32-bit chunk Id

4 byte

objectID

32-bit object Id

4 byte

nBytes

Number of data bytes in this chunk

2 byte

BlockSequence

Sequence number for this block

4 byte

tagsEcc

ECC on tags area

3 byte

Ecc

ECC,3 bytes/256 bytes of data

24 byte

Total

 

42 byte

blockSequence:记录着各块被分配出去的先后顺序,每分配出去一块,就加1。

 

垃圾回收策略:

YAFFS1删除数据页是通过将NAND FLASH的相应页的一个标志位字节(ObjectID)写为0实现的,而YAFFS2为了能支持某些特殊的NAND FLASH,垃圾回收器读NAND FLASH每页的OOB区,得到这个数据页所属的文件ID,若此文件在YAFFS2虚拟创建的unlinked目录下,则此数据页无效。

/kernel/fs/fs-writeback.c/__mark_inode_dirty 删除文件的函数

/kernel/fs/yaffs2/yaffs_guts.c/yaffs_create_initial_diràyaffs_create_fake_dir创建四个虚拟目录:unlinked, deleted,root and lost and found。

如何选择块则由一定的策略而定,如最少脏页块。如果回收块中有有效数据,则将有效数据复制到新的空闲扇区中,并重新对其进行映射。垃圾回收器的启动时机是在FLASH中可用的扇区低于一定的阈值或者FLASH中的脏扇区的时候。也就是说,当系统企图获得空闲扇区的时候,发现FALSH中的空闲扇区低于预期了,则开始垃圾回收来获取更多的空闲扇区。

具体代码:

Kernel/fs/yaffs2/yaffs_guts.c

Yaffs_vfs.c/ yaffs2_mountàyaffs2_internal_read_super_mtdà yaffs_internal_read_superàyaffs_bg_startàkthread_run(yaffs_bg_thread_fn, (void *)dev, "yaffs-bg- %d",context->mount_id);创建后台垃圾收集线程yaffs_bg_thread_fn:根据可用chunk数A(可用块中)与总可用chunk数(包括A与已有数据的块中的空闲chunk数)之间的关系,确定下次垃圾回收的时间,具体见yaffs_vfs.c/ yaffs_bg_gc_urgency函数。垃圾回收的具体实现函数:yaffs_guts.c/yaffs_bg_gc àyaffs_check_gc

另外,在写数据时也会调用yaffs_check_gc函数。

 

由于文件系统的基本组织信息保存在页面的备份空间中,因此,在文件系统加载时只需要扫描各个页面的备份空间,即可建立起整个文件系统的结构,而不需要像JFFS1/2那样扫描整个介质,从而大大加快了文件系统的加载速度。

 

2.3 yaffs文件系统在在内存中的组织方式

2.3.1 SupperBlock

操作文件系统的第一步自然是取得SupperBlock了,Yaffs文件系统本身在NAND Flash上并不存在所谓的SupperBlock块,完全是在文件系统mount的过程中由read_super函数填充的,不过有意思的一点是,由于物理上没有存储SupperBlock块,所以NADN Flash上的yaffs文件系统本身没有存储filesystem的魔数(MagicNum),在内存中SupperBlock里的s_magic参数也是直接赋值的,所以存储在NAND Flash上的任何文件系统都能被当作yaffs文件系统mount上来,只是数据都会被当作错误数据放在lost+found目录中,不知道这算不算yaffs文件系统的一个bug.

超级块中保存了全局文件,如硬盘已用空间、数据块可用空间、inode结点的个数和部分可以及时使用的inode结点列表、文件系统名称(比如 ext2)、文件系统的大小和状态、块设备的引用和元数据信息(比如空闲列表等等)、最近一次的更新时间与文件系统的状态等。做一个形象的比喻,这个超级块就似乎是企业的资产负债表,一个文件系统中有哪些资源都记录在这个表中。

Inode结点定义:

Linux系统中的每个文件都被赋予一个唯一的数值,这个数值称做索引节点。索引节点存储在一个称作索引节点表< inode table>中,该表在磁盘格式化时被分配。每个实际的磁盘或分区都有其自己的索引节点表。一个索引节点包含文件的所有信息,包括磁盘上数据的地址和文件类型。文件类型包括如普通文件、目录和特殊文件这样的信息。

 

魔数通常位于一个对象中,并用来判断对象(如文件)的类型。例如对于a.out格式的执行文件来说,其开始两个字节就是一个魔数,具有特定的值。对于一个文件系统来说,其超级块开始的字节就用来指定文件系统的类型,也是一个魔数。

通常一个具体的文件系统在VFS的supper_block结构中除了通用的数据外,还有自己专用的数据,yaffs文件系统的专用数据是一个yaffs_DeviceStruct结构,主要用来存储一些相关软硬件配置信息,相关函数指针和统计信息等。

 

VFS超级块与YAFFS等具体文件系统超级块的关系:

引用《Linux系统中虚拟文件系统内核机制研究》的定义: VFS超级块是在文件系统安装时由系统在内存中建立的,对于每个已安装的文件系统,在内存中都有与其对应的VFS超级块。VFS超级块的作用是把在各种文件系统中表示整体组织结构的信息转换成统一的格式。各种文件系统的VFS超级块都是一个super-block结构体,VFS超级块super-block结构体的定义在/include/fs/fs.h中。各文件系统VFS超级块例程read-super()把某种文件系统的管理信息写入它自己的VFS超级块中。VFS超级块主要包含的信息有:文件系统的组织信息、文件系统的注册和安装信息、超级块的属性信息,不同物理文件系统特有的信息则由联合体U的各个成员项表示,指向super-operations结构体中包含着对超级块进行操作的函数指针等。

 

SupperBlock填充及MagicNum赋值的源代码调用流程

Kernel/fs/namespace.c/

SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,

char __user *, type,unsigned long, flags, void __user *, data)

->do_mount->do_new_mount->do_kern_mount根据”yaffs2”字符串获取file_system_type结构指针type->vfs_kern_mountàmount_fs:type->mount(type, flags, name, data);此时跟踪yaffs注册时的代码。Kernel/fs/yaffs2/yaffs_vfs.c/init_yaffs_fsàregister_filesystem(fsinst->fst),而fsinst =fs_to_install;

static struct file_system_to_install fs_to_install[] = {

{&yaffs_fs_type, 0},

{&yaffs2_fs_type, 0},

{NULL, 0}

};

static struct file_system_type yaffs2_fs_type = {

.owner = THIS_MODULE,

.name ="yaffs2",

.mount = yaffs2_mount,

.kill_sb =kill_block_super,

.fs_flags =FS_REQUIRES_DEV,

};

此时再跟踪yaffs2_mountàkernel/fs/super.c/mount_bdev->get(fs_type,test_bdev_super, set_bdev_super, bdev);查找或创建超级块。

mount_bdev -> fill_super-> yaffs2_internal_read_super_mtd ->yaffs_internal_read_super->

sb->s_magic = YAFFS_MAGIC;

 

2.3.2 文件在内存中的组织方式

在mount过程执行read_super的过程中,yaffs文件系统还需要将文件系统的目录结构在内存中建立起来。由于没有super块,所以需要扫描yaffs分区,根据从OOB中读取出的yaffs_tags信息判断出是文件头page还是数据page,再根据文件头page中的内容以及数据page中的ObjectID/ChunkID/serialNumber等信息在内存中为每个文件(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, lostNfoundDir分别用作根目录,unlinked对象挂接的目录,delete对象挂接的目录,无效或零时数据块挂接的目录。通过创建这些yaffs_object, yaffs文件系统就能够将存储在NAND FLASH上数据系统的组织起来,在内存中维护一个完整的文件系统结构。

 

3. 与其他层的交互

3.1 关于文件系统的三个易混淆的概念:

创建 以某种方式格式化磁盘的过程就是在其之上建立一个文件系统的过程。创建文现系统时,会在磁盘的特定位置写入关于该文件系统的控制信息。

注册 向内核报到,声明自己能被内核支持。一般在编译内核的时侯注册;也可以加载模块的方式手动注册。注册过程实际上是将表示各实际文件系统的数据结构struct file_system_type 实例化。

安装 也就是我们熟悉的mount操作,将文件系统加入到Linux的根文件系统的目录树结构上;这样文件系统才能被访问。

 

3.2 MTD简介

MTD是Memory Technology Device的缩写,它是底层硬件和上层软件之间的桥梁。对底层来说,它无论对NOR型或是NAND型都有很好的驱动支持;对上层来说,它抽象出文件系统所需要的接口函数。

yaffs2文件系统结构及应用(转载+总结)

一个MTD设备按照用户访问顺序可以得到下图所示的层次结构。图中NAND特定硬件驱动层为具体NAND设备的驱动,实现特定硬件的具体操作;NAND通用驱动层是所有NAND设备的公用部分,实现了NAND设备发现,通用NAND读写等操作; MTD原始设备层是MTD原始设备的通用代码,此外还包括各个特定的闪存设备所注册的数据,例如NAND分区等。MTD向上提供块设备和字符设备两种接口。文件系统通过MTD块设备接口访问NAND闪存驱动。

yaffs2文件系统结构及应用(转载+总结)

 

A、Flash硬件驱动层:硬件驱动层负责驱动Flash硬件。

B、MTD原始设备:原始设备层有两部分组成,一部分是MTD原始设备的通用代码,另一部分是各个特定的Flash的数据,例如分区。用于描述MTD原始设备的数据结构是mtd_info,这其中定义了大量的关于MTD的数据和操作函数。mtd_table(mtdcore.c)则是所有MTD原始设备的列表,mtd_part(mtdpart.c)是用于表示MTD原始设备分区的结构,其中包含了mtd_info,因为每一个分区都是被看成一个MTD原始设备加在mtd_table中的,mtd_part.mtd_info中的大部分数据都从该分区的主分区mtd_part->master中获得。在drivers/mtd/maps/子目录下存放的是特定的flash的数据,每一个文件都描述了一块板子上的flash。其中调用add_mtd_device()、del_mtd_device()建立/删除mtd_info结构并将其加入/删除mtd_table(或者调用add_mtd_partition()、del_mtd_partition()(mtdpart.c)建立/删除mtd_part结构并将mtd_part.mtd_info加入/删除mtd_table 中)。

C、MTD设备层:基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90)。MTD字符设备的定义在mtdchar.c中实现,通过注册一系列fileoperation函数(lseek、open、close、read、write)。MTD块设备则是定义了一个描述MTD块设备的结构mtdblk_dev,并声明了一个名为mtdblks的指针数组,这数组中的每一个mtdblk_dev和mtd_table中的每一个mtd_info一一对应。

D、设备节点:通过mknod在/dev子目录下建立MTD字符设备节点(主设备号为90)和MTD块设备节点(主设备号为31),通过访问此设备节点即可访问MTD字符设备和块设备。

E、根文件系统:在Bootloader中将JFFS(或JFFS2)的文件系统映像jffs.image(或jffs2.img)烧到flash的某一个分区中。

F、文件系统:内核启动后,通过mount命令可以将flash中的其余分区作为文件系统挂载到mountpoint上。

一个MTD原始设备可以通过mtd_part分割成数个MTD原始设备注册进mtd_table,mtd_table中的每个MTD原始设备都可以被注册成一个MTD设备。

具体的NAND闪存驱动是和NAND通用驱动相关联的,要实现一个NAND闪存硬件驱动,需要实现以下部分:初始化函数,硬件相关的设备就绪函数和控制函数。为了灵活起见,还可以实现硬件相关的命令函数,硬件相关的等待函数和硬件ECC函数。

编程上,MTD为上层提供了一系列的接口,可以使上层程序不关心底层的硬件细节,而通过这些函数直接访问。这些接口包括:erase, read, write,read_ecc, write_ecc, read_oob, write_oob, sync, lock, unlock, suspend, resume,block_isbad, block_mardbad等函数。通过这些抽象出的接口,文件系统就可以方便的对Flash进行各种操作了。

 

平台设备数据在kernel/arch/arm/mach-msm/Devices-9615.c

#define DMOV_NAND_CHAN 3// Dma.h文件中

#define MSM_NAND_PHYS 0x1B400000

static struct resourceresources_nand[] = {

[0] = {

.name = "msm_nand_dmac",

.start = DMOV_NAND_CHAN,

.end = DMOV_NAND_CHAN,

.flags = IORESOURCE_DMA,

},

[1] = {

.name = "msm_nand_phys",

.start = MSM_NAND_PHYS,

.end = MSM_NAND_PHYS + 0x7FF,

.flags = IORESOURCE_MEM,

},

};

 

struct flash_platform_datamsm_nand_data = {

.parts = NULL,

.nr_parts = 0,

};

 

struct platform_device msm_device_nand= {

.name = "msm_nand",

.id = -1,

.num_resources = ARRAY_SIZE(resources_nand),

.resource = resources_nand,

.dev = {

.platform_data = &msm_nand_data,

},

};

 

Kernel/arch/arm/mach-msm/nand_partitions.c文件中get_nand_partitions函数:

msm_nand_data.nr_parts= 1;

msm_nand_data.parts = msm_nand_partitions;

而#define MSM_MAX_PARTITIONS 18

static structmtd_partition msm_nand_partitions[MSM_MAX_PARTITIONS];而msm_nand_partiti- ons数组中数据的填充:

__tagtable(ATAG_MSM_PARTITION, parse_tag_msm_partition); ATAG_MSM_PARTITION(#define ATAG_MSM_PARTITION 0x4d534D70 /* MSMp */该类型的tag结构体空间里的数据由parse_tag_msm_partition函数来解析)。tag结构体空间里数据由bootloader写入。

解析时机:kernel/init/main.c/start_kernelà/kernel/arch/arm/kernel/setup.c/setup_arch(&command_ line ); àsetup_machine_tagsàparse_tags(tags)分析。

 

/kernel/drivers/mtd/devices/msm_nand.c

/kernel/drivers/mtd/mtdcore.c

msm_nand.c/msm_nand_initàmsm_nand_probe填充msm_nand_info *info结构;调用msm_nand_scan(&info->mtd, 1)填充mtd的操作函数(read,write等),数据结构,ECC检测模式等;调用setup_mtd_deviceàmtdcore.c/mtd_device_registerà add_mtd_ device注册MTD设备。

Yaffs操作函数的注册:

Kernel/fs/yaffs2/yaffs_vfs.c

init_yaffs_fsàcreate_proc_entry : Install the proc_fsentries;调用 register_filesystem(fsinst->fst)注册文件系统:

fsinst = fs_to_install;

static struct file_system_to_install fs_to_install[]= {

{&yaffs_fs_type, 0},

{&yaffs2_fs_type, 0},

{NULL, 0}

};

static struct file_system_typeyaffs2_fs_type = {

.owner = THIS_MODULE,

.name = "yaffs2",

.mount = yaffs2_mount,

.kill_sb = kill_block_super,

.fs_flags = FS_REQUIRES_DEV,

};

yaffs2_mountàmount_bdevàyaffs2_internal_read_super_mtdàyaffs_internal_read_super注册functions:

param->write_chunk_tags_fn =nandmtd2_write_chunk_tags;

param->read_chunk_tags_fn =nandmtd2_read_chunk_tags;

.........................

inode->i_op =&yaffs_dir_inode_operations;

inode->i_fop= &yaffs_dir_operations;

.........................

读写操作流程:

Yaffs2_mtdif2.c /nandmtd2_write_chunk_tagsàmsm_nand.c /msm_nand_writeàmsm_nand_write_ oob 将IO请求压入请求队列,由mtdblock.c

static struct mtd_blktrans_opsmtdblock_tr = {

.name = "mtdblock",

.major = 31,

.part_bits = 0,

.blksize = 512,

.open = mtdblock_open,

.flush = mtdblock_flush,

.release = mtdblock_release,

.readsect = mtdblock_readsect,

.writesect = mtdblock_writesect,

.add_mtd = mtdblock_add_mtd,

.remove_dev = mtdblock_remove_dev,

.owner = THIS_MODULE,

};

init_mtdblockàmtd_blkdevs.c/register_mtd_blktransàtr->add_mtd(tr, mtd);<=> mtdblock_add_ mtdàadd_mtd_blktrans_dev:

new->thread =kthread_run(mtd_blktrans_thread, new,"%s%d", tr->name, new-> mtd->index);创建mtd_blktrans_thread线程来处理IO请求:àmtdblock_writesectàdo_cached_write

 

Yaffs2_mtdif2.c /nandmtd2_read_chunk_tagsà msm_nand.c /msm_nand_readàmsm_nand_read_ oob 后续操作与write类似。

 

4.Yaffs2文件系统集成及应用相关

4.1 注册Yaffs2文件系统

⑴在内核中建立YAFFS目录fs/yaffs2,并把下载的YAFFS2代码复制到该目录下面。

⑵参考yaffs代码中的Kconfig文件,修改你自己的Config.in文件,使得可以配置YAFFS2。

⑶修改fs/makefile,加入yaffs目录,即obj-$(CONFIG_YAFFS_FS) += yaffs2/

在配置YAFFS的时候需要注意一点,即使你的NAND FLASH是512+16B的,不需要使用YAFFS2,也需要将对2k page的NAND FLASH的支持这一项选上,否则编译无法通过(因为部分代码没有用CONFIG宏包起来),不知道这是不是这个版本的个别现象,还是对Makefile还需要进一步的修改。

此外就是最好把Lets Yaffs do itsown ECC选上,理由后面会说,其他选项就无所谓了,主要是对性能的调整,看着选吧,按推荐配置好了,比如Turn off debug chunk erasecheck,这一项,平均可以提高20-30%左右的擦写速度。

kernel\fs\yaffs2\yaffs_vfs.c\init_yaffs_fs调用register_filesystem完成对文件系统的注册

module_init(init_yaffs_fs)

 

4.2 通过mkyaffs2image制作yaffs文件系统

根据apps_proc\external\yaffs2\Android.mk文件生成mkyaffs2image可执行程序,用来制作yaffs2文件系统的镜像.

需要注意的是,制作出来的yaffs image文件与通常的文件系统的image文件不同,因为在image文件里除了以512字节为单位的一个page的data数据外,同时紧跟在后还包括了16字节为单位的NandFlash备用区OOB的数据。所以实际上是以528个字节为单位的。就是因为包含了这额外的16字节/page的数据,所以通常的下载其它类型image的工具就无法正常下载yaffs image了,需要修改你所使用的下载工具的代码,使得它能将yaffs image中的这些额外数据也写入NandFlash OOB中。

这里还有一点需要注意的是,通过mkyaffsimage制做出来的image其OOB中也包含它自己计算的ECC校验数据,其校验算法有可能和MTD驱动的校验算法不同,如果在内核中由MTD来处理ECC,会造成MTD认为所有的page都校验错误。要解决这个问题通常有两种方法:

第一、重新配置内核,进入文件系统模块的选择界面,选中"Lets Yaffs doits own ECC",同时将MTD中的ECC校验关闭。但是这种不校验数据ECC的做法,在系统使用过程中产生坏块位时,就会显示出系统的健壮和容错性很差。

第二、通过修改bootloader(比如uboot),在下载文件系统时,通过硬件计算并读出数据ECC,并修改image中的ECC后,将OOB数据写入到芯片中。

 

在kernel目录下运行make menuconfig命令,File systems--->Miscellaneous filesystems(NEW)--- >

yaffs2文件系统结构及应用(转载+总结)

 

apps_proc\external\yaffs2 \Android.mk文件中最后一条语句

$(call dist-for-goals,droid,$(LOCAL_BUILT_MODULE))//调用dist-for-goals函数,该函数的作用是将$(LOCAL_BUILT_MODULE)即mkyaffs2image拷贝到droid环境变量所定义的目录中去(如下所示):

/build/tmp/work/i686-linux/yaffs2-utils-native-git-r1/yaffs2/yaffs2/mkyaffs2image

/build/tmp/work/i686-linux/yaffs2-utils-native-git-r1/home/driver/SRC/X715_0223/apps_proc/build/tmp/sysroots/i686-linux/bin/ mkyaffs2image

/build/tmp/work/i686-linux/yaffs2-utils-native-git-r1/sysroot-destdir/home/driver/SRC/X715_0223/apps_proc/build/tmp/sysroots/i686-linux/bin/ mkyaffs2image

/build/tmp/work/i686-linux/yaffs2-utils-native-git-r1/staging-pkg/sysroots/i686-linux/bin/ mkyaffs2image

/build/tmp/sysroots/i686-linux/bin/ mkyaffs2image

调用mkyaffs2image时会自动到相应的目录中去找。

 

4.2.19615-cdp-usr-image..usrfs.yaffs2镜像文件的生成

build/qcom-recipes/recipes/images/9615-cdp-usr-image.inc

do_make_usr () {

rm -rf ${USR_IMAGE_ROOTFS}

mkdir-p ${USR_IMAGE_ROOTFS}

mv ${IMAGE_ROOTFS}/usr/* ${USR_IMAGE_ROOTFS}

OUTPUT_FILE=${DEPLOY_DIR_IMAGE}/${USR_IMAGE_BASENAME}.usrfs.yaffs2

mkyaffs2image ${USR_IMAGE_ROOTFS}${OUTPUT_FILE}

chmod644 ${OUTPUT_FILE}

}

 

4.2.2 micro-msm-9615-cdp-image-eglibc-ipk-20120301-9615-cdp.rootfs.yaffs2镜像文件的生成

build/openembedded/conf/bitbake.conf:383:IMAGE_CMD_yaffs2= "mkyaffs2image

${EXTRA_IMAGECMD}${IMAGE_ROOTFS} ${DEPLOY_DIR_IMAGE}/${IMAGE_NAME}.rootfs.yaffs2"

 

5.根文件系统相关

5.1简介

boot loader装入kernel, 然后kernel需要执行/sbin/init, 读取这个文件就必须先mount根文件系统, 早期是通过启动时的root=参数告诉内核根文件系统在哪个设备上, 随着硬件和技术的发展,现在根文件系统可能位于一个网络存储如NFS上, 可能由于RAID而散布于多个设备上, 可能位于一个加密设备上需要提供用户名和密码,这时root=参数就显得不够了. 为了应付这种局面, 先后出现两种机制来作为boot loader装载kernel到真正的/sbin/init执行这个启动过程的桥梁: initrd和initramfs, 两者有类似的地方, 比如都是由内核执行其上的某个程序(initrd是/linuxrc, initramfs是/init),由这个程序决定加载什么驱动以及如何装载根文件系统.(可参考kernel/init/do_mounts_initrd.c/handle_initrd: pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD))

 

5.2ramdisk介绍

本系统使用ramdisk。

要想使用RamDisk你必须或是得到内核的支持或是以模块的形式将他加载到系统中。其中内核的配置选项是 CONFIG_BLK_DEV_RAM. ramdisk就是指使用一部分内存空间来模拟硬盘分区,也就是说ramdisk是一个块设备,要用mkfs格式化,才能真正使用它.

.ramdisk在内核2.0/2.2版本就已经支持.

.ramdisk设备是它不允许重新声明它的内存空间,所以ramdisk块通常会一直占用空间的内存直到系统重启.

.ramdisk的一个缺点是在它上面创建一个文件系统,它同时会消耗缓冲区高速缓存和文件系统缓存,理论上,它可以消耗一个磁盘文件的两倍随机内存.

.ramdisk的另一个缺点是它大小固定,之后不能改变

.现在大多数需要RAM临时存储的应用程序会使用tmpfs文件系统而不是ramdisk设备.

 

5.3 initrd介绍

本系统支持initrd,内核的配置选项是CONFIG_BLK_DEV_INITRD

 

initrd 的英文含义是 boot loaderinitialized RAM disk,就是由 boot loader 初始化的内存盘。在 linu2.6内核启动前, boot loader 会将存储介质中的initrd 文件加载到内存,内核启动时会在访问真正的根文件系统前先访问该内存中的 initrd 文件系统。在 boot loader 配置了 initrd 的情况下,内核启动被分成了两个阶段,第一阶段先执行 initrd 文件系统中的init,完成加载驱动模块等任务,第二阶段才会执行真正的根文件系统中的 /sbin/init 进程。

Initrd的具体实现程是:bootloader把根文件系统映象装载到内存指定位置,把相关参数传递给内核,内核启动时把initrd中的内容复制到ramdisk中(ram0),把initrd占用的内存释放掉,在ram0上mount根文件系统。

 

内核对initrd 的处理流程:

boot loader 把内核以及 initrd 文件(/dev/initrd)加载到内存的特定位置。/dev/initrd是由boot loader初始化的设备,存储着initrd。

内核判断initrd的文件格式,如果是cpio格式。将initrd的内容释放到rootfs中。执行initrd中的/init文件,执行到这一点,内核的工作全部结束,完全交给/init文件处理。

如果是initrd 是以 ramdisk 形式存在的,需要额外挂载到 rootfs 上才能使用。具体流程(参考/kernel/init/do_mounts_initrd.c文件中的handle_initrd函数):

①内核把/dev/initrd设备的内容解压缩并拷贝到/dev/ram0设备上。

②内核以可读写的方式把/dev/ram0设备挂载为原始的根文件系统。

③如果/dev/ram0被指定为真正的根文件系统,那么内核跳至最后一步正常启动。

④执行initrd上的/linuxrc,该线程负载加载内核访问根文件系统必须的驱动,以及加载根文件系统。

⑤/linuxrc执行完毕,真正的根文件系统被挂载。

⑥如果真正的根文件系统存在/initrd目录,那么/dev/ram0将从/移动到/initrd.否则如果/initrd目录不存在,/dev/ram0将被卸载。

⑦在真正的根文件系统上进行正常启动过程,执行/sbin/init

 

5.4 根文件系统加载过程

(1) 注册rootfs的文件系统

kernel/init/main.c/start_kernel----->vfs_caches_init---->kernel/fs/dcache.c/mnt_init---->kernel/fs/namespace.c/mnt_init---->init_rootfs

 

(2)加载根文件系统

A. cpio包根文件系统挂载

static int __init kernel_init(void * unused)

{

... ...

do_basic_setup();

if(!ramdisk_execute_command)

ramdisk_execute_command = "/init";

if (sys_access((const char __user *)ramdisk_execute_command, 0) != 0)

{

ramdisk_execute_command = NULL;

prepare_namespace();

}

init_post();

return 0;

}

在 kernel_init() 中会调用 do_basic_setup(),而 do_basic_setup() 又会调用 do_initcalls(),所以 cpio 包类型的 initrd (如果有的话)就会在此时被填充到 rootfs 中去。接下来初始化 ramdisk_execute_command 变量,这个变量表示在 cpio 包中要被执行的第一个程序,可通过在内核启动参数中给 rdinit= 赋值来指定。接下来检查在 rootfs 中是否存在变量 ramdisk_execute_command 所指的文件。如果有,就说明 cpio 包类型的 initrd 成功加载了,那就不需要内核再调用 prepare_namespace() 来挂载根文件系统了,这些都留给 cpio 包里的 ramdisk_execute_command 所指的程序去完成了;如果没有,就说明内核并没有成功用上 cpio 包类型的 initrd,还需要调用 prepare_namespace() 来继续准备加载根文件系统,并清空变量ramdisk_execute_command。无论怎样,内核都会继续执行 init_post()。

填充rootfs时的代码流程:

rootfs_initcall调用 initramfs.c 中的 populate_rootfs() 函数来填充 rootfs。如果在 populate_rootfs() 中成功地 unpack_to_rootfs() 的话,之后内核就不会再对 initrd 作任何操作,也不会去挂载根文件系统,所有的工作都留给 cpio 包(也就是rootfs)中的 /init 去完成了。

 

B. ramdisk类型根文件系统挂载

kernel_init->prepare_namespace->initrd_load()

int __init initrd_load(void)

{

if (mount_initrd) {

create_dev("/dev/ram",Root_RAM0);

/*

* Load the initrd data into /dev/ram0. Executeit as initrd

* unless /dev/ram0 is supposed to be ouractual root device,

* in that case the ram disk is just set uphere, and gets

* mounted in the normal path.

*/

if(rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {

sys_unlink("/initrd.image");

handle_initrd();

return 1;

}

}

sys_unlink("/initrd.image");

return 0;

}

变量 mount_initrd 是是否要加载 initrd 的标志,默认为1,当内核启动参数中包含 noinitrd 字符串时,mount_initrd 会被设为0 。接着为了把 initrd 释放到内存盘中,先需要创建设备文件,然后通过 rd_load_image 把之前保存的 /initrd.image 加载到内存盘中。之后判断如果内核启动参数中指定的最终的根文件系统不是内存盘的话,那就先要执行 initrd 中的 linuxrc;如果最终的根文件系统就是刚加载到内存盘的 initrd 的话,那就先不处理它,留到之后当真正的根文件系统处理。

需要补充的是,只要没有用到 cpio 类型的 initrd,那内核都会执行到rd_load_image("/initrd.image"),无论是否真的提供了 initrd 。如果没有提供 initrd,那 /initrd.image 自然不会存在,rd_load_image() 也会提早结束。另外 /dev/ram 这个设备节点文件在 rd_load_image() 中用完之后总会被删除(但相应的内存盘中的内容还在)。

 

handle_initrd()函数中,用到了一个real_root_dev 变量,它是一个 int 型的全局变量,它的值从变量 ROOT_DEV 转换过来。变量 real_root_dev 是和文件 /proc/sys/kernel/real-root-dev 相关联的(参见 kernel/sysctl.c 第405行左右),所以如果在执行 initrd 中的 /linuxrc 时改了 /proc/sys/kernel/real-root-dev 的话,就相当于又重新指定了真正的根文件系统。之所以要新弄一个 real_root_dev 变量,使因为 procfs 不支持改 kdev_t 型的 ROOT_DEV 变量。

另外,在 rootfs 中会建有两个设备文件节点:/dev/root 是真正的根文件系统的设备节点,其设备号是 ROOT_DEV 的值;/dev/root.old 是 ramdisk 型的 initrd 的设备节点,其设备号总是 Root_RAM0 。handle_initrd->mount_root->create_dev("/dev/root",ROOT_DEV);

 

走完上述流程回退到prepare_namespace() 函数时就会直接跳到 out: 标签处,剩下还有两行代码要执行:

out:

sys_mount(".", "/", NULL,MS_MOVE, NULL);

sys_chroot(".");

}

就是把当前目录替换 rootfs,使其成为 Linux VFS 的根目录。

 

rootfs是用来mount根/的文件系统 ,它可以是任何支持的文件系统,比如ext2,romfs等

rootfs文件系统是基于内存的文件系统,也是虚拟的文件系统,在系统启动之后,隐藏在真正的根文件系统后面,不能被卸载。

 

Initrd可以说是启动过程中用到的一种机制。就是在装载linux之前,bootloader可以把一个比较小的根文件系统的映象装载在内存的某个指定位置,姑且把这段内存称为initrd,然后通过传递参数的方式告诉内核initrd的起始地址和大小(也可以把这些参数编译在内核中),在启动阶段就可以暂时的用initrd来mount根文件系统。Initrd的最初的目的是为了把kernel的启动分成两个阶段:在kernel中保留最少最基本的启动代码,然后把对各种各样硬件设备的支持以模块的方式放在initrd中,这样就在启动过程中可以从initrd所mount的根文件系统中装载需要的不同的硬件。在启动完成的最后阶段,根文件系统可以重新mount到其他设备上,但是也可以不再重新mount(很多嵌入式系统就是这样)。Initrd的具体实现过程是这样的:bootloader把根文件系统映象装载到内存指定位置,把相关参数传递给内核,内核启动时把initrd中的内容复制到ramdisk中(ram0),把Initrd占用的内存释放掉,在ram0上mount的根文件系统不再切换,因为这个时候实际的设备就是ram0。还有就是Initrd的起始地址参数为虚拟地址,需要和bootloader中用的物理地址对应。


==yaffs2 checkpoint==============================================

checkpoint 存储了

yaffs_CheckpointValidity

yaffs_CheckpointDevice

yaffs_CheckpointObject