linux内核源码阅读之facebook硬盘加速flashcache之二

时间:2023-03-09 08:22:23
linux内核源码阅读之facebook硬盘加速flashcache之二
flashcache数据结构都在flashcache.h文件中,但在看数据结构之前,需要先过一遍flashcache是什么,要完成哪些功能?如果是自己设计这样一个系统的话,大概要怎么设计。
前面讲过,flashcache主要用途还是在写缓存上,要写入磁盘的IO先写入速度较快的SSD盘,随后再由单独的线程将SSD盘中脏数据块同步到磁盘中。这样看来,SSD就是一个缓存,有缓存的基本特性如命中、脏、水位线、写回策略等概念。
作为一个缓存,就必须划分为块,这些块对应于磁盘上大小相同的数据块,所以需要将SSD划分成数据块。这些数据块需要管理结构,这就需要一个结构体来表示这个数据块对应的位置和状态等信息。为了支持掉电之后系统还能把SSD中缓存的数据找出来,还需要一个表示缓存基本信息的结构。
除了在SSD上保存这些信息之外,在系统内存中还需要保存缓存映射,数据块基本情况等信息。
最后还要想办法让应用层知道有这个flashcache设备的存在,否则用户都不知道如何使用。当然你可能会问,flashcache只是设备的写缓存,难道就不能对用户透明吗?其实我也是这么想的,作为缓存竟然跳出来喧宾夺主,为什么要这样呢?前面讲过flashcache是基于dm层设计的,好了,dm层就是在块层之上,你要用我的框架那就得遵守我的规矩,dm规矩就是一个逻辑层,他的个性就不是幕后英雄,而是要出人投地。dm层作用就是让物理设备层对于上层应用是透明的,所以才可以无限循环组成新的逻辑层。所以在某些应用中,这将成为flashcache的一个缺点,那就是不能动态加载,就是说现在有一个块设备,想要写缓存的时候就创建一个写缓存,不想要写缓存的时候就可以删除掉,如果要想删除flashcache设备时,就必须断业务,就不能称之为动态删除了。
先看看SSD盘上有什么,第一个是flash_superblock
302struct flash_superblock {
303 sector_t size; /* Cache size */
304 u_int32_t block_size; /* Cache block size */
305 u_int32_t assoc; /* Cache associativity */
306 u_int32_t cache_sb_state; /* Clean shutdown ? */
307 char cache_devname[DEV_PATHLEN];
308 sector_t cache_devsize;
309 char disk_devname[DEV_PATHLEN];
310 sector_t disk_devsize;
311 u_int32_t cache_version;
312};

那是怎么知道的呢?猜的呀,超级块就叫superblock,放在SSD上的超级块就叫flash_superblock。猜归猜,有什么证据吗?看,代码在此:

