這一小節介紹一下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);
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\n";
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. */
};
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)的時候我要的物品都已經送上門來了。