• 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
    ", 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内存中的数据结构。
  • 相关阅读:
    [JavaScript]使用setTimeout减少多余事件
    Spring.NET教程(二)——环境搭建(基础篇) (转)
    IIS开启GZIP压缩效率对比及部署方法 (转)
    提高表格操作的十五款jQuery插件
    SQLServer和Oracle常用函数对比
    [hystar整理]Entity Framework 教程
    Remoting方法重载遇到的一个问题
    异变: input的背景background
    实时股票数据接口
    发现并解决ASP.NET内存耗尽(OOM),让服务器"永不重启"
  • 原文地址:https://www.cnblogs.com/keanuyaoo/p/3325039.html
Copyright © 2020-2023  润新知