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)
#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));
((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;
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 ", 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内存中的数据结构。