NOVA文件系统——空间管理之初始化free-list

NOVA为了让NVMM空间分配与回收更快,它将NVMM分为持久性内存池(pools),每个CPU一个内存池(pool),并将空闲的NVMM页面lists放置在DRAM中。

如果当前的CPU pool中没有足够的页面可用,那么NOVA就从最大的那个池(pool)中进行分配,同时使用per-pool locks进行保护。

为了减少分配器的大小,NOVA使用一棵红黑树来维护空闲列表按地址排序,从而实现有效的合并并提供O(logn)回收。

为了提高性能,NOVA在运行期间不将分配器状态存储在NVMM中。 正常关闭时,它将在电源中断的情况下将分配器状态记录到recovery inode’s log ,并通过扫描所有索引节点的日志来恢复分配器状态。

NOVA积极分配日志空间,以避免需要频繁调整日志大小。 最初,inode的日志包含一页。 当日志耗尽可用空间时,NOVA分配足够的新页面使日志空间增加一倍,并将其附加到日志中。 如果日志长度超过给定阈值,则NOVA每次都会分配固定数量的页面。

NOVA源码:
https://github.com/NVSL/NOVA (论文中的)

https://github.com/NVSL/linux-nova(后续更新)

使用DRAM模拟持久性内存:
https://blog.csdn.net/SweeNeil/article/details/90265226

NOVA的安装与挂载:
https://blog.csdn.net/qq_34018840/article/details/79739307

在源码中 balloc.c文件中主要是与空间管理相关的代码~

1、free-list初始化

在NOVA中使用 struct free_list结构来描述空闲列表,在这里我把free_list称为空闲列表描述符,每个CPU都有一个free_list,该结构定义在头文件nova.h中:

struct free_list {
	spinlock_t s_lock;
     //rb_tree的根,前面介绍过红黑树来进行按地址排序的空闲列表维护
	struct rb_root	block_free_tree;
	struct nova_range_node *first_node;
	struct nova_range_node *last_node;
	int 		index;
	unsigned long	csum_start;
	unsigned long	replica_csum_start;
	unsigned long	parity_start;
	unsigned long	block_start;
	unsigned long	block_end;
	unsigned long	num_free_blocks;
	unsigned long	num_blocknode;
	unsigned long	num_csum_blocks;
	unsigned long	num_parity_blocks;
	u32		csum;		/* Protect integrity */

	/* Statistics */
	unsigned long	alloc_log_count;
	unsigned long	alloc_data_count;
	unsigned long	free_log_count;
	unsigned long	free_data_count;
	unsigned long	alloc_log_pages;
	unsigned long	alloc_data_pages;
	unsigned long	freed_log_pages;
	unsigned long	freed_data_pages;

	u64		padding[8];	/* Cache line break */
};

要分析NOVA空间管理,先从NOVA开始的地方出发,在super.c中,有init_nova_fs(),这是NOVA文件系统模块的加载函数,里面对nova_range_node_cachep、nova_inode_cachep、nova_snapshot_info_cachep开辟空间,然后注册文件系统~

在NOVA被mount的时候,整个流程如下:
vfs_kern_mount(vfs代码,super.c)->mount_fs(vfs代码,super.c)-> nova_mount(nova代码,这里传进来了sb,说明sb在这之前就进行了初始化,在mount_fs中)-> nova_fill_super();

static struct dentry *nova_mount(struct file_system_type *fs_type,
				  int flags, const char *dev_name, void *data)
{
	return mount_bdev(fs_type, flags, dev_name, data, nova_fill_super);
}

1.0 nova_fill_super

在nova_fill_super()中进一步进行一些初始化的工作,在该函数中,初始化nova_sb_info(内存中的nova super_block):

	sbi = kzalloc(sizeof(struct nova_sb_info), GFP_KERNEL);
	if (!sbi)
		return -ENOMEM;
	sb->s_fs_info = sbi;
	sbi->sb = sb;

我们直接在balloc.c的一些函数中添加dump_stack() 打印出函数调用栈

dump_stack()使用方式:
https://blog.csdn.net/SweeNeil/article/details/88061381

得到函数的调用栈如下:

