加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

Yaffs的读写

发布时间:2020-12-15 06:27:37 所属栏目:百科 来源:网络整理
导读:?在看过 dreamice 的《 yaffs 文件系统分析》 ?[转载]yaffs文件系统分析.doc (如果对yaffs的内核实现不太了解的,建议仔细阅读dreamice兄得这篇文章)??之后,总是感到意犹未尽,还不够深入,所以先结合自己了解的一些知识继续写下去。 Yaffs 文件系统最终
?在看过 dreamice 的《 yaffs 文件系统分析》

?[转载]yaffs文件系统分析.doc (如果对yaffs的内核实现不太了解的,建议仔细阅读dreamice兄得这篇文章)??之后,总是感到意犹未尽,还不够深入,所以先结合自己了解的一些知识继续写下去。

Yaffs文件系统最终是通过VFS层接口被调用的,所以yaffs必须给VFS层提供相应的接口。VFS提供的标准接口结构是inodedentry结构,也就是说不管最终yaffs是如何实现的,提供给VFS层的必须是inodedentry结构。

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_writecommit_write函数被write_beginwrite_end函数所代替,这儿为了保持yaffs的可移植性,采用了一个条件编译。

其中write_begin函数主要调用grab_cache_page_write_beginradix树里面查找要被写的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_objchunk_id方面的要求。这个函数返回值的类型为struct?yaffs_cache?*struct?yaffs_cache?结构体定义在yaffs_guts.h中。Object用于表示该cache中缓存的数据属于哪个文件,因为yaffs_cacheyaffs_obj都是属于ram中数据,不存在于flash中,所以yaffs_cachedatavoid*的指针。chunk_id用于表示缓存的数据是文件中第几个chunkdirty用于表示该缓存区的数据是否为脏。函数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_cachesyaffs_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专门使用的,基本上没有缓解系统的写入压力。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读