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_inode與nova_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存放了下一個節點的地址,這樣就將這些鏈表鏈接起來了~