root@ubuntu:~/Desktop/nova# mount -t NOVA -o init /dev/pmem0 /mnt/nova
root@ubuntu:~/Desktop/nova# dmesg
[ 5688.093321] nova: init_nova_fs: 1 cpus online
[ 5688.093324] nova: Arch new instructions support: PCOMMIT NO, CLWB NO
[ 5712.090980] nova: nova_get_block_info: dev pmem0, phys_addr 0x100000000, virt_addr ffffc90040000000, size 2147483648
[ 5712.090988] nova_alloc_block_free_lists
[ 5712.090990] CPU: 0 PID: 5534 Comm: mount Tainted: G           OE   4.4.4 #1
[ 5712.090991] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 05/19/2017
[ 5712.090992]  0000000000000286 00000000054a92ee ffff8800b313fca0 ffffffff813dbf33
[ 5712.090994]  ffff8800b37ed200 ffff8800b98c5800 ffff8800b313fcc0 ffffffffc038a581
[ 5712.090995]  ffff8800b37ed200 ffff8800b98c5800 ffff8800b313fd40 ffffffffc039b8c0
[ 5712.090996] Call Trace:
[ 5712.091010]  [] dump_stack+0x63/0x90
[ 5712.091016]  [] nova_alloc_block_free_lists+0xa1/0xb0 [nova]
[ 5712.091019]  [] nova_fill_super+0x360/0xcc0 [nova]
[ 5712.091022]  [] mount_bdev+0x1a6/0x1e0
[ 5712.091024]  [] ? nova_check_integrity+0x460/0x460 [nova]
[ 5712.091028]  [] ? alloc_pages_current+0x8c/0x110
[ 5712.091030]  [] nova_mount+0x15/0x20 [nova]
[ 5712.091031]  [] mount_fs+0x38/0x160
[ 5712.091033]  [] vfs_kern_mount+0x67/0x110
[ 5712.091035]  [] do_mount+0x23f/0xde0
[ 5712.091036]  [] SyS_mount+0x9f/0x100
[ 5712.091040]  [] entry_SYSCALL_64_fastpath+0x16/0x71
[ 5712.091041] nova: creating an empty nova of size 2147483648
[ 5712.091041] nova_init 
[ 5712.091042] CPU: 0 PID: 5534 Comm: mount Tainted: G           OE   4.4.4 #1
[ 5712.091043] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 05/19/2017
[ 5712.091044]  0000000000000286 00000000054a92ee ffff8800b313fcc0 ffffffff813dbf33
[ 5712.091045]  ffff8800b37ed200 ffff8800b98c5800 ffff8800b313fd40 ffffffffc039bac9
[ 5712.091046]  ffff8800b313fce8 dbf0ecc28119a217 ffffffffc03a1dc0 ffff880092c1e00c
[ 5712.091047] Call Trace:
[ 5712.091048]  [] dump_stack+0x63/0x90
[ 5712.091051]  [] nova_fill_super+0x569/0xcc0 [nova]
[ 5712.091052]  [] mount_bdev+0x1a6/0x1e0
[ 5712.091054]  [] ? nova_check_integrity+0x460/0x460 [nova]
[ 5712.091055]  [] ? alloc_pages_current+0x8c/0x110
[ 5712.091057]  [] nova_mount+0x15/0x20 [nova]
[ 5712.091058]  [] mount_fs+0x38/0x160
[ 5712.091059]  [] vfs_kern_mount+0x67/0x110
[ 5712.091061]  [] do_mount+0x23f/0xde0
[ 5712.091062]  [] SyS_mount+0x9f/0x100
[ 5712.091064]  [] entry_SYSCALL_64_fastpath+0x16/0x71
[ 5712.091065] nova_init_blockmap
[ 5712.091066] CPU: 0 PID: 5534 Comm: mount Tainted: G           OE   4.4.4 #1
[ 5712.091067] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 05/19/2017
[ 5712.091067]  0000000000000286 00000000054a92ee ffff8800b313fc60 ffffffff813dbf33
[ 5712.091068]  ffff8800b37ed200 ffff8800b98c5800 ffff8800b313fcc0 ffffffffc038a5ff
[ 5712.091069]  ffff8800b37ed200 0000000000000003 ffff8800b313fca0 ffffffff8101b034
[ 5712.091070] Call Trace:
[ 5712.091072]  [] dump_stack+0x63/0x90
[ 5712.091074]  [] nova_init_blockmap+0x3f/0x220 [nova]
[ 5712.091076]  [] ? show_stack+0x34/0x70
[ 5712.091077]  [] nova_fill_super+0x6c4/0xcc0 [nova]
[ 5712.091079]  [] mount_bdev+0x1a6/0x1e0
[ 5712.091080]  [] ? nova_check_integrity+0x460/0x460 [nova]
[ 5712.091082]  [] ? alloc_pages_current+0x8c/0x110
[ 5712.091084]  [] nova_mount+0x15/0x20 [nova]
[ 5712.091085]  [] mount_fs+0x38/0x160
[ 5712.091086]  [] vfs_kern_mount+0x67/0x110
[ 5712.091087]  [] do_mount+0x23f/0xde0
[ 5712.091089]  [] SyS_mount+0x9f/0x100
[ 5712.091090]  [] entry_SYSCALL_64_fastpath+0x16/0x71

