2012年(47)
分类:
2012-09-29 22:59:47
我们知道在每个nandflash的驱动中,都会在nand_scan函数之后调用add_mtd_partitions将nandflash设备虚拟为若干个逻辑块设备。也就是我们一般看到的mtdblock。
下面我们看一下这些函数的调用:
add_mtd_partitions:
->add_mtd_device:
list_for_each(this, &mtd_notifiers) {
struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list);
not->add(mtd);
}
在add_mtd_device中会遍历的调用通知链mtd_notifiers中的所有注册函数。下面我们看看到底注册了哪些函数。
(1)在init_mtdchar函数中调用register_mtd_user(¬ifier);注册
该函数对于的是mtd_notify_add函数,主要为每一个分区建立一个/dev/mtdX和./dev/mtdXro的接口。
(2)在register_mtd_blktrans函数中注册register_mtd_user(&blktrans_notifier);
static void blktrans_notify_add(struct mtd_info *mtd)
{
struct list_head *this;
if (mtd->type == MTD_ABSENT)
return;
list_for_each(this, &blktrans_majors) {
struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list);
tr->add_mtd(tr, mtd);
}
}
其中tr->add_mtd用于为不同的ftl转换层注册接口。
我们分别考察下面的几个add_mtd:
(1)mtdblock_add_mtd
(2)ftl_add_mtd
其中ftl_add_ mtd用于扫描一个分区来驱动是否是满足ftl的一个fat文件系统。
mtdblock_add_mtd函数用于为每一个分区建立mtdblock接口。
此时我们假设该分区是经过flasheraseall工具擦除过的一个分区。那么显然不是一个有效地fat文件系统。注册之后的各个结构体之间的关系如下图所示。
图一:blktrans_dev注册过程中各个结构体之间的关系
不同的接口也就意味着不同的处理方法,比如通过mtdblock接口进行操作的话(如mount -t vfat /dev/mtdblock2 /tmp 或者 cp /test.txt /dev/mtdblock2等等),那么显然它最终都是调用的都是mtdblock_tr中的处理办法。
static struct mtd_blktrans_ops mtdblock_tr = {
.name = "mtdblock",
.major = 31,
.part_bits = 0,
.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,
};
假如我现在设计了一种新型的ftl方案,其struct mtd_blktrans_ops如下:
static struct mtd_blktrans_ops md_tr = {
.name = "md",
.major = 35,
.part_bits = 0,
.open = md_open,
.flush = md_flush,
.release = md_release,
.readsect = md_readsect,
.writesect = md_writesect,
.add_mtd = md_add_mtd,
.remove_dev = md_remove_dev,
.owner = THIS_MODULE,
};
那么在通过/dev/mdX接口的所有操作都会最终追溯到上面的md_tr中的函数指针。
下面来分析一下fat文件系统的读写流程:
我们知道fat文件系统是为block类设备而设计的一种文件系统。
static int fat_writepage(struct page *page, struct writeback_control *wbc)
{
return block_write_full_page(page, fat_get_block, wbc);
}
block_write_full_page最终会调用到submit_bh来进行将buffer-head中数据写入到block设备中去。
Submit_bh
->submit_bio
->generic_make_request
该函数会调用到相应队列的make_request_fn函数指针。这个函数指针在初始化队列的时候会重定向,比如在注册mtdblock接口的时候调用register_mtd_blktrans函数:
tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);
函数blk_init_queue的输入参数中有一个是函数,该函数用于对队列的make_request_fn函数指针进行重定向。
最终每一个具体的io请求都挂到相应的gendisk的request_queue中去了。这样一次io请求就算发出去了。
既然io请求发出了,那么这样的请求是怎么样具体的落实下去的呢?
同样在register_mtd_blktrans函数中:
ret = kernel_thread(mtd_blktrans_thread, tr, CLONE_KERNEL);
kernel_thread创建了一个进程。这样我就明白了。内核通过一个后台进程来处理这样具体的io请求。
mtd_blktrans_thread:
->do_blktrans_request:
其中do_blktrans_request十分明了,根据request命令是读还是写来具体的调用struct mtd_blktrans_ops中writesect还是readsect。