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文件中主要是与空间管理相关的代码~
在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);
}
在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()函数中的一致~
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,只是分配了空间列表描述符~
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中的空间管理。
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()函数,下面分别进行分析
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);
}
在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插入到红黑树中。
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管理的空间就被初始化了