文件系统之inode

注:本文分析基于linux-4.18.0-193.14.2.el8_2内核版本,即CentOS 8.2

1 inode

内存中,每个文件都有一个inode,一切皆文件,其实应该是说一切皆inode。inode保存了文件系统中一个文件的属性描述,比如文件类型,文件权限,属主,文件创建、读取或修改的时间等等。除了内存中的inode,磁盘中也有对应的inode结构,比如ext4文件系统中,内存中的inode是struct ext4_inode_info,而磁盘中的inode是struct ext4_inode_info,今天我们主要分析下内存中的inode结构。

2 inode主要成员变量

struct inode {
	umode_t			i_mode;  //文件类型和访问权限
	unsigned short		i_opflags;
	kuid_t			i_uid; //inode所属文件的所有者id
	kgid_t			i_gid; //inode所属文件所在组id
	unsigned int		i_flags;
	...
	const struct inode_operations	*i_op; //该inode操作函数集合
	struct super_block	*i_sb;  //指向所属超级块对象
	struct address_space	*i_mapping; //指向inode在内存中的pagecache
	...
	unsigned long		i_ino;  //inode节点号 ls -i 可以查看文件inode号
	...
	struct timespec64	i_atime; //文件最后访问时间
	struct timespec64	i_mtime; //文件最后修改时间
	struct timespec64	i_ctime; //文件创建时间
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	unsigned short          i_bytes; //文件在最后一块中的字节数
	unsigned int		i_blkbits; //block size(字节位数),一般为9
	enum rw_hint		i_write_hint;
	blkcnt_t		i_blocks; //文件的总块数,这里一个block为512 byte,也就是扇区大小
	...
	unsigned long		dirtied_when;	//inode变脏的时间
	unsigned long		dirtied_time_when;
	//通过该变量链接到全局inode哈希表inode_hashtable,用于inode的快速查找
	//哈希值通过超级块和inode number计算
	struct hlist_node	i_hash; 
	//通过该变量挂载到bdi_writeback的b_io链表,等待BDI回写,也就是这是脏inode
	struct list_head	i_io_list;	/* backing dev IO list */
	//该inode所在设备对应的bdi_writeback结构,用于inode的回写
	struct bdi_writeback	*i_wb;	
	...
	struct list_head	i_lru;	//通过该变量链接到超级块的s_inode_lru链表
	struct list_head	i_sb_list; //通过该变量链接到超级块的s_inodes上
	struct list_head	i_wb_list;	//通过该变量链接到超级块的s_inodes_wb上,回写统计
	union {
		//所有引用该inode的目录项将形成一个链表
		//比如文件被链接到其他的文件,就会有多个dentry
		struct hlist_head	i_dentry; 
		struct rcu_head		i_rcu;
	};
	...
	const struct file_operations	*i_fop;	//该inode对应的文件的操作函数集合
	struct file_lock_context	*i_flctx;
	struct address_space	i_data; //内嵌在inode的pagecache对象
	...
	void			*i_private; /* fs or device private pointer */
	RH_KABI_RESERVE(1)
	RH_KABI_RESERVE(2)
};

3 创建一个inode

我们以ext4的创建目录——mkdir为例,看下inode的创建过程,

const struct inode_operations ext4_dir_inode_operations = {
	.create		= ext4_create,
	...
	.mkdir		= ext4_mkdir,
	...
};

const struct file_operations ext4_dir_operations = {
	.llseek		= ext4_dir_llseek,
	.read		= generic_read_dir,
	...
	.open		= ext4_dir_open,
	.release	= ext4_release_dir,
};

static int ext4_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
{
	...
	//调用ext4文件系统的alloc_inode函数,创建新的inode
	inode = ext4_new_inode_start_handle(dir, S_IFDIR | mode,
					    &dentry->d_name,
					    0, NULL, EXT4_HT_DIR, credits);
	...
	//对于目录文件,i_op和i_fop指向的也是对应的目录操作函数
	//如果是普通文件,i_op和i_fop指向的会是file操作函数
	inode->i_op = &ext4_dir_inode_operations;
	inode->i_fop = &ext4_dir_operations;
	...
}

ext4_new_inode_start_handle通过以下调用路径,

ext4_new_inode_start_handle ->
	__ext4_new_inode ->
		new_inode 

最终在new_inode中将分配成功的inode挂到对应超级块的s_inodes链表上,这个链表就是这超级块所有的inode对象。

struct inode *new_inode(struct super_block *sb)
{
	struct inode *inode;

	spin_lock_prefetch(&sb->s_inode_list_lock);
	//调用对应文件系统的alloc_inode方法创建一个inode
	inode = new_inode_pseudo(sb);
	if (inode)
		//将该inode挂载到对应超级块的s_inodes链表上
		inode_sb_list_add(inode);
	return inode;
}

4 添加inode到inode cache链表

超级块上除了s_inodes链表,还有一个LRU链表s_inode_lru,这放的是未使用,或者干净的inode,比如回写完毕的inode就会挂载到这个链表上,这个链表也称为inode cache,在系统需要回收内存时,就会对这个链表下手,回收最近最少使用的inode。

