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内存中的数据结构。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 22
    评论
### 回答1: MD是Linux内核中的一个模块,可实现磁盘阵列的软件级RAID,它和RAID0、RAID1、RAID4、RAID5、RAID6一样,就是一种磁盘阵列方案。 MD中最主要的部分是驱动程序,它运行在内核态中。它将多个磁盘设备组合在一起,成为一个逻辑设备,该逻辑设备对应着一个块设备文件。在这个逻辑设备上,可实现磁盘阵列的软件级RAID功能。 MD驱动程序的主要源代码是在/drivers/md目录下的md.c文件中,它包括了MD的全部源代码,还有一些其他相关文件,比如raid5.c等。 在这个文件中,最值得学习的是内核的模块化编程思想。模块化编程是一种将代码划分为模块的软件设计方法,通过将代码划分为不同的模块,实现代码的解耦、可重用、可维护性等目标。 在MD.c中我们还可以看到内核中的锁、内存管理等基本的内核技术的应用。通过对MD.c进行源代码解读,能够深入了解Linux内核的实现原理,特别是MD的RAID功能的实现,对于我们进一步学习Linux内核的相关知识和对其进行应用开发具有很大的帮助。 总之,通过对MD.c源代码的解读,我们可以学习到Linux内核模块化编程思想、内存管理、锁机制等基本内核技术,进一步掌握Linux内核的实现原理,从而在Linux应用开发中更加熟练娴熟。 ### 回答2: MD(Multiple Devices)是一种常用的软件RAID方案,可以在Linux内核中实现,同时也是Linux内核中最基本的RAID模式之一。MD在实现中使用了驱动程序和用户空间工具,其中驱动程序包含在内核中,因此我们需要对MD的源代码进行解读。 MD的源代码是由C语言编写的,主要包含在drivers/md/目录下。在这个目录下,可以看到一些重要的文件和子目录,例如md.c、md.h、raid1.c、raid5.c等。这些文件和子目录定义了MD的基本结构和函数,如磁盘阵列的基本信息结构、磁盘块的操作函数等。 MD的实现思路比较清晰,可以简单地理解为将多个物理磁盘组合在一起,形成一个虚拟的块设备。在这个虚拟的块设备上,可以进行读写等操作,而具体的数据操作则由MD提供的不同RAID模式实现。例如,MD支持的RAID1模式就是将数据同步写入两个物理磁盘,以实现磁盘容错。而MD支持的RAID5模式则是将数据分散写入多个物理磁盘,通过奇偶校验等方式实现磁盘容错。 在MD的源代码解读过程中,需要重点关注这些RAID模式的实现方式和相关函数。同时,还需要了解整个MD的插入和移除机制、数据恢复机制等,以便更好地理解和修改MD的源代码。 总之,对于想要深入了解Linux内核中RAID相关实现的开发者来说,对MD的源代码进行解读是一个非常有价值的学习和探索过程。 ### 回答3: md是linux内核中的一个重要模块,支持多种存储设备,包括硬盘、闪存和网络存储等。如果想要深入了解linux内核的运行机制,就必须掌握md的源代码。下面就对md源代码进行解读。 md源代码的核心是md.c文件。这个文件中定义了md模块的核心函数,包括md_init()、md_run()和md_stop()等。其中md_init()函数主要负责初始化md模块的各个子系统,包括raid核心、hotplugging、proc文件系统和sysfs文件系统等。md_run()函数则是md模块的主要循环,负责轮询设备状态并执行相应的IO操作。md_stop()函数则是md模块的关闭函数,用于释放模块占用的各种资源。 除了md.c文件外,md模块的代码还包括一些关键性质的文件,例如mddev.c、md.h和md_u.h等。其中,mddev.c文件定义了md设备的数据结构,包括磁盘阵列、线性设备和伪设备等。md.h和md_u.h文件则分别定义了用户空间和内核空间的md控制接口,包括创建和删除设备、添加和删除磁盘等。 在理解md源代码时需要注意的是,md模块涉及到多个子系统,包括块设备、文件系统和RAID等,因此需要对这些子系统的工作原理和相互关系有清晰的理解。同时,由于md模块的代码相当复杂,需要仔细地阅读和调试,才能完成内核的定制和优化工作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值