704static int
705flashcache_md_create(struct cache_c *dmc, int force)
706{
707 struct flash_cacheblock *meta_data_cacheblock, *next_ptr;
708 struct flash_superblock *header;
709#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
710 struct io_region where;
711#else
712 struct dm_io_region where;
713#endif
714 int i, j, error;
715 sector_t cache_size, dev_size;
716 sector_t order;
717 int sectors_written = 0, sectors_expected = 0; /* debug */
718 int slots_written = 0; /* How many cache slots did we fill in this MD io block ? */
719
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
flashcache_md_create中调用了flashcache_dm_io_sync_vm,这个函数用于读数据,读是从这里读出来的,那么写也往这里写的。看参数where,bdev指向的是dmc->cache_dev,这就是SSD设备,sector是0,可见flash_superblock就是在最前面的。
那又怎么知道是在这个函数里创建的呢?这就要从dm设备说起,dm设备创建都调用构造函数,构造函数里做这些初始化。在flashcache模块里,找到flashcache_init模块初始化函数,注册一个dm target设备
     r = dm_register_target(&flashcache_target);
再接着看:
static struct target_type flashcache_target = {
.name = "flashcache",
.version= {1, 0, 1},
.module = THIS_MODULE,
.ctr = flashcache_ctr,
.dtr = flashcache_dtr,
.map = flashcache_map,
.status = flashcache_status,
.ioctl = flashcache_ioctl,
};

创建flashcache设备之后就会调用构造函数flashcache_ctr,再找到:

1290	if (persistence == CACHE_CREATE) {
1291 if (flashcache_md_create(dmc, 0)) {
1292 ti->error = "flashcache: Cache Create Failed";
1293 r = -EINVAL;
1294 goto bad5;
1295 }
1296 } else {

就看到了flashcache_md_create函数,追根溯源,我们知道了结构体flash_superblock就是在这里写到SSD的第0个扇区的。

现在看下每个字段:
302struct flash_superblock {
303 sector_t size; /* Cache size */
304 u_int32_t block_size; /* Cache block size */
305 u_int32_t assoc; /* Cache associativity */
306 u_int32_t cache_sb_state; /* Clean shutdown ? */
307 char cache_devname[DEV_PATHLEN];
308 sector_t cache_devsize;
309 char disk_devname[DEV_PATHLEN];
310 sector_t disk_devsize;
311 u_int32_t cache_version;
312};

size 是表示SSD上用作cache的block数量,这里的block是指SSD缓存的块大小,也就是用flashcache_create命令创建时指定的block_size大小。

block_size  就是缓存块大小
assoc  set数量
cache_sb_state 状态标志
cache_devname和disk_devname就分别是SSD盘和磁盘。
flash_superblock后面的数据是什么?接着看flashcache_md_create:
788	meta_data_cacheblock = (struct flash_cacheblock *)vmalloc(METADATA_IO_BLOCKSIZE);
789 if (!meta_data_cacheblock) {
790 DMERR("flashcache_md_store: Unable to allocate memory");
791 DMERR("flashcache_md_store: Could not write out cache metadata !");
792 return 1;
793 }
794 where.sector = 1;
795 slots_written = 0;
796 next_ptr = meta_data_cacheblock;
这里写的sector是1,当然是紧随着flash_superblock的,看上面代码可以看出是struct flash_cacheblock,由于block是dmc->size个数,flash_cacheblock是block的管理结构,所以也是dmc->size个数。在这个for循环中初始化了flash_cacheblock结构并且写到SSD盘上,写盘函数是flashcache_dm_io_sync_vm,再看参数where就知道目的盘和扇区。
797	j = MD_BLOCKS_PER_SECTOR;
798 for (i = 0 ; i < dmc->size ; i++) {
799 next_ptr->dbn = dmc->cache[i].dbn;
800#ifdef FLASHCACHE_DO_CHECKSUMS
801 next_ptr->checksum = dmc->cache[i].checksum;
802#endif
803 next_ptr->cache_state = dmc->cache[i].cache_state &
804 (INVALID | VALID | DIRTY);
805 next_ptr++;
806 slots_written++;
807 j--;
808 if (j == 0) {
809 /*
810 * Filled the sector, goto the next sector.
811 */
812 if (slots_written == MD_BLOCKS_PER_SECTOR * METADATA_IO_BLOCKSIZE_SECT) {
813 /*
814 * Wrote out an entire metadata IO block, write the block to the ssd.
815 */
816 where.count = slots_written / MD_BLOCKS_PER_SECTOR;
817 slots_written = 0;
818 sectors_written += where.count; /* debug */
819#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
820 error = flashcache_dm_io_sync_vm(&where, WRITE,
821 meta_data_cacheblock);
822#else
823 error = flashcache_dm_io_sync_vm(dmc, &where, WRITE,
824 meta_data_cacheblock);
825#endif
826 if (error) {
827 vfree((void *)header);
828 vfree((void *)meta_data_cacheblock);
829 vfree(dmc->cache);
830 DMERR("flashcache_md_create: Could not write cache metadata sector %lu error %d !",
831 where.sector, error);
832 return 1;
833 }
834 where.sector += where.count; /* Advance offset */
835 }
836 /* Move next slot pointer into next sector */
837 next_ptr = (struct flash_cacheblock *)
838 ((caddr_t)meta_data_cacheblock + ((slots_written / MD_BLOCKS_PER_SECTOR) * 512));
839 j = MD_BLOCKS_PER_SECTOR;
840 }
841 }
这个循环有必要仔细看一下,涉及到循环里面的808行和812行两个if语句和循环外842行的一个if语句。
808行的if (j == 0) ,在797行设置 j = MD_BLOCKS_PER_SECTOR; 
而宏定义为#define MD_BLOCKS_PER_SECTOR          (512 / (sizeof(struct flash_cacheblock)))
这个宏表示一个sector可以存放的flash_cacheblock的数量,那么808行的if表示的就是flash_cacheblock写了一个扇区,但一扇区可能没有完全放满,举个例子,如果flash_cacheblock结构体大小为20个字节,一个扇区是512字节,那么一个扇区可以放512/20=25个flash_cacheblock结构体,另外还多余出512%20=12个字节,那么这个时候就不能直接用next_ptr++找到下一个flash_cacheblock的位置了,而是按照837行:
836			/* Move next slot pointer into next sector */
837 next_ptr = (struct flash_cacheblock *)
838 ((caddr_t)meta_data_cacheblock + ((slots_written / MD_BLOCKS_PER_SECTOR) * 512));
839 j = MD_BLOCKS_PER_SECTOR;
将next_ptr指针指向到下一个扇区。那又为什么要从下一个扇区开始写呢?如果只是内存操作,就没有必要这样了,但这里是要写到SSD,按扇区写是最方便的,不然的话每次写个结构体还要计算是不是跨扇区,如果跨的话还要写两个扇区,除此之外还涉及到数据结构的设计,一个flash_cacheblock的写操作只挂在一个cache_c->cache_md_sector_head上面,如果分在两个扇区上,那就要涉及到两个扇区的写,再要出点异常一个扇区写成功一个扇区写失败就很难处理了。所以flash_cacheblock结构只放在一个扇区里是有道理的。
小结一下,808行if的意思就是如果一个扇区再放不下一个flash_cacheblock结构时,就移到下一个扇区开始写。
再看812行if (slots_written == MD_BLOCKS_PER_SECTOR * METADATA_IO_BLOCKSIZE_SECT) {
第一个宏刚刚看过,表示一个扇区最大flash_cacheblock数量,第二个宏定义如下:
#define METADATA_IO_BLOCKSIZE          (256*1024)
#define METADATA_IO_BLOCKSIZE_SECT     (METADATA_IO_BLOCKSIZE / 512)

METADATA_IO_BLOCKSIZE就是788行申请的内存大小,METADATA_IO_BLOCKSIZE_SECT就是扇区数,所以第二个if语句意思就是当写满了申请的内存空间时就做一次写SSD操作,将flash_cacheblock结构写到SSD。然后将slots_written置为0,到837行
               next_ptr = (struct flash_cacheblock *)

                    ((caddr_t)meta_data_cacheblock + ((slots_written / MD_BLOCKS_PER_SECTOR) * 512));

执行这句之后,next_ptr = meta_data_cacheblock,这样又从申请的这段内存的起始位置开始写,写满了就继续下一次写SSD操作。所以第二个if语句的意思就是写满METADATA_IO_BLOCKSIZE_SECT扇区就做一次写SSD操作。
理解了第二个if语句的意思,那么第三个if就很容易明白了。
     if (next_ptr != meta_data_cacheblock) {

就是最后的几个flash_cacheblock没有达到METADATA_IO_BLOCKSIZE_SECT扇区,所以要再余下的flash_cacheblock写到SSD。
到这里我们已经知道的SSD上的存储布局如下:
flash_superblock | flash_cacheblock ...| 

可以看出SSD盘前面放的是管理结构,后面大概放的是cache数据块了。但是作为一名软件工程师,不能用大概、可能之类的词语。因为大概、可能很大程度上意味着错误,对于错误,软件使用者是不能容忍的,所以我们也不能容忍这样的词语存在。顺便八一下,个人更喜欢软件工程师胜过程序员的称呼,因为用英文说就是software engineer,而程序员是programmer,后者是动词后面加er的名词,用中国的话讲就是干什么的,programmer译过来像“写代码的”,而软件工程师会显得更职业些。所以下次别人问你是干什么的时候,不要再说码农之类,要抬头大声地说是软件工程师。人必自轻而后人轻之,一个人要有起码的自信,才会有别人的尊重。
没错,后面放的是cache数据块,我们除了有这种直觉之外还要去证明。答案还是在flashcache_md_create中,
     /* Compute the size of the metadata, including header. 

        Note dmc->size is in raw sectors */

     dmc->md_sectors = INDEX_TO_MD_SECTOR(dmc->size / dmc->block_size) + 1 + 1;

     dmc->size -= dmc->md_sectors;     /* total sectors available for cache */

     dmc->size /= dmc->block_size;

     dmc->size = (dmc->size / dmc->assoc) * dmc->assoc;     

     /* Recompute since dmc->size was possibly trunc'ed down */

     dmc->md_sectors = INDEX_TO_MD_SECTOR(dmc->size) + 1 + 1;
md_sectors看注释是metadata大小,包括头部。所以这里有两次加1,第一个加1表示flash_cacheblock可能未满一个扇区,第二个1表示头部flash_superblock。所以md_sectors就是cache数据块的开始地址。最后一句重新计算dmc->md_sectors的意思就是说第一次计算的md_sectors可能偏大了,在纸上画一下就明白了。
md_sectors只是表示元数据的结束,对于表示cache数据块的开始的意思还不是很清晰。而接下来757行
747	/* Compute the size of the metadata, including header.
748 Note dmc->size is in raw sectors */
749 dmc->md_sectors = INDEX_TO_MD_SECTOR(dmc->size / dmc->block_size) + 1 + 1;
750 dmc->size -= dmc->md_sectors; /* total sectors available for cache */
751 dmc->size /= dmc->block_size;
752 dmc->size = (dmc->size / dmc->assoc) * dmc->assoc;
753 /* Recompute since dmc->size was possibly trunc'ed down */
754 dmc->md_sectors = INDEX_TO_MD_SECTOR(dmc->size) + 1 + 1;
755 DMINFO("flashcache_md_create: md_sectors = %d\n", dmc->md_sectors);
756 dev_size = to_sector(dmc->cache_dev->bdev->bd_inode->i_size);
757 cache_size = dmc->md_sectors + (dmc->size * dmc->block_size);
758 if (cache_size > dev_size) {
759 DMERR("Requested cache size exceeds the cache device's capacity" \
760 "(%lu>%lu)",
761 cache_size, dev_size);
762 vfree((void *)header);
763 return 1;
764 }
cache_size是缓存的总大小,dmc->md_sector是头部大小,dmc->size就是cache块数量,dmc->block_size是cache块大小。到这里为止我们就知道SSD存储布局如下:
flash_superblock | flash_cacheblock ...| cache数据块...|
看flash_cacheblock的结构
322struct flash_cacheblock {
323 sector_t dbn; /* Sector number of the cached block */
324#ifdef FLASHCACHE_DO_CHECKSUMS
325 u_int64_t checksum;
326#endif
327 u_int32_t cache_state; /* INVALID | VALID | DIRTY */
328};
这个结构是用来管理后面的cache数据块的。
到这里为止,我们对SSD上的存储结构有了初步的了解。现在以上一节flashcache_dirty_writeback为例,讲cache块如何从SSD盘到磁盘的。这个函数首先调用new_kcached_job创建一个kcached_job,
307struct kcached_job *
308new_kcached_job(struct cache_c *dmc, struct bio* bio,
309 int index)
310{
311 struct kcached_job *job;
312
313 job = flashcache_alloc_cache_job();
314 if (unlikely(job == NULL)) {
315 dmc->memory_alloc_errors++;
316 return NULL;
317 }
318 job->dmc = dmc;
319 job->index = index;
320 job->cache.bdev = dmc->cache_dev->bdev;
321 if (index != -1) {
322 job->cache.sector = (index << dmc->block_shift) + dmc->md_sectors;
323 job->cache.count = dmc->block_size;
324 }
325 job->error = 0;
326 job->bio = bio;
327 job->disk.bdev = dmc->disk_dev->bdev;
328 if (index != -1) {
329 job->disk.sector = dmc->cache[index].dbn;
330 job->disk.count = dmc->block_size;
331 } else {
332 job->disk.sector = bio->bi_sector;
333 job->disk.count = to_sector(bio->bi_size);
334 }
335 job->next = NULL;
336 job->md_sector = NULL;
337 return job;
338}
这里重点关注SSD数据块到磁盘的映射关系。这里的参数index是指cache数据块的下标,在writeback上下文中是不为-1的。从322行看出目的地址是SSD盘,扇区为(index << dmc->block_shift) + dmc->md_sectors;,写入目的地址是磁盘的dmc->cache[index].dbn,大小为block_size。调用dm_kcopyd_copy之后,SSD数据就已经拷贝到磁盘了,也就是缓存的脏数据块写到目的设备上了。
下面一节继续讲flashcache内存中的数据结构。