添加到s_inode_lru链表的路径主要有两个:

  • 回写完毕的inode,也就是干净的inode
  • inode没有其他进程引用

不过最终都是通过inode_lru_list_add将inode挂载到s_inode_lru链表,

static void inode_lru_list_add(struct inode *inode)
{
	//将inode挂载到超级块的s_inode_lru链表
	if (list_lru_add(&inode->i_sb->s_inode_lru, &inode->i_lru))
		this_cpu_inc(nr_unused);
	else
		inode->i_state |= I_REFERENCED;
}

先看回写完毕的inode,

inode_sync_complete ->
	inode_add_lru ->
		inode_lru_list_add

然后是无其他进程引用时,

iput ->
	iput_final ->
		inode_add_lru ->
			inode_lru_list_add

5 从inode cache中删除inode(回收inode cache)

上面我们提到系统回收内存时,会对操作s_inode_lru链表,我们也大概看下,

long prune_icache_sb(struct super_block *sb, struct shrink_control *sc)
{
	LIST_HEAD(freeable);
	long freed;
	//遍历超级块的s_inode_lru链表,按照回收控制结构sc指定的回收数量,
	//将可回收的inode隔离到freeable链表中集中回收
	freed = list_lru_shrink_walk(&sb->s_inode_lru, sc,
				     inode_lru_isolate, &freeable);
	//将隔离出来的inode进行回收,这样隔离后可以避免锁竞争
	dispose_list(&freeable);
	return freed;
}

回收inode主要是要从几个链表中抽离,和脏数据回写

  • 超级块的s_inode_lru链表
  • bdi_writeback的b_io链表
  • 超级块的s_inodes链表
  • 回写pagecache
  • 全局inode哈希表
static void dispose_list(struct list_head *head)
{
	while (!list_empty(head)) {
		struct inode *inode;
		
		inode = list_first_entry(head, struct inode, i_lru);
		//将inode从超级块的s_inode_lru链表摘除
		list_del_init(&inode->i_lru);
		//回收inode
		evict(inode);
		cond_resched();
	}
}

static void evict(struct inode *inode)
{
	const struct super_operations *op = inode->i_sb->s_op;

	BUG_ON(!(inode->i_state & I_FREEING));
	BUG_ON(!list_empty(&inode->i_lru));
	//从bdi_writeback的b_io链表摘除
	if (!list_empty(&inode->i_io_list))
		inode_io_list_del(inode);
	//将inode从超级块的s_inodes链表摘除
	inode_sb_list_del(inode);

	//等待该inode回写完毕
	inode_wait_for_writeback(inode);
	//调用对应文件系统的evict_inode方法,回写pagecache
	if (op->evict_inode) {
		op->evict_inode(inode);
	} else {
		truncate_inode_pages_final(&inode->i_data);
		clear_inode(inode);
	}
	//如果是块设备inode
	if (S_ISBLK(inode->i_mode) && inode->i_bdev)
		bd_forget(inode);
	//如果是字符型设备
	if (S_ISCHR(inode->i_mode) && inode->i_cdev)
		cd_forget(inode);
	//从全局inode哈希表中摘除
	remove_inode_hash(inode);
	...
	//回收inode
	destroy_inode(inode);
}

处理完这些引用后,就可以调用destroy_inode回收到slab缓存,对于ext4,调用的是ext4_destroy_inode,

static void destroy_inode(struct inode *inode)
{
	BUG_ON(!list_empty(&inode->i_lru));
	__destroy_inode(inode);
	//调用对应文件系统的destroy_inode方法,将inode回收到slab缓存
	//对于ext4,调用的是ext4_destroy_inode
	if (inode->i_sb->s_op->destroy_inode)
		inode->i_sb->s_op->destroy_inode(inode);
	else
		call_rcu(&inode->i_rcu, i_callback);
}

static void ext4_destroy_inode(struct inode *inode)
{	
	if (!list_empty(&(EXT4_I(inode)->i_orphan))) {
		...
	}
	//调用ext4_i_callback将inode释放会slab缓存
	call_rcu(&inode->i_rcu, ext4_i_callback);
}

static void ext4_i_callback(struct rcu_head *head)
{
	struct inode *inode = container_of(head, struct inode, i_rcu);
	//释放回slab缓存
	kmem_cache_free(ext4_inode_cachep, EXT4_I(inode));
}

6 结构关系

  • 一块磁盘,三个分区,sda1和sda2是ext4文件系统,sda3是xfs文件系统
  • 全局超级块链表super_blocks将三个超级块串联在一起
  • sda1上的ext4_inode_info结构中内嵌inode结构,其中i_sb指向对应的超级块
  • A、B、C三个inode挂载到超级块s_nodes链表,A,B两个未使用的inode还会挂载到s_inode_lru链表
  • sda2上的ext4文件系统同sda1一样
  • 而sda3上的xfs除了对应的xfs_inode结构不同,大体结构也是一样的,这其实就是VFS的作用,对所有文件系统抽象了一层
    在这里插入图片描述
  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值