在文件系统mount之后,就对空间管理进行了初始化,从打印出来的信息看是nova_alloc_block_free_lists()先被调用,然后是nova_init()函数,最后是nova_init_blockmap();这与nova_fill_super()函数中的一致~

1.1 nova_alloc_block_free_lists()

int nova_alloc_block_free_lists(struct super_block *sb)
{
	struct nova_sb_info *sbi = NOVA_SB(sb);
	struct free_list *free_list;
	int i;

	/* 为free_lists开辟空间,
	 * 大小为每个free_list与CPU个数的乘积,
	 * 每个CPU一个freelist结构
	 */
	sbi->free_lists = kzalloc(sbi->cpus * sizeof(struct free_list),
							GFP_KERNEL);
	if (!sbi->free_lists)
		return -ENOMEM;
	/*
	 * 针对每个CPU进行初始化,
	 * nova_get_free_list返回每个cpu free_list的地址
	 * 相当于初始化每个CPU的 free_list结构体
	 */
	for (i = 0; i < sbi->cpus; i++) {
		free_list = nova_get_free_list(sb, i);
		free_list->block_free_tree = RB_ROOT;
		spin_lock_init(&free_list->s_lock);
		free_list->index = i;
	}
	return 0;
}

nova_alloc_block_free_lists()函数主要就是分配free_list结构体,然后对每个CPU的free_list结构体进行了一下初始化,这时还没有真正的free_list,只是分配了空间列表描述符~

1.2 nova_init()

nova_init()可以看做NOVA的初始化函数 ,它被定义在nova/super.c文件中,初始化一个nova实例,只在nova_fill_super()中被调用。在nova_init()中有不少重要的信息,首先NOVA的默认Block大小为4KB:

	nova_dbgv("nova: Default block size set to 4K\n");
	blocksize = sbi->blocksize = NOVA_DEF_BLOCK_SIZE_4K;

	nova_set_blocksize(sb, blocksize);
	blocksize = sb->s_blocksize;

在nova_init()函数中,nova_init_blockmap(sb, 0);来进行空间的初始化,进入到该函数,就正式进入到本文的主角 balloc.c中的空间管理。

1.3 nova_init_blockmap()

void nova_init_blockmap(struct super_block *sb, int recovery)
{
	struct nova_sb_info *sbi = NOVA_SB(sb);
	struct rb_root *tree;
	struct nova_range_node *blknode;
	struct free_list *free_list;
	int i;
	int ret;

	/* Divide the block range among per-CPU free lists */
	sbi->per_list_blocks = sbi->num_blocks / sbi->cpus;
	for (i = 0; i < sbi->cpus; i++) {
		//从SuperBlock中获得每个CPU对应的free-list,这个free-list还是空壳
		free_list = nova_get_free_list(sb, i);
		tree = &(free_list->block_free_tree);
	    //在这里对free_list进行初始化,1.2给出代码与注释
		nova_init_free_list(sb, free_list, i);

		/* For recovery, update these fields later */
		/* 
		 * 在nova_init()函数中,传进来的参数就是0,也就是每次挂载都进行recovery
		 */
		if (recovery == 0) {
			//对于每个CPU而言,重新初始化
			free_list->num_free_blocks = free_list->block_end -
						free_list->block_start + 1;
			//nova_range_node定义在nova.h中
			/*
			struct nova_range_node {
				struct rb_node node;
				struct vm_area_struct *vma;
				unsigned long mmap_entry;
				unsigned long range_low;
				unsigned long range_high;
				u32	csum;		// Protect vma, range low/high 
			};
			这个结构标识nova的range,从代码上来看,似乎也是每个CPU一个blknode
			range_low与range_high对应于free_list的block_start与block_end
			*/
			blknode = nova_alloc_blocknode(sb);
			if (blknode == NULL)
				NOVA_ASSERT(0);
			blknode->range_low = free_list->block_start;
			blknode->range_high = free_list->block_end;
			nova_update_range_node_checksum(blknode);
			//把blknode插入blocktree,1.3给出代码与注释,tree为rb_tree_root
			ret = nova_insert_blocktree(sbi, tree, blknode);
			if (ret) {
				nova_err(sb, "%s failed\n", __func__);
				nova_free_blocknode(sb, blknode);
				return;
			}
			/* 初始化时,整个空间都是连续的,
			 * 所以只需要一个nova_range_node来进行描述,
			 * 因此第一个node是blknode,最后一个node也是blk_node
			 */
			free_list->first_node = blknode;
			free_list->last_node = blknode;
			free_list->num_blocknode = 1;
		}

		nova_dbgv("%s: free list %d: block start %lu, end %lu, "
				"%lu free blocks\n", __func__, i,
				free_list->block_start,
				free_list->block_end,
				free_list->num_free_blocks);
	}
}

