zoukankan      html  css  js  c++  java
  • linux内核源码阅读之facebook硬盘加速flashcache之四


    这一小节介绍一下flashcache读写入口和读写的基础实现。
    首先,不管是模块还是程序,必须先找到入口,用户态代码会经常去先看main函数,内核看module_init,同样看IO流时候也要找到入口。flashcache作为一个dm_target,入口就是struct target_type 的map函数,对应的是flashcache_map函数:
    1581/*
    1582 * Decide the mapping and perform necessary cache operations for a bio request.
    1583 */
    1584int 
    1585flashcache_map(struct dm_target *ti, struct bio *bio,
    1586	       union map_info *map_context)
    1587{
    1588	struct cache_c *dmc = (struct cache_c *) ti->private;
    1589	int sectors = to_sector(bio->bi_size);
    1590	int queued;
    1591	
    1592	if (sectors <= 32)
    1593		size_hist[sectors]++;
    1594
    1595	if (bio_barrier(bio))
    1596		return -EOPNOTSUPP;
    1597
    1598	VERIFY(to_sector(bio->bi_size) <= dmc->block_size);
    1599
    1600	if (bio_data_dir(bio) == READ)
    1601		dmc->reads++;
    1602	else
    1603		dmc->writes++;
    1604
    1605	spin_lock_irq(&dmc->cache_spin_lock);
    1606	if (unlikely(sysctl_pid_do_expiry && 
    1607		     (dmc->whitelist_head || dmc->blacklist_head)))
    1608		flashcache_pid_expiry_all_locked(dmc);
    1609	if ((to_sector(bio->bi_size) != dmc->block_size) ||
    1610	    (bio_data_dir(bio) == WRITE && flashcache_uncacheable(dmc))) {
    1611		queued = flashcache_inval_blocks(dmc, bio);
    1612		spin_unlock_irq(&dmc->cache_spin_lock);
    1613		if (queued) {
    1614			if (unlikely(queued < 0))
    1615				flashcache_bio_endio(bio, -EIO);
    1616		} else {
    1617			/* Start uncached IO */
    1618			flashcache_start_uncached_io(dmc, bio);
    1619		}
    1620	} else {
    1621		spin_unlock_irq(&dmc->cache_spin_lock);		
    1622		if (bio_data_dir(bio) == READ)
    1623			flashcache_read(dmc, bio);
    1624		else
    1625			flashcache_write(dmc, bio);
    1626	}
    1627	return DM_MAPIO_SUBMITTED;
    1628}

    第1588行,dmc = ti->private,是什么时候保持的这个指针呢?看构造函数flashcache_ctr
    1350     ti->split_io = dmc->block_size;
    1351     ti->private = dmc;

    这里对private赋值,这里还有一个额外的收获,就是1350行,这是告诉dm层将IO分发为指定大小下发到dm_target设备。所以就有了flashcache_map函数1609行判断bio->bi_size是否为block_size大小。1606行和1610行是关于黑名单管理的,用于管理哪些进程或组不使用flashcache的,这里暂且不管,有兴趣可以查看flashcache_ioctl。
    为什么大小不为block_size就直接下发到磁盘呢?因为flashcache只处理block_size大小的数据,由于设置了ti->spilit_io为block_size,所以flashcache_map接收到的数据都不会超过block_size,取大的bio在dm层被拆分成最大block_size的bio下发。那么处理小块数据对flashcache来讲有什么不好呢?因为flashcache为了提高效率都在按block_size下发到磁盘,这时有小的数据块缓存,那么必须要凑齐block_size才能下发,那怎么凑齐呢,就要去磁盘里读。所以flashcache对于缓存的数据是有选择性的,那么也决定了上层了流量模型不能是小块数据,这样的话flashcache就会直接下发到磁盘,就没起到缓存的作用了。
    如果是小数据块的情况,第1611行调用flashcache_inval_block将与该bio有交集的cache块全部设置为INVALID,因为不再是最新的了。然后很不幸的是,设置cache块为invalid也会失败,按直观的想法就是设置一个脏标志位不就行了吗?根据墨菲定律,我们总是会过于乐观的判断一件事情。这里先不讲这些异常处理,因为如果还没有理解正常流程是什么样的,讲异常就失去了意义。
    这样我们就很快找了正常流程的读写入口,第1623行是读入口,第1625行是写入口。
    这里不急于去看读写实现,先来说说flashcache采用的读写磁盘的方法。
    flashcache中跟磁盘相关的读写分为以下两类:
    1)磁盘跟内存的交互
    2)磁盘跟磁盘之前的交互
    比如说读不命中时就是直接从磁盘读,属于第1种情况,那读命中呢?也是属于第1种情况,不过这时候是从SSD读。磁盘跟磁盘之间交互是用于写脏数据,将SSD中脏cache块拷贝到磁盘上去。现在介绍下两种情况使用的接口函数,这样后面在看读写流程时看到这两个函数就十分亲切了,并且清楚地知道数据是从哪里流向哪里。
    首先看第一种情况是通过flashcache_dm_io_sync_vm函数实现的:
    571int
    572flashcache_dm_io_sync_vm(struct cache_c *dmc, struct dm_io_region *where, int rw, void *data)
    573{
    574	unsigned long error_bits = 0;
    575	int error;
    576	struct dm_io_request io_req = {
    577		.bi_rw = rw,
    578		.mem.type = DM_IO_VMA,
    579		.mem.ptr.vma = data,
    580		.mem.offset = 0,
    581		.notify.fn = NULL,
    582		.client = dmc->io_client,
    583	};
    584
    585	error = dm_io(&io_req, 1, where, &error_bits);
    586	if (error)
    587		return error;
    588	if (error_bits)
    589		return error_bits;
    590	return 0;
    591}
    

    这里我们只关心dm_io的使用,并不关心其实现,因为这已经涉及到dm层的代码了。
    dmc 就是flashcache在内存中的管理结构
    where是读写的目标设备
    rw 读写
    data 对应的内存地址
    我们就以flashcache_md_create中读flash_superblock为例
    720	header = (struct flash_superblock *)vmalloc(512);
    721	if (!header) {
    722		DMERR("flashcache_md_create: Unable to allocate sector");
    723		return 1;
    724	}
    725	where.bdev = dmc->cache_dev->bdev;
    726	where.sector = 0;
    727	where.count = 1;
    728#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
    729	error = flashcache_dm_io_sync_vm(&where, READ, header);
    730#else
    731	error = flashcache_dm_io_sync_vm(dmc, &where, READ, header);
    732#endif
    

    第一个参数dmc,第二个参数设置设备为SSD,即cache_dev->bdev,扇区0开始,1个扇区大小,读,目的地址是header。由于flashcache_dm_io_sync_vm中第581行设置fn=NULL,所以该函数是同步的。
    现在看第二类磁盘和磁盘之间交互。看函数原型:
    int dm_kcopyd_copy(struct dm_kcopyd_client *kc, struct dm_io_region *from,
                 unsigned num_dests, struct dm_io_region *dests,
                 unsigned flags, dm_kcopyd_notify_fn fn, void *context);
    第一个参数dm_kcopyd_client,在使用kcopyd异步拷贝服务时,必须先创建一个对应的client,创建在flashcache_ctr函数中
    1208     r = dm_kcopyd_client_create(FLASHCACHE_COPY_PAGES, &dmc->kcp_client);
    1209     if (r) {
    1210          ti->error = "Failed to initialize kcopyd client
    ";
    1211          dm_io_client_destroy(dmc->io_client);
    1212          goto bad3;
    1213     }

    第二个参数dm_io_region是源地址,第四个参数是目的地址,定义如下
    struct dm_io_region {
         struct block_device *bdev;
         sector_t sector;
         sector_t count;          /* If this is zero the region is ignored. */
    };
    dm_kcopyd_notify_fn fn是kcopyd处理完请求的回调函数
    context 是回调函数参数,在flashcache都设置对应的kcached_job
    小结一下,以上两类函数其实本质是一样的,调用者填写好源地址和目的地址,地址可以是内存中的也可以是设备的,填好之后就调用函数,再接着就等回调通知。就好比我们在网上购物,帐号(dm_client)登录,我们只负责填好订单(dm_io_region),具体的生产制造物流过程我不关心,我只关心门铃响(dm_kcopyd_notify_fn)的时候我要的物品都已经送上门来了。
  • 相关阅读:
    程序员偷偷深爱的9个不良编程习惯
    JQuery实现放大镜
    ACM1995
    liubo.im
    Linux中的一些点
    EPOLL使用详解
    Elays'Blog
    c#数据库解析
    codeforces #332 div 2 D. Spongebob and Squares
    类型
  • 原文地址:https://www.cnblogs.com/suncoolcat/p/3327553.html
Copyright © 2011-2022 走看看