2012年(5)
分类:
2012-08-13 15:38:19
在看过dreamice的《yaffs文件系统分析》 [转载]yaffs文件系统分析.doc (如果对yaffs的内核实现不太了解的,建议仔细阅读dreamice兄得这篇文章) 之后,总是感到意犹未尽,还不够深入,所以先结合自己了解的一些知识继续写下去。
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, 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专门使用的,基本上没有缓解系统的写入压力。