在1.3中比较重要的有nova_init_free_list()函数和nova_insert_blocktree()函数,下面分别进行分析

1.4 nova_init_free_list()

static void nova_init_free_list(struct super_block *sb,
	struct free_list *free_list, int index)
{
	struct nova_sb_info *sbi = NOVA_SB(sb);
	unsigned long per_list_blocks;

	//将整个NVM空间均分给各个CPU,这些信息在SuperBlock中都进行了保存
	per_list_blocks = sbi->num_blocks / sbi->cpus;

	/*这里的index对应的是每个cpu,根据代码来看,每个CPU的free_list都是完整的一块
	 *per_list_blocks大小的NVM区域,所以free_list的block_start就直接等于区域大小乘CPU数量
	 */
	free_list->block_start = per_list_blocks * index;
	free_list->block_end = free_list->block_start +
					per_list_blocks - 1;
	/*在这两个判断语句中,将头和尾占用的部分去掉,例如superblock在头部,
	 *也就是第1个CPU(0),与最后一个CPU的free_list要比其他部分少。
	 * head_reserved_blocks,与tail_reserved_blocks记录在nova_sb_info结构中
	 */
	if (index == 0)
		free_list->block_start += sbi->head_reserved_blocks;
	if (index == sbi->cpus - 1)
		free_list->block_end -= sbi->tail_reserved_blocks;
	//下面是校验,校验和与就校验,保存在free_list结构中
	nova_data_csum_init_free_list(sb, free_list);
	nova_data_parity_init_free_list(sb, free_list);
}

1.5 nova_insert_blocktree()

在nova_insert_blocktree()函数中,主要执行的是nova_insert_range_node(tree, new_node)
参数sbi(nova_sb_info)没有继续下传,在1.3中主要进行 nova_insert_range_node()函数的了解

nova_insert_range_node()主要还是将nova_range_node中的rb_node插入到红黑树中。

2、总结

NOVA使用free_list来描述空闲页面链表,可以把free_list称为空闲列表描述符,在NOVA被mount之后,空间管理机制就开始运行,首先通过nova_alloc_block_free_lists为free_list开辟空间,整个文件系统的free_list在nova_sb_info中保存。在nova_init中,对NOVA进行初始化,通过nova_init_blockmap()函数来正式给每个CPU分配空间。在nova_init_blockmap()中,通过nova_get_free_list()函数获得free_list结构体,然后通过nova_init_free_list()函数进行初始化,进一步填充free_list结构体。然后引入nova_range_node结构体,来表示每一个nova range,将nova_range_node插入到红黑树中,以此初始化完成。

step 1 :
nova_alloc_block_free_lists,nova_get_free_list 为free_list开辟空间,并进行一些简单的初始化,此时每个CPU还没有真正分得空间,只是为free_list结构体开辟了空间。

step 2:
在nova_init()函数中进行一些初始化,其中也进行空间管理的初始化,该函数中,nova_init_blockmap()中来进行空间分配。在该函数中,nova_init_free_list()正式按照CPU分配空间。

step 3:
通过nova_range_node结构体来描述一个连续的空闲空间,将nova_range_node插入到红黑树中,NOVA管理的空间就被初始化了

你可能感兴趣的:(持久性内存文件系统,文件系统,NOVA文件系统,Linux内核,空间管理)