Yaffs的读写
?在看过
dreamice
的《
yaffs
文件系统分析》
Yaffs文件系统最终是通过VFS层接口被调用的,所以yaffs必须给VFS层提供相应的接口。VFS提供的标准接口结构是inode和dentry结构,也就是说不管最终yaffs是如何实现的,提供给VFS层的必须是inode和dentry结构。 在yaffs中,函数yaffs_fill_inode_from_obj用于根据yaffs的结构体yaffs_obj来填充inode结构体,以便于VFS层使用。 static?void?yaffs_fill_inode_from_obj(struct?inode?*inode, ??????struct?yaffs_obj?*obj) { .... switch?(obj->yst_mode?&?S_IFMT)?{ default: /*?fifo,?device?or?socket?*/ init_special_inode(inode,?obj->yst_mode, ???old_decode_dev(obj->yst_rdev)); break; case?S_IFREG: /*?file?*/ inode->i_op?=?&yaffs_file_inode_operations; inode->i_fop?=?&yaffs_file_operations; inode->i_mapping->a_ops?=?&yaffs_file_address_operations; break; case?S_IFDIR: /*?directory?*/ inode->i_op?=?&yaffs_dir_inode_operations; inode->i_fop?=?&yaffs_dir_operations; break; case?S_IFLNK: /*?symlink?*/ inode->i_op?=?&yaffs_symlink_inode_operations; break; } .... } 其中obj->yst_mode?用于表示yaffs_obj指代的object是具体文件、目录、symlink,以及hardlink等等。对于不同类型的object,显然处理的方法是不一样。 首先需要关注的是关于VFS的缓冲页的操作结构体: static?struct?address_space_operations?yaffs_file_address_operations?=?{ .readpage?=?yaffs_readpage, .writepage?=?yaffs_writepage, #if?(YAFFS_USE_WRITE_BEGIN_END?>?0) .write_begin?=?yaffs_write_begin, .write_end?=?yaffs_write_end, #else .prepare_write?=?yaffs_prepare_write, .commit_write?=?yaffs_commit_write, #endif}; 因为内核版本的更新,原版本的prepare_write和commit_write函数被write_begin和write_end函数所代替,这儿为了保持yaffs的可移植性,采用了一个条件编译。 其中write_begin函数主要调用grab_cache_page_write_begin在radix树里面查找要被写的page,如果不存在则创建一个。如果被写入的设备是一个块设备的话,调用__block_prepare_write为这个page准备一组buffer_head结构,用于描述组成这个page的数据块?。 write_end主要用于将被写入的page标记为脏,后台进程pdflush会寻找这些脏页,并将数据写入设备中去。如果被写入的设备是块设备的话,还需要将相应的buffer-head标记为脏。 static?int?yaffs_write_begin(struct?file?*filp,?struct?address_space?*mapping, ?????loff_t?pos,?unsigned?len,?unsigned?flags, ?????struct?page?**pagep,?void?**fsdata) { struct?page?*pg?=?NULL; pgoff_t?index?=?pos?>>?PAGE_CACHE_SHIFT; int?ret?=?0; int?space_held?=?0; /*?Get?a?page?*/ pg?=?grab_cache_page_write_begin(mapping,?index,?flags); 首先根据文件内部偏移量算出所处的page位置index,然后根据index从文件的struct?address_space中获得page。当然如果不存在该page的话,内核也会为它分配一个page用于该段的缓存。 space_held?=?yaffs_hold_space(filp); 接着调用yaffs_hold_space(filp);来判断flash内部是否存在足够的空间用于写操作。这就是不同类型的文件系统相差别的地方。可能存在下面的几种情况: (1)往文件中添加数据,但是设备中已经没有空间存放多余的数据了。这种情况不仅仅在flash设备上存在,在块设备,磁盘也是存在的,是一种比较普遍的现象。 (2)NANDFLASH比较特殊,在每一次写入之前必须擦除。这是由于NANDFLASH的特性决定的。文件系统采用block-mapping的机制来进行回避,即将更新的数据写入一个新页中,然后将保存旧数据的旧页标记为脏,便于后面垃圾回收。那么就是说在不增加任何数据量的修改过程中,也需要一个空闲页(这儿的页指flash的页)来进行数据更新。会不会因为没有空闲页而导致更新失败呢? 这个计算空闲空间的过程稍显复杂。除了考虑flash中的空闲页之外,还需要考虑flash中的脏页(不能因为是脏页就不算了,脏页可以通过garbage?collection回收利用的)。除了上面两点之外还需要考虑那些保存在缓冲区中尚未写入flash中的数据,虽然没有写进去,但是它也算是预先占有了空间。 n_free?=?dev->n_free_chunks; n_free?+=?dev->n_deleted_files; /*?Now?count?and?subtract?the?number?of?dirty?chunks?in?the?cache.?*/ for?(n_dirty_caches?=?0,?i?=?0;?i?<?dev->param.n_caches;?i++)?{ if?(dev->cache[i].dirty) n_dirty_caches++; n_free?-=?n_dirty_caches; n_free_chunks记录着设备中空闲的chunk数目,n_deleted_files记录着设备中等待被删除的文件。然后通过一个for循环来便来yaffs文件系统的缓冲区,看是否有缓冲区是脏的。(即仍未写入设备中)。 blocks_for_checkpt?=?yaffs_calc_checkpt_blocks_required(dev); n_free?-=?(blocks_for_checkpt?*?dev->param.chunks_per_block); 关于yaffs中的check?pionter暂时还没弄清楚,呵呵?=。= if?(!PageUptodate(pg)) ret?=?yaffs_readpage_nolock(filp,?pg); 在写入数据之前(这儿的写入数据是指更新缓冲区中的数据),需要检查该页中的数据是否是最新,如果不是,需要将缓冲区中的数据从flash中更新。 虽然yaffs在设计上与VFS提供的接口完美的配合一起,但是yaffs的实现却取完全背离了VFS提供的缓冲页得原始初衷。 如果你在往yaffs的文件系统中拷贝数据的时候,通过top来观察pdflush线程组的资源使用情况,就会发现在往yaffs文件系统中拷贝数据的时候pdflush根本没有动静。那是为什么呢?下面继续细细的研究一下yaffs的源码。 其实根本的原因在yaffs_write_end调用的函数?yaffs_file_write上。 static?ssize_t?yaffs_file_write(struct?file?*f,?const?char?*buf,?size_t?n, loff_t?*?pos) 该函数直接将保存在缓冲页中的数据写进了NANDFLASH中。在这儿,缓冲页根本没有起到缓冲的效果,反而通过缓冲页的过渡降低了写入的数据。但是没办法,这就是软件带来的消耗。 虽然yaffs没有使用内核提供的缓冲页机制,但是它也是带缓冲的,只不过这种缓冲是在文件系统内部实现的。在文档中模仿情景分析,在介绍代码的过程中介绍各个数据结构、变量等等的含义。 /*?Find?a?cached?chunk?*/ static?struct?yaffs_cache?*yaffs_find_chunk_cache(const?struct?yaffs_obj?*obj, ??int?chunk_id) { struct?yaffs_dev?*dev?=?obj->my_dev; int?i; if?(dev->param.n_caches?<?1) return?NULL; for?(i?=?0;?i?<?dev->param.n_caches;?i++)?{ if?(dev->cache[i].object?==?obj?&& ????dev->cache[i].chunk_id?==?chunk_id)?{ dev->cache_hits++; return?&dev->cache[i]; } } return?NULL; } 这个函数的代码比较简练,在yaffs设备的一个cache数组中,遍历的查找是否存在一个cache满足yaffs_obj和chunk_id方面的要求。这个函数返回值的类型为struct?yaffs_cache?*,struct?yaffs_cache?结构体定义在yaffs_guts.h中。Object用于表示该cache中缓存的数据属于哪个文件,因为yaffs_cache和yaffs_obj都是属于ram中数据,不存在于flash中,所以yaffs_cache的data为void*的指针。chunk_id用于表示缓存的数据是文件中第几个chunk。dirty用于表示该缓存区的数据是否为脏。函数yaffs_guts_initialise中对设备的cache进行了初始化。事实上,在yaffs不是每一次写操作都会使用yaffs_cache的。 if?(n_copy?!=?dev->data_bytes_per_chunk?|| ????dev->param.inband_tags)?{ if?(dev->param.n_caches?>?0)?{ struct?yaffs_cache?*cache; cache?=?yaffs_find_chunk_cache(in,?chunk); 通过源码可以看出,只有在写入数据n_copy不等于data_bytes_per_chunk的时候才使用yaffs_cache。因为在写入不整数据的是否,它写入的不是一个完整的chunk。不如一个chunk的大小为512bytes,从第100bytes开始写入412bytes字节的数据。其中前100字节的数据不能被破坏。根据涉及思路就应该集合原来的这100字节,以及将要写入的412字节来完整的写入一个chunk。既然需要整合,那么就必然需要一个缓存区来暂时的存放这些需要整合的数据。事实上,yaffs_cache的目的正是如此。 if?(!cache?&& ????yaffs_check_alloc_available(dev,?1))?{ cache?=?yaffs_grab_chunk_cache(dev); cache->object?=?in; cache->chunk_id?=?chunk; cache->dirty?=?0; cache->locked?=?0; yaffs_rd_data_obj(in,?chunk, ??cache->data); } 如果在设备的yaffs_cache中没有找到命中的cache,那么就需要分配一个空闲的yaffs_cache,并将相应的数据从flash中读入到cache中。 如果yaffs_cache中没有空闲的怎么办??事实上,yaffs_cache毕竟是有限的,也是少数的(因为从设计上来说让一个模块在初始化的时候占有那么多内存资源)。 u8?*local_buffer?=?yaffs_get_temp_buffer(dev); 其实yaffs除了dev->param.n_caches个yaffs_cache缓存之外,还有YAFFS_N_TEMP_BUFFERS个另外的临时缓存。 u8?*yaffs_get_temp_buffer(struct?yaffs_dev?*?dev) { int?i; dev->temp_in_use++; if?(dev->temp_in_use?>?dev->max_temp) dev->max_temp?=?dev->temp_in_use; for?(i?=?0;?i?<?YAFFS_N_TEMP_BUFFERS;?i++)?{ if?(dev->temp_buffer[i].in_use?==?0)?{ dev->temp_buffer[i].in_use?=?1; return?dev->temp_buffer[i].buffer; } } yaffs_trace(YAFFS_TRACE_BUFFERS,?"Out?of?temp?buffers"); dev->unmanaged_buffer_allocs++; return?kmalloc(dev->data_bytes_per_chunk,?GFP_NOFS); } dev->temp_buffer数组是有函数yaffs_init_tmp_buffers进行初始化的。yaffs_get_temp_buffer函数遍历的查询dev->temp_buffer数组看是否有空闲的buffer,如果有,那么用于暂时的缓存数据。如果没有,就调用kmalloc来分配空间。 yaffs_rd_data_obj(in,?local_buffer); memcpy(&local_buffer[start],?buffer,?n_copy); chunk_written?= ????yaffs_wr_data_obj(in, ??????local_buffer, ??????n_writeback,?0); yaffs_release_temp_buffer(dev,?local_buffer); 由于是临时的缓冲区,使用完成之后需要立即释放掉,要不然别人就用不了了。 通常上我们提到的缓冲区是为了缓解系统的压力而设计的,如VFS层的缓存页,通过pdflush线程组来进行数据的写入。从上面看来yaffs的缓冲区完全是为数据的overwrite专门使用的,基本上没有缓解系统的写入压力。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |