NOVA文件系統——inode初始化與管理

1、NOVA inode簡介

在NOVA論文中對Inode Table進行了一下描述:

NOVA將每個inode table初始化爲2MB的inode塊數組。 每個NOVA inode都以128字節邊界對齊,因此給定的inode number NOVA可以輕鬆地定位目標inode。 NOVA按循環順序將新的inode分配給每個inode table,以便inode在inode table之間均勻分佈。 如果inode table已滿,則NOVA通過構建2MB sub-tables的鏈表來擴展它。 爲了減小inode table的大小,每個NOVA inode都包含一個有效位,並且NOVA將無效的inode重新用於新文件和目錄。 每個CPU一個inode table避免了inode分配爭用,並允許在故障恢復中進行並行掃描。

NOVA inode包含指向其log開頭和結尾的指針。log是一個4KB頁面的鏈接列表,並且尾部始終指向最新提交的log entry。 當系統首次訪問inode時,NOVA從頭到尾掃描log以重建DRAM數據結構。

接下來從NOVA源代碼中來具體分析inode的初始化與管理~

2、NOVA inode初始化代碼

2.1 nova_fill_super()

直接從nova_fill_super()函數入手,在nova_fill_super中也有對inode初始化的相關內容,首先映入眼簾的是inode_map結構體:

struct inode_map *inode_map;
inode_map定義在nova/nova.h文件中:

struct inode_map {
	struct mutex inode_table_mutex;
	struct rb_root	inode_inuse_tree;
	unsigned long	num_range_node_inode;
	struct nova_range_node *first_inode_range;
	int allocated;
	int freed;
};

通過對其他文件系統的瞭解,我知道在文件系統(例如ext4)中,有bitmap用來指示一個inode是否已經被分配,其他文件系統似乎就一個bit用來指示分配,0表示該inode沒有被分配,1表示該inode已被分配。

看到inode_map第一感覺也與這個相關,裏面的結構也有inode_inuse_tree相關的內容,目前還佔不清楚它的具體作用,通過繼續向下看代碼來確定它的作用。

在 nova_fill_super()函數中爲inode_maps開闢了空間,同時inode_maps也在nova_sb_info(sbi)中進行了保存:

	sbi->inode_maps = kzalloc(sbi->cpus * sizeof(struct inode_map),
					GFP_KERNEL);

從上面可以看出,每個CPU都會有一個inode_map結構,因爲每個CPU都有自己的inode_table。
繼續往下看,將每個inode_map與每個cpu的inode_map對應起來,同時初始化inode_table互斥器(鎖)

	for (i = 0; i < sbi->cpus; i++) {
		inode_map = &sbi->inode_maps[i];
		mutex_init(&inode_map->inode_table_mutex);
		inode_map->inode_inuse_tree = RB_ROOT;
	}

到了這裏之後,在nova_fill_super()函數中對於inode的內容就沒有了,我們進入到nova_init()函數查看裏面是否對inode進行了一些初始化。

2.2 nova_init()

定義在nova/super.c中

在nova_init()函數中,使用了nova_get_inode_by_ino函數來獲取inode,看到這裏有點奇怪,我們還沒看到inode的初始化,爲什麼這裏就開始在獲取inode了呢,我們看一看這個函數的執行:
pi = nova_get_inode_by_ino(sb, NOVA_BLOCKNODE_INO);
從上面這行可以看出,獲取的inode的inode number是一個宏定義,進入到宏定義中查看具體情況:

/* We have space for 31 reserved inodes */
#define NOVA_ROOT_INO		(1)
#define NOVA_INODETABLE_INO	(2)	/* Temporaty inode table */
#define NOVA_BLOCKNODE_INO	(3)
#define NOVA_INODELIST_INO	(4)
#define NOVA_LITEJOURNAL_INO	(5)
#define NOVA_INODELIST1_INO	(6)
#define NOVA_SNAPSHOT_INO	(7)	/* Fake snapshot inode */
#define NOVA_TEST_PERF_INO	(8)

/* Normal inode starts at 32 */
#define NOVA_NORMAL_INODE_START      (32)

從上面可以看出原來在NOVA中,有31個保留的inode number,而上面獲取的inode就來自這些特殊的inode number,普通的inode是從32開始的,進入到這個nova_get_inode_by_ino函數中,其內容如下:

/* If this is part of a read-modify-write of the inode metadata,
 * nova_memunlock_inode() before calling! */
static inline struct nova_inode *nova_get_inode_by_ino(struct super_block *sb,
						  u64 ino)
{
	if (ino == 0 || ino >= NOVA_NORMAL_INODE_START)
		return NULL;

	return nova_get_basic_inode(sb, ino);
}

從上面的調用來看,這個函數只能獲取保留的inode,如果inode number超過了31,那麼就直接返回NULL,實際的inode獲取是通過nova_get_basic_inode函數來實現的;

nova_get_basic_inode函數定義在nova/nova.h文件中,其內容如下:

static inline struct nova_inode *nova_get_basic_inode(struct super_block *sb,
	u64 inode_number)
{
	struct nova_sb_info *sbi = NOVA_SB(sb);
	u64 addr;

	addr = nova_get_basic_inode_addr(sb, inode_number);

	return (struct nova_inode *)(sbi->virt_addr + addr);
}

在這裏面其實也是通過函數nova_get_basic_inode_addr來獲取inode的地址,然後通過類似於基址加偏移的方式返回inode真正的地址。

再次進入到nova_get_basic_inode_addr函數中,其內容如下:

static inline u64 nova_get_basic_inode_addr(struct super_block *sb,
	u64 inode_number)
{
	return 	(NOVA_DEF_BLOCK_SIZE_4K * RESERVE_INODE_START) +
			inode_number * NOVA_INODE_SIZE;
}

其中RESERVE_INODE_START爲1,這個是以4K開始偏移inode_number * NOVA_INODE_SIZE,這在一個數組中好理解,至於爲什麼要在4K的基礎上偏移暫時還未知。

至此nova_get_inode_by_ino就找到了根源,繼續在nova_init()函數中往下看!

下面就是inode與inodetable初始化的核心函數了:

	if (nova_init_inode_inuse_list(sb) < 0)
		return ERR_PTR(-EINVAL);

	if (nova_init_inode_table(sb) < 0)
		return ERR_PTR(-EINVAL);

即在nova_init函數中,調用了nova_init_inode_inuse_list,與nova_init_inode_table來進行inode的初始化;

2.3 nova_init_inode_inuse_list()

定義在nova/inode.c中,其內容如下:

int nova_init_inode_inuse_list(struct super_block *sb)
{
	struct nova_sb_info *sbi = NOVA_SB(sb);
	struct nova_range_node *range_node;
	struct inode_map *inode_map;
	unsigned long range_high;
	int i;
	int ret;

	sbi->s_inodes_used_count = NOVA_NORMAL_INODE_START;

	/*
	 * 個人認爲是將保留的inode分發給每個CPU來進行存儲
	 * 這裏不太確定
	 */
	range_high = NOVA_NORMAL_INODE_START / sbi->cpus;
	if (NOVA_NORMAL_INODE_START % sbi->cpus)
		range_high++;

	for (i = 0; i < sbi->cpus; i++) {
		//拿出每個CPU的inode_map
		inode_map = &sbi->inode_maps[i];
		//分配一個nova_range_node
		range_node = nova_alloc_inode_node(sb);
		if (range_node == NULL)
			/* FIXME: free allocated memories */
			return -ENOMEM;

		range_node->range_low = 0;
		range_node->range_high = range_high;
		nova_update_range_node_checksum(range_node);
		//將這些inode插入到inodetree中
		ret = nova_insert_inodetree(sbi, range_node, i);
		if (ret) {
			nova_err(sb, "%s failed\n", __func__);
			nova_free_inode_node(sb, range_node);
			return ret;
		}
		inode_map->num_range_node_inode = 1;
		inode_map->first_inode_range = range_node;
	}

	return 0;
}

這個nova_init_inode_inuse_list,個人感覺就是把那些預留的inode分給各個CPU來進行存放,將這些預留的inode插入到inodetree中,然後更新inode_map的對應值:

		inode_map->num_range_node_inode = 1;
		inode_map->first_inode_range = range_node;

也就是說此時,只有一range_node_inode,然後第一個inode_range就指向這個range_node。接下來看nova_init_inode_table函數~

2.4 nova_init_inode_table()

int nova_init_inode_table(struct super_block *sb)
{
	struct nova_inode *pi = nova_get_inode_by_ino(sb, NOVA_INODETABLE_INO);
	struct nova_inode_info_header sih;
	int num_tables;
	int ret = 0;
	int i;

	nova_memunlock_inode(sb, pi);
	//對NOVA_INODETABLE_INO的inode進行一些初始化
	pi->i_mode = 0;
	pi->i_uid = 0;
	pi->i_gid = 0;
	pi->i_links_count = cpu_to_le16(1);
	pi->i_flags = 0;
	pi->nova_ino = NOVA_INODETABLE_INO;

	//這裏可以看到就是使用的2M,和論文中描述一致
	pi->i_blk_type = NOVA_BLOCK_TYPE_2M;
	nova_memlock_inode(sb, pi);

	
	sih.ino = NOVA_INODETABLE_INO;
	sih.i_blk_type = NOVA_BLOCK_TYPE_2M;

	num_tables = 1;
	if (replica_metadata)
		num_tables = 2;

	//在這裏面分配inode_table,有點納悶的是,不是對每個CPU進行分配
	for (i = 0; i < num_tables; i++) {
		ret = nova_alloc_inode_table(sb, &sih, i);
		if (ret)
			return ret;
	}

	PERSISTENT_BARRIER();
	return ret;
}

在nova_init_inode_table函數中,inode_table空間分配函數是nova_alloc_inode_table,接下來進入這個函數進行查看~

2.5 nova_alloc_inode_table()

static int nova_alloc_inode_table(struct super_block *sb,
	struct nova_inode_info_header *sih, int version)
{
	struct nova_sb_info *sbi = NOVA_SB(sb);
	struct inode_table *inode_table;
	unsigned long blocknr;
	u64 block;
	int allocated;
	int i;

	/*
	 * 前面提到了沒有按照per-CPU的方式進行分配,
	 * 在這裏就按照了每個CPU的方式進行了分配
	 */
	for (i = 0; i < sbi->cpus; i++) {
		//首先得到每個cpu的inode_table
		inode_table = nova_get_inode_table(sb, version, i);
		if (!inode_table)
			return -EINVAL;

		/* Allocate replicate inodes from tail */
		// 這裏爲inode table分配空間,至於爲什麼是log方式分配
		// 有大佬說是因爲inode中只有兩種塊,一個是log塊,一個是data塊
		//所以這裏是分配的log塊,進入到這個函數裏面,通過調用
		//nova_new_blocks分配2M的空間
		allocated = nova_new_log_blocks(sb, sih, &blocknr, 1, 1,
							i, version);
		nova_dbgv("%s: allocate log @ 0x%lx\n", __func__,
							blocknr);
		if (allocated != 1 || blocknr == 0)
			return -ENOSPC;

		
		block = nova_get_block_off(sb, blocknr, NOVA_BLOCK_TYPE_2M);
		nova_memunlock_range(sb, inode_table, CACHELINE_SIZE);
		inode_table->log_head = block;
		nova_memlock_range(sb, inode_table, CACHELINE_SIZE);
		nova_flush_buffer(inode_table, CACHELINE_SIZE, 0);
	}

	return 0;
}

上面代碼中最後通過nova_new_blocks函數分配2M的inode table空間~

3、NOVA inode管理

3.1 nova_create創建文件,初始化inode

nova/namei.c文件中,這裏主要關心與inode有關的內容,裏面主要調用nova_new_nova_inodenova_new_vfs_inode來創建相關的inode~

ino = nova_new_nova_inode(sb, &pi_addr);
inode = nova_new_vfs_inode(TYPE_CREATE, dir, pi_addr, ino, mode, 0, 0, &dentry->d_name, epoch_id);

在nova_new_nova_inode()函數中,通過nova_alloc_unused_inode函數分配沒有被使用的inode~

在nova_new_vfs_inode()函數中,通過new_inode分配一個vfs inode,然後對vfs inode的一些字段進行賦值~

3.2 nova_get_inode_address獲取inode地址

nova/inode.c文件中:

int nova_get_inode_address(struct super_block *sb, u64 ino, int version,
	u64 *pi_addr, int extendable, int extend_alternate)
{
	struct nova_sb_info *sbi = NOVA_SB(sb);
	struct nova_inode_info_header sih;
	struct inode_table *inode_table;
	unsigned int data_bits;
	unsigned int num_inodes_bits;
	u64 curr;
	unsigned int superpage_count;
	u64 alternate_pi_addr = 0;
	u64 internal_ino;
	int cpuid;
	int extended = 0;
	unsigned int index;
	unsigned int i = 0;
	unsigned long blocknr;
	unsigned long curr_addr;
	int allocated;

	sih.ino = NOVA_INODETABLE_INO;
	sih.i_blk_type = NOVA_BLOCK_TYPE_2M;
	/*
	 * BLOCK_TYPE的位數,4K爲12位
	 * unsigned int blk_type_to_shift[NOVA_BLOCK_TYPE_MAX] = {12, 21, 30};
	 */
	data_bits = blk_type_to_shift[sih.i_blk_type];
	/*
	 * #define NOVA_INODE_BITS   7
	 * 如果data_bits爲21,那麼此時num_inode_bits爲14,2^14個inode
	 * 如果爲12,那麼num_inode_bits爲5,
	 * 那麼就是說一個inode_table裏面有2^5(32)個inode
	 */
	num_inodes_bits = data_bits - NOVA_INODE_BITS;

	/*
	 * cpuid爲inode_number與cpu個數取餘,
	 * 這是否表明inode按照這種方式分給各個cpu?
	 * 這有可能,應該是按照這種方式分給各個CPU
	 */	
	cpuid = ino % sbi->cpus;
	/*
	 * 然後找出,這個inode,在每個CPU中的位置
	 * 這個位置應該是從0開始~
	 */
	internal_ino = ino / sbi->cpus;

	/* 獲取inode_table */
	inode_table = nova_get_inode_table(sb, version, cpuid);
	/* 由於inode是以2M爲鏈表來組織的,這裏是看在鏈表的第幾個節點上 */
	superpage_count = internal_ino >> num_inodes_bits;
	/* 找到在對應的2M內部的索引,也就是在其中一個鏈表節點的哪個位置 */
	index = internal_ino & ((1 << num_inodes_bits) - 1);

	curr = inode_table->log_head;
	if (curr == 0)
		return -EINVAL;

	for (i = 0; i < superpage_count; i++) {
		if (curr == 0)
			return -EINVAL;

		//直接在NOVA開始的地方加上curr
		curr_addr = (unsigned long)nova_get_block(sb, curr);
		/* Next page pointer in the last 8 bytes of the superpage */
		/* nova_inode_blk_size獲取每個inode的blocksize,在這裏是2M
		 * 得到每個塊的最後8byte的地址,裏面存放的是下一個塊的地址
		 * 依次循環就找到了inode number對應的那一個塊
		 */
		curr_addr += nova_inode_blk_size(&sih) - 8;
		curr = *(u64 *)(curr_addr);

		//如果等於0,那麼在下面進行分配
		if (curr == 0) {
			if (extendable == 0)
				return -EINVAL;

			extended = 1;

			//分配block
			allocated = nova_new_log_blocks(sb, &sih, &blocknr,
							1, 1, cpuid, version);

			if (allocated != 1)
				return allocated;

			curr = nova_get_block_off(sb, blocknr,
						NOVA_BLOCK_TYPE_2M);
			nova_memunlock_range(sb, (void *)curr_addr,
						CACHELINE_SIZE);
			*(u64 *)(curr_addr) = curr;
			nova_memlock_range(sb, (void *)curr_addr,
						CACHELINE_SIZE);
			nova_flush_buffer((void *)curr_addr,
						NOVA_INODE_SIZE, 1);
		}
	}

	/* Extend alternate inode table */
	if (extended && extend_alternate && replica_metadata)
		nova_get_inode_address(sb, ino, version + 1,
					&alternate_pi_addr, extendable, 0);

	//最後返回的地址有點類似於基址加偏移
	*pi_addr = curr + index * NOVA_INODE_SIZE;

	return 0;
}

從上面的代碼可以看出,inode是交錯着分配給各個CPU的,通過餘CPU的個數,確定當前inode屬於哪一個cpu的inode_table,通過整除確定在哪個cpu中inode_table中的位置。

由於NOVA的inode是以2M的鏈表來進行管理,下一個節點的指針存放在這2M的最後8Byte中,所以還是需要for循環一直去找,直到找到了對應的塊,然後再根據塊的地址加上inode在這個塊中的偏移就獲得了inode的地址~

4、總結

在NOVA中,inode table通過2M鏈表的方式進行管理,每個CPU交錯管理inode(可以在一定程度上利用空間局部性,提高併發),2M inode table的最後8byte存放了下一個節點的地址,這樣就將這些鏈表鏈接起來了~

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