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]  [<ffffffff813dbf33>] dump_stack+0x63/0x90
[ 5712.091016]  [<ffffffffc038a581>] nova_alloc_block_free_lists+0xa1/0xb0 [nova]
[ 5712.091019]  [<ffffffffc039b8c0>] nova_fill_super+0x360/0xcc0 [nova]
[ 5712.091022]  [<ffffffff81207766>] mount_bdev+0x1a6/0x1e0
[ 5712.091024]  [<ffffffffc039b560>] ? nova_check_integrity+0x460/0x460 [nova]
[ 5712.091028]  [<ffffffff811d952c>] ? alloc_pages_current+0x8c/0x110
[ 5712.091030]  [<ffffffffc039ac85>] nova_mount+0x15/0x20 [nova]
[ 5712.091031]  [<ffffffff81208098>] mount_fs+0x38/0x160
[ 5712.091033]  [<ffffffff81223b97>] vfs_kern_mount+0x67/0x110
[ 5712.091035]  [<ffffffff812263bf>] do_mount+0x23f/0xde0
[ 5712.091036]  [<ffffffff8122728f>] SyS_mount+0x9f/0x100
[ 5712.091040]  [<ffffffff81810532>] 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]  [<ffffffff813dbf33>] dump_stack+0x63/0x90
[ 5712.091051]  [<ffffffffc039bac9>] nova_fill_super+0x569/0xcc0 [nova]
[ 5712.091052]  [<ffffffff81207766>] mount_bdev+0x1a6/0x1e0
[ 5712.091054]  [<ffffffffc039b560>] ? nova_check_integrity+0x460/0x460 [nova]
[ 5712.091055]  [<ffffffff811d952c>] ? alloc_pages_current+0x8c/0x110
[ 5712.091057]  [<ffffffffc039ac85>] nova_mount+0x15/0x20 [nova]
[ 5712.091058]  [<ffffffff81208098>] mount_fs+0x38/0x160
[ 5712.091059]  [<ffffffff81223b97>] vfs_kern_mount+0x67/0x110
[ 5712.091061]  [<ffffffff812263bf>] do_mount+0x23f/0xde0
[ 5712.091062]  [<ffffffff8122728f>] SyS_mount+0x9f/0x100
[ 5712.091064]  [<ffffffff81810532>] 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]  [<ffffffff813dbf33>] dump_stack+0x63/0x90
[ 5712.091074]  [<ffffffffc038a5ff>] nova_init_blockmap+0x3f/0x220 [nova]
[ 5712.091076]  [<ffffffff8101b034>] ? show_stack+0x34/0x70
[ 5712.091077]  [<ffffffffc039bc24>] nova_fill_super+0x6c4/0xcc0 [nova]
[ 5712.091079]  [<ffffffff81207766>] mount_bdev+0x1a6/0x1e0
[ 5712.091080]  [<ffffffffc039b560>] ? nova_check_integrity+0x460/0x460 [nova]
[ 5712.091082]  [<ffffffff811d952c>] ? alloc_pages_current+0x8c/0x110
[ 5712.091084]  [<ffffffffc039ac85>] nova_mount+0x15/0x20 [nova]
[ 5712.091085]  [<ffffffff81208098>] mount_fs+0x38/0x160
[ 5712.091086]  [<ffffffff81223b97>] vfs_kern_mount+0x67/0x110
[ 5712.091087]  [<ffffffff812263bf>] do_mount+0x23f/0xde0
[ 5712.091089]  [<ffffffff8122728f>] SyS_mount+0x9f/0x100
[ 5712.091090]  [<ffffffff81810532>] 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管理的空間就被初始化了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章