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内存中的数据结构。

你可能感兴趣的:(Facebook)