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內存中的數據結構。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章