Chinaunix首页 | 论坛 | 博客
  • 博客访问: 733173
  • 博文数量: 79
  • 博客积分: 2671
  • 博客等级: 少校
  • 技术积分: 1247
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-02 15:26
个人简介

宅男

文章分类

全部博文(79)

文章存档

2017年(11)

2016年(12)

2015年(6)

2012年(10)

2011年(33)

2010年(7)

分类: LINUX

2011-04-11 20:53:55


在看过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, chunk, local_buffer);

memcpy(&local_buffer[start], buffer, n_copy);

chunk_written =

    yaffs_wr_data_obj(in, chunk,

      local_buffer,

      n_writeback, 0);

yaffs_release_temp_buffer(dev, local_buffer);

由于是临时的缓冲区,使用完成之后需要立即释放掉,要不然别人就用不了了。

通常上我们提到的缓冲区是为了缓解系统的压力而设计的,如VFS层的缓存页,通过pdflush线程组来进行数据的写入。从上面看来yaffs的缓冲区完全是为数据的overwrite专门使用的,基本上没有缓解系统的写入压力。

阅读(4127) | 评论(2) | 转发(1) |
给主人留下些什么吧!~~

mournjust2011-11-17 13:32:22

匿名: GFP_NOFS 是干什么的?.....
具有GFP_NOFS标志的分配不允许执行任何文件系统调用,而GFP_NOIO禁止任何I/O的初始化,这两个标志主要在文件系统和虚拟内存代码中使用,这些代码中的内存分配可以休眠,但是不应该发生递归的文件系统调用(摘之LDD3)

2011-11-16 14:08:53

GFP_NOFS 是干什么的?