IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux内核文件系统10 -> 正文阅读

[系统运维]Linux内核文件系统10

2021SC@SDUSC

inode.c(1)

今天来分析inode.c文件。有了前面对ext4_jbd2.h、acl.h等头文件的分析做基础,今天的分析将相对简单。

在看代码之前,首先要说一下inode数据结构。inode是Linux内核文件系统中最重要的数据结构之一,里面保存了文件的大小、文件块的大小、创建时间等参数,可以说,一个inode就代表了一个文件。因为软连接、硬连接的存在,指向一个文件的路径可能有多个,即一个文件可以有多个dentry,但是一个文件只能有一个inode。

ext4_inode 定义于/fs/ext4/ext4.h,大小为256字节,也就是说一个4KB的块可以保存16个inode。

/*
 * Structure of an inode on the disk
 */
struct ext4_inode {
	__le16	i_mode;		/*文件模式 */
	__le16	i_uid;		/* 低16位的owner Uid */
	__le32	i_size_lo;	/* 文件大小的字节数 */
	__le32	i_atime;	/* 存取时间 */
	__le32	i_ctime;	/* inode改变时间 */
	__le32	i_mtime;	/* 修改时间 */
	__le32	i_dtime;	/* 删除时间 */
	__le16	i_gid;		/* 低16位 group id */
	__le16	i_links_count;	/* 链接数 */
	__le32	i_blocks_lo;	/* 块数目 */
	__le32	i_flags;	/* 文件标志 */
	union {
		struct {
			__le32  l_i_version;
		} linux1;
		struct {
			__u32  h_i_translator;
		} hurd1;
		struct {
			__u32  m_i_reserved1;
		} masix1;
	} osd1;				/* OS dependent 1 */
	__le32	i_block[EXT4_N_BLOCKS];/* 块指针 */
	__le32	i_generation;	/* 文件版本(适用于NFS) */
	__le32	i_file_acl_lo;	/* 文件的ACL */
	__le32	i_size_high;
	__le32	i_obso_faddr;	/* 弃用片段的地址 */
	union {
		struct {
			__le16	l_i_blocks_high; /* were l_i_reserved1 */
			__le16	l_i_file_acl_high;
			__le16	l_i_uid_high;	/* these 2 fields  */
			__le16	l_i_gid_high;	/* were reserved2[0]  */
			__le16	l_i_checksum_lo;/* crc32c(uuid+inum+inode) LE */
			__le16	l_i_reserved;
		} linux2;
		struct {
			__le16	h_i_reserved1;	/* 在ext4中被删除的残片号/大小 */
			__u16	h_i_mode_high;
			__u16	h_i_uid_high;
			__u16	h_i_gid_high;
			__u32	h_i_author;
		} hurd2;
		struct {
			__le16	h_i_reserved1;	/* 在ext4中被删除的残片号/大小 */
			__le16	m_i_file_acl_high;
			__u32	m_i_reserved2[2];
		} masix2;
	} osd2;				/* OS dependent 2 */
	__le16	i_extra_isize;
	__le16	i_checksum_hi;	/* crc32c(uuid+inum+inode) BE */
	__le32  i_ctime_extra;  /* 额外的变化(change)时间   (nsec << 2 | epoch) */
	__le32  i_mtime_extra;  /* 额外的修改(Modification)时间   (nsec << 2 | epoch) */
	__le32  i_atime_extra;  /* 额外的访问(Access)时间   (nsec << 2 | epoch) */
	__le32  i_crtime;       /* 文件创建时间 */
	__le32  i_crtime_extra; /* 额外的文件创建时间 (nsec << 2 | epoch) */
	__le32  i_version_hi;	/* 高32位(64位版本) */
	__le32	i_projid;	    /* Project ID */
};

由此可见字段i_block的大小为60个字节,即__le32 i_block[EXT4_N_BLOCKS]且EXT4_N_BLOCKS=15。其中前12个字节为extent头,保存的是extent的基本信息;后48个字节可以保存4个extent节点,每个extent节点为12字节大小。

以下是inode.c文件的代码分析:

/*
*测试一个索引节点是否为快速符号链接。
*快速符号链接的符号链接数据存储在ext4_inode_info->i_data中。
 */
int ext4_inode_is_fast_symlink(struct inode *inode)
{
	if (!(EXT4_I(inode)->i_flags & EXT4_EA_INODE_FL)) {
		int ea_blocks = EXT4_I(inode)->i_file_acl ?
				EXT4_CLUSTER_SIZE(inode->i_sb) >> 9 : 0;

		if (ext4_has_inline_data(inode))
			return 0;

		return (S_ISLNK(inode->i_mode) && inode->i_blocks - ea_blocks == 0);
	}
	return S_ISLNK(inode->i_mode) && inode->i_size &&
	       (inode->i_size < EXT4_N_BLOCKS * 4);
}

/*
 * 在i_nlink为零的最后一个iput()函数中调用。
 */
void ext4_evict_inode(struct inode *inode)
{
	handle_t *handle;
	int err;
	/*
	 *最终的inode清理和释放:
	 *sb + inode (ext4_orphan_del()),块位图(block bitmap),组描述符(xattr块释放),
	 位图(bitmap),组描述符(inode释放)
	 */
	int extra_credits = 6;
	struct ext4_xattr_inode_array *ea_inode_array = NULL;
	bool freeze_protected = false;

	trace_ext4_evict_inode(inode);

	if (inode->i_nlink) {
		/*
		 *当记录数据脏缓冲区只在日志中跟踪。
		 因此,尽管mm认为一切就绪,可以获取inode,
		 但在运行的事务中仍然可能有一些页面需要写入,或者等待被检查点。
		 因此,调用jbd2_journal_invalidatepage()(通过truncate_inode_pages())来丢弃这些缓冲区可能会导致数据丢失。
		 而且,即使我们没有丢弃这些缓冲区,在获取inode之后,我们也无法找到它们,
		 因此,如果用户试图在事务被检查点之前读取这些缓冲区,就可能看到过期的数据。
		 所以要小心,把所有东西都放到圆盘上。
		 我们使用ei->i_datasync_tid来存储包含inode数据的最新事务。
		 *
		 *注意目录没有这个问题,因为它们不使用页面缓存。
		 */
		if (inode->i_ino != EXT4_JOURNAL_INO &&
		    ext4_should_journal_data(inode) &&
		    (S_ISLNK(inode->i_mode) || S_ISREG(inode->i_mode)) &&
		    inode->i_data.nrpages) {
			journal_t *journal = EXT4_SB(inode->i_sb)->s_journal;
			tid_t commit_tid = EXT4_I(inode)->i_datasync_tid;

			jbd2_complete_transaction(journal, commit_tid);
			filemap_write_and_wait(&inode->i_data);
		}
		truncate_inode_pages_final(&inode->i_data);

		goto no_delete;
	}

	if (is_bad_inode(inode))
		goto no_delete;
	dquot_initialize(inode);

	if (ext4_should_order_data(inode))
		ext4_begin_ordered_truncate(inode, 0);
	truncate_inode_pages_final(&inode->i_data);

	/*
	 对于带有日志数据的inode,事务提交可能已经污染了inode。
	 Flush worker因为I_FREEING标志而忽略了它,
	 但是我们仍然需要从writeback列表中移除这个inode。
	 */
	if (!list_empty_careful(&inode->i_io_list)) {
		WARN_ON_ONCE(!ext4_should_journal_data(inode));
		inode_io_list_del(inode);
	}

	/*
	 保护我们不被冻结- iput()调用者不需要有任何保护。
	 但是,当我们处于运行的事务中时,我们已经得到了防止冻结的保护,
	 而且由于锁排序约束,我们无法获取进一步的保护。
	 */
	if (!ext4_journal_current_handle()) {
		sb_start_intwrite(inode->i_sb);
		freeze_protected = true;
	}

	if (!IS_NOQUOTA(inode))
		extra_credits += EXT4_MAXQUOTAS_DEL_BLOCKS(inode->i_sb);

	/*
	 ext4_blocks_for_truncate()和extra_credits中都包含块位图、组描述符和inode,所以减去3。
	 */
	handle = ext4_journal_start(inode, EXT4_HT_TRUNCATE,
			 ext4_blocks_for_truncate(inode) + extra_credits - 3);
	if (IS_ERR(handle)) {
		ext4_std_error(inode->i_sb, PTR_ERR(handle));
		/*
		如果我们要跳过正常的清理,我们仍然需要确保核内孤立链表被正确清理。 
		 */
		ext4_orphan_del(NULL, inode);
		if (freeze_protected)
			sb_end_intwrite(inode->i_sb);
		goto no_delete;
	}

	if (IS_SYNC(inode))
		ext4_handle_sync(handle);

	/*
	 在调用 ext4_truncate() 之前将 inode->i_size 设置为 0。 
	 这里我们需要对符号链接进行特殊处理,
	 因为 i_size 用于确定 ext4_inode_info->i_data 是否包含符号链接数据或块映射。 
	 将 i_size 设置为 0 将删除其快速符号链接状态。 
	 擦除 i_data 使其成为有效的空块映射。
	 */
	if (ext4_inode_is_fast_symlink(inode))
		memset(EXT4_I(inode)->i_data, 0, sizeof(EXT4_I(inode)->i_data));
	inode->i_size = 0;
	err = ext4_mark_inode_dirty(handle, inode);
	if (err) {
		ext4_warning(inode->i_sb,
			     "couldn't mark inode dirty (err %d)", err);
		goto stop_handle;
	}
	if (inode->i_blocks) {
		err = ext4_truncate(inode);
		if (err) {
			ext4_error_err(inode->i_sb, -err,
				       "couldn't truncate inode %lu (err %d)",
				       inode->i_ino, err);
			goto stop_handle;
		}
	}

	/* 删除 xattr 引用。 */
	err = ext4_xattr_delete_inode(handle, inode, &ea_inode_array,
				      extra_credits);
	if (err) {
		ext4_warning(inode->i_sb, "xattr delete (err %d)", err);
stop_handle:
		ext4_journal_stop(handle);
		ext4_orphan_del(NULL, inode);
		if (freeze_protected)
			sb_end_intwrite(inode->i_sb);
		ext4_xattr_inode_array_free(ea_inode_array);
		goto no_delete;
	}

	/*
	 删除 ext4_truncate 创建的孤立记录。
	 注意 ext4_orphan_del() 必须能够处理不存在的孤儿的删除
	 ——这是因为我们不知道 ext4_truncate() 是否真的创建了一个孤儿记录。
	 */
	ext4_orphan_del(handle, inode);
	EXT4_I(inode)->i_dtime	= (__u32)ktime_get_real_seconds();

	/*
	一个微妙的排序要求:如果出现任何问题(事务中止、IO 错误等等),
	那么我们仍然可以执行这些后续步骤(fs 将已经被标记为有错误),
	但是如果 mark_dirty 失败,我们就不能释放 inode。
	 */
	if (ext4_mark_inode_dirty(handle, inode))
		/* 如果失败,只需清除所需的核心 inode。 */
		ext4_clear_inode(inode);
	else
		ext4_free_inode(handle, inode);
	ext4_journal_stop(handle);
	if (freeze_protected)
		sb_end_intwrite(inode->i_sb);
	ext4_xattr_inode_array_free(ea_inode_array);
	return;
no_delete:
	if (!list_empty(&EXT4_I(inode)->i_fc_list))
		ext4_fc_mark_ineligible(inode->i_sb, EXT4_FC_REASON_NOMEM);
	ext4_clear_inode(inode);	/* 我们必须保证清除inode... */
}


/*
 * 在 i_data_sem down 时调用,这很重要,因为我们可以从这里调用 ext4_discard_preallocations()。
 */
void ext4_da_update_reserve_space(struct inode *inode,
					int used, int quota_claim)
{
	struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
	struct ext4_inode_info *ei = EXT4_I(inode);

	spin_lock(&ei->i_block_reservation_lock);
	trace_ext4_da_update_reserve_space(inode, used, quota_claim);
	if (unlikely(used > ei->i_reserved_data_blocks)) {
		ext4_warning(inode->i_sb, "%s: ino %lu, used %d "
			 "with only %d reserved data blocks",
			 __func__, inode->i_ino, used,
			 ei->i_reserved_data_blocks);
		WARN_ON(1);
		used = ei->i_reserved_data_blocks;
	}

	/* 更新每个 inode 的预留 */
	ei->i_reserved_data_blocks -= used;
	percpu_counter_sub(&sbi->s_dirtyclusters_counter, used);

	spin_unlock(&ei->i_block_reservation_lock);

	/* 更新数据块的配额子系统 */
	if (quota_claim)
		dquot_claim_block(inode, EXT4_C2B(sbi, used));
	else {
		/*
		 我们确实使用已经延迟分配的偏移量进行了错误分配。 
		 因此,在延迟分配的回写时,我们不应该收回分配块的配额。
		 */
		dquot_release_reservation_block(inode, EXT4_C2B(sbi, used));
	}

	/*
	 * 如果我们已经完成了所有挂起的块分配,并且如果 inode 上没有任何写入者,我们可以丢弃 inode 预分配。
	 */
	if ((ei->i_reserved_data_blocks == 0) &&
	    !inode_is_open_for_write(inode))
		ext4_discard_preallocations(inode, 0);
}


/*
* ext4_map_blocks() 函数尝试查找请求的块,如果块已经被映射则返回。
  * 否则获取 i_data_sem 的写锁并分配块并将分配的块存储在结果缓冲区头中并将其标记为映射。
  * 如果文件类型是extents based,则调用ext4_ext_map_blocks(),否则调用ext4_ind_map_blocks()处理基于间接映射的文件
  * 成功时,它返回被映射或分配的块数。 如果 create==0 并且块已预先分配且未写入,则生成的 @map 将标记为未写入。 如果 create == 1,它会将 @map 标记为已映射。
  * 如果简单查找失败(块尚未分配),则返回 0,在这种情况下,@map 返回为未映射,但我们仍然填充 map->m_len 以指示从 map->m_lblk 开始的孔的长度。
  * 它在分配失败的情况下返回false。
 */
int ext4_map_blocks(handle_t *handle, struct inode *inode,
		    struct ext4_map_blocks *map, int flags)
{
	struct extent_status es;
	int retval;
	int ret = 0;
#ifdef ES_AGGRESSIVE_TEST
	struct ext4_map_blocks orig_map;

	memcpy(&orig_map, map, sizeof(*map));
#endif

	map->m_flags = 0;
	ext_debug(inode, "flag 0x%x, max_blocks %u, logical block %lu\n",
		  flags, map->m_len, (unsigned long) map->m_lblk);

	/*
	 * ext4_map_blocks 返回一个整数,而 m_len 是一个无符号整数
	 */
	if (unlikely(map->m_len > INT_MAX))
		map->m_len = INT_MAX;

	/* 我们可以处理小于 EXT_MAX_BLOCKS 的块数 */
	if (unlikely(map->m_lblk >= EXT_MAX_BLOCKS))
		return -EFSCORRUPTED;

	/* 首先查找范围状态树 */
	if (!(EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY) &&
	    ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es)) {
		if (ext4_es_is_written(&es) || ext4_es_is_unwritten(&es)) {
			map->m_pblk = ext4_es_pblock(&es) +
					map->m_lblk - es.es_lblk;
			map->m_flags |= ext4_es_is_written(&es) ?
					EXT4_MAP_MAPPED : EXT4_MAP_UNWRITTEN;
			retval = es.es_len - (map->m_lblk - es.es_lblk);
			if (retval > map->m_len)
				retval = map->m_len;
			map->m_len = retval;
		} else if (ext4_es_is_delayed(&es) || ext4_es_is_hole(&es)) {
			map->m_pblk = 0;
			retval = es.es_len - (map->m_lblk - es.es_lblk);
			if (retval > map->m_len)
				retval = map->m_len;
			map->m_len = retval;
			retval = 0;
		} else {
			BUG();
		}
#ifdef ES_AGGRESSIVE_TEST
		ext4_map_blocks_es_recheck(handle, inode, map,
					   &orig_map, flags);
#endif
		goto found;
	}

	/*
	 * 尝试看看我们是否可以在不请求新的文件系统块的情况下获得该块。
	 */
	down_read(&EXT4_I(inode)->i_data_sem);
	if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {
		retval = ext4_ext_map_blocks(handle, inode, map, 0);
	} else {
		retval = ext4_ind_map_blocks(handle, inode, map, 0);
	}
	if (retval > 0) {
		unsigned int status;

		if (unlikely(retval != map->m_len)) {
			ext4_warning(inode->i_sb,
				     "ES len assertion failed for inode "
				     "%lu: retval %d != map->m_len %d",
				     inode->i_ino, retval, map->m_len);
			WARN_ON(1);
		}

		status = map->m_flags & EXT4_MAP_UNWRITTEN ?
				EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN;
		if (!(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) &&
		    !(status & EXTENT_STATUS_WRITTEN) &&
		    ext4_es_scan_range(inode, &ext4_es_is_delayed, map->m_lblk,
				       map->m_lblk + map->m_len - 1))
			status |= EXTENT_STATUS_DELAYED;
		ret = ext4_es_insert_extent(inode, map->m_lblk,
					    map->m_len, map->m_pblk, status);
		if (ret < 0)
			retval = ret;
	}
	up_read((&EXT4_I(inode)->i_data_sem));

found:
	if (retval > 0 && map->m_flags & EXT4_MAP_MAPPED) {
		ret = check_block_validity(inode, map);
		if (ret != 0)
			return ret;
	}

	/* 如果它只是一个块查找 */
	if ((flags & EXT4_GET_BLOCKS_CREATE) == 0)
		return retval;

	/*
	 * 如果块已经分配,则返回
	 * 请注意,如果块已被预分配 ext4_ext_get_block() 返回 create = 0 且缓冲区头未映射。
	 */
	if (retval > 0 && map->m_flags & EXT4_MAP_MAPPED)
		/*
		 * 如果我们需要将范围转换为未写,我们继续并在 ext4_ext_map_blocks() 中进行实际工作
		 */
		if (!(flags & EXT4_GET_BLOCKS_CONVERT_UNWRITTEN))
			return retval;

	/*
	 * 这里我们清除 m_flags 因为在分配了一个新的范围后,它将再次设置。
	 */
	map->m_flags &= ~EXT4_MAP_FLAGS;

	/*
	 * 新块分配和/或写入未写入的范围可能会导致更新 i_data,
	 *因此我们获取 i_data_sem 的写锁,并使用 create == 1 标志调用 get_block()。
	 */
	down_write(&EXT4_I(inode)->i_data_sem);

	/*
	 * 我们需要在这里检查 EXT4,因为 migrate 可能已经改变了两者之间的 inode 类型
	 */
	if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS)) {
		retval = ext4_ext_map_blocks(handle, inode, map, flags);
	} else {
		retval = ext4_ind_map_blocks(handle, inode, map, flags);

		if (retval > 0 && map->m_flags & EXT4_MAP_NEW) {
			/*
			 * 我们分配了新的块,这将导致 i_data 的格式发生变化。 通过清除迁移标志强制迁移失败
			 */
			ext4_clear_inode_state(inode, EXT4_STATE_EXT_MIGRATE);
		}

		/*
		 延迟至今的成功块分配后更新保留块/元数据块。 
		 我们不支持非扩展文件的 fallocate。 
		 所以我们可以在这里更新预留空间。
		 */
		if ((retval > 0) &&
			(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE))
			ext4_da_update_reserve_space(inode, retval, 1);
	}

	if (retval > 0) {
		unsigned int status;

		if (unlikely(retval != map->m_len)) {
			ext4_warning(inode->i_sb,
				     "ES len assertion failed for inode "
				     "%lu: retval %d != map->m_len %d",
				     inode->i_ino, retval, map->m_len);
			WARN_ON(1);
		}

		/*
		 我们必须在将块插入到范围状态树之前将它们清零。 否则有人可以在那里查找它们并在它们真正归零之前使用它们。 
		 我们还必须在归零之前取消映射元数据,否则回写可能会用来自块设备的陈旧数据覆盖零。
		 */
		if (flags & EXT4_GET_BLOCKS_ZERO &&
		    map->m_flags & EXT4_MAP_MAPPED &&
		    map->m_flags & EXT4_MAP_NEW) {
			ret = ext4_issue_zeroout(inode, map->m_lblk,
						 map->m_pblk, map->m_len);
			if (ret) {
				retval = ret;
				goto out_sem;
			}
		}

		/*
		 * 如果extent 已经被清零,我们不需要更新extent 状态树。
		 */
		if ((flags & EXT4_GET_BLOCKS_PRE_IO) &&
		    ext4_es_lookup_extent(inode, map->m_lblk, NULL, &es)) {
			if (ext4_es_is_written(&es))
				goto out_sem;
		}
		status = map->m_flags & EXT4_MAP_UNWRITTEN ?
				EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN;
		if (!(flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE) &&
		    !(status & EXTENT_STATUS_WRITTEN) &&
		    ext4_es_scan_range(inode, &ext4_es_is_delayed, map->m_lblk,
				       map->m_lblk + map->m_len - 1))
			status |= EXTENT_STATUS_DELAYED;
		ret = ext4_es_insert_extent(inode, map->m_lblk, map->m_len,
					    map->m_pblk, status);
		if (ret < 0) {
			retval = ret;
			goto out_sem;
		}
	}

out_sem:
	up_write((&EXT4_I(inode)->i_data_sem));
	if (retval > 0 && map->m_flags & EXT4_MAP_MAPPED) {
		ret = check_block_validity(inode, map);
		if (ret != 0)
			return ret;

		/*
		 * 事务提交后内容可见的新分配块的索引节点必须在事务的有序数据列表中。
		 */
		if (map->m_flags & EXT4_MAP_NEW &&
		    !(map->m_flags & EXT4_MAP_UNWRITTEN) &&
		    !(flags & EXT4_GET_BLOCKS_ZERO) &&
		    !ext4_is_quota_file(inode) &&
		    ext4_should_order_data(inode)) {
			loff_t start_byte =
				(loff_t)map->m_lblk << inode->i_blkbits;
			loff_t length = (loff_t)map->m_len << inode->i_blkbits;

			if (flags & EXT4_GET_BLOCKS_IO_SUBMIT)
				ret = ext4_jbd2_inode_add_wait(handle, inode,
						start_byte, length);
			else
				ret = ext4_jbd2_inode_add_write(handle, inode,
						start_byte, length);
			if (ret)
				return ret;
		}
		ext4_fc_track_range(handle, inode, map->m_lblk,
			    map->m_lblk + map->m_len - 1);
	}

	if (retval < 0)
		ext_debug(inode, "failed with err %d\n", retval);
	return retval;
}


/*
 * 更新 bh->b_state 中的 EXT4_MAP_FLAGS。 
 对于附加到页面的缓冲区头,我们必须小心,因为其他人也可能在操纵 b_state。
 */
static void ext4_update_bh_state(struct buffer_head *bh, unsigned long flags)
{
	unsigned long old_state;
	unsigned long new_state;

	flags &= EXT4_MAP_FLAGS;

	/* 虚拟buffer_head  非原子设置。 */
	if (!bh->b_page) {
		bh->b_state = (bh->b_state & ~EXT4_MAP_FLAGS) | flags;
		return;
	}
	/*
	 其他人可能正在修改 b_state。 
	 这很恶心,但是一旦我们摆脱了使用 bh 作为映射信息的容器以传递到 get_block 函数/从 get_block 函数传递的信息,这种情况就会消失。
	 */
	do {
		old_state = READ_ONCE(bh->b_state);
		new_state = (old_state & ~EXT4_MAP_FLAGS) | flags;
	} while (unlikely(
		 cmpxchg(&bh->b_state, old_state, new_state) != old_state));
}


/*
 * 如果我们需要在未分配块的情况下创建未写入的范围,则在准备缓冲写入时使用获取块函数。 
 * IO 完成后,extent 将转换为写入。
 */
int ext4_get_block_unwritten(struct inode *inode, sector_t iblock,
			     struct buffer_head *bh_result, int create)
{
	ext4_debug("ext4_get_block_unwritten: inode %lu, create flag %d\n",
		   inode->i_ino, create);
	return _ext4_get_block(inode, iblock, bh_result,
			       EXT4_GET_BLOCKS_IO_CREATE_EXT);
}


/*
 * 如果 create 为零,handle 可以为 NULL
 */
struct buffer_head *ext4_getblk(handle_t *handle, struct inode *inode,
				ext4_lblk_t block, int map_flags)
{
	struct ext4_map_blocks map;
	struct buffer_head *bh;
	int create = map_flags & EXT4_GET_BLOCKS_CREATE;
	int err;

	ASSERT((EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY)
		    || handle != NULL || create == 0);

	map.m_lblk = block;
	map.m_len = 1;
	err = ext4_map_blocks(handle, inode, &map, map_flags);

	if (err == 0)
		return create ? ERR_PTR(-ENOSPC) : NULL;
	if (err < 0)
		return ERR_PTR(err);

	bh = sb_getblk(inode->i_sb, map.m_pblk);
	if (unlikely(!bh))
		return ERR_PTR(-ENOMEM);
	if (map.m_flags & EXT4_MAP_NEW) {
		ASSERT(create != 0);
		ASSERT((EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY)
			    || (handle != NULL));

		/*
		 * 既然我们并不总是记录数据,我们应该记住这是否应该总是将新缓冲区记录为元数据。 
		 * 目前,常规文件写入使用 ext4_get_block 代替,所以这不是问题。
		 */
		lock_buffer(bh);
		BUFFER_TRACE(bh, "call get_create_access");
		err = ext4_journal_get_create_access(handle, bh);
		if (unlikely(err)) {
			unlock_buffer(bh);
			goto errout;
		}
		if (!buffer_uptodate(bh)) {
			memset(bh->b_data, 0, inode->i_sb->s_blocksize);
			set_buffer_uptodate(bh);
		}
		unlock_buffer(bh);
		BUFFER_TRACE(bh, "call ext4_handle_dirty_metadata");
		err = ext4_handle_dirty_metadata(handle, inode, bh);
		if (unlikely(err))
			goto errout;
	} else
		BUFFER_TRACE(bh, "not a new buffer");
	return bh;
errout:
	brelse(bh);
	return ERR_PTR(err);
}


/*
 * 为了保持顺序,必须将孔实例化和数据写入封装在单个事务中。
  我们不能在 ext4_get_block() 和 commit_write() 之间关闭一个事务并开始一个新的事务。
  所以在 prepare_write() 开始时做 jbd2_journal_start 是正确的地方。

 * 此外,该函数可以嵌套在 ext4_writepage() 中。
  在这种情况下,我们知道 ext4_writepage() 已生成足够的缓冲区信用来完成整个页面。
  所以在这种情况下我们不会阻塞日志,这很好,因为调用者可能是 PF_MEMALLOC。

 * 偶然地,当通过配额文件写入打开事务时,可以重新进入 ext4。
  如果我们在重新进入时提交事务,可能会出现死锁——我们将持有一个配额锁,
  如果另一个线程打开一个事务并且阻塞在配额锁上,提交将永远不会完成——违反排名。

 * 所以我们所做的是依赖于 jbd2_journal_stop/journal_start 在这些情况下不会_运行提交的事实,
  因为 handle->h_ref 被提升了。我们仍然有足够的积分用于微小的配额文件写入。
 */
int do_journal_get_write_access(handle_t *handle,
				struct buffer_head *bh)
{
	int dirty = buffer_dirty(bh);
	int ret;

	if (!buffer_mapped(bh) || buffer_freed(bh))
		return 0;
	/*
	  __block_write_begin() 可能弄脏了一些缓冲区。 
	  清理脏位,因为 jbd2_journal_get_write_access() 可能会抱怨 fs 完整性问题。 
	  通过 __block_write_begin() 设置脏位在这里并不是真正的问题,
	  因为我们在释放页锁之前清除了该位,因此回写永远无法写入缓冲区。
	 */
	if (dirty)
		clear_buffer_dirty(bh);
	BUFFER_TRACE(bh, "get write access");
	ret = ext4_journal_get_write_access(handle, bh);
	if (!ret && dirty)
		ret = ext4_handle_dirty_metadata(handle, NULL, bh);
	return ret;
}


static int ext4_write_begin(struct file *file, struct address_space *mapping,
			    loff_t pos, unsigned len, unsigned flags,
			    struct page **pagep, void **fsdata)
{
	struct inode *inode = mapping->host;
	int ret, needed_blocks;
	handle_t *handle;
	int retries = 0;
	struct page *page;
	pgoff_t index;
	unsigned from, to;

	if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
		return -EIO;

	trace_ext4_write_begin(inode, pos, len, flags);
	/*
	 * 多保留一个块以添加到孤儿列表,以防我们分配块但由于某种原因写入失败
	 */
	needed_blocks = ext4_writepage_trans_blocks(inode) + 1;
	index = pos >> PAGE_SHIFT;
	from = pos & (PAGE_SIZE - 1);
	to = from + len;

	if (ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA)) {
		ret = ext4_try_to_write_inline_data(mapping, inode, pos, len,
						    flags, pagep);
		if (ret < 0)
			return ret;
		if (ret == 1)
			return 0;
	}

	/*
	 如果系统由于内存压力而抖动,或者页面正在被写回,grab_cache_page_write_begin() 可能需要很长时间。 
	 所以在我们开始事务句柄之前先抓住它。 这也允许我们在不使用 GFP_NOFS 的情况下分配页面(如果需要)。
	 */
retry_grab:
	page = grab_cache_page_write_begin(mapping, index, flags);
	if (!page)
		return -ENOMEM;
	unlock_page(page);

retry_journal:
	handle = ext4_journal_start(inode, EXT4_HT_WRITE_PAGE, needed_blocks);
	if (IS_ERR(handle)) {
		put_page(page);
		return PTR_ERR(handle);
	}

	lock_page(page);
	if (page->mapping != mapping) {
		/* 页面从我们下面被截断 */
		unlock_page(page);
		put_page(page);
		ext4_journal_stop(handle);
		goto retry_grab;
	}
	/* 如果在页面解锁时开始写回 */
	wait_for_stable_page(page);

#ifdef CONFIG_FS_ENCRYPTION
	if (ext4_should_dioread_nolock(inode))
		ret = ext4_block_write_begin(page, pos, len,
					     ext4_get_block_unwritten);
	else
		ret = ext4_block_write_begin(page, pos, len,
					     ext4_get_block);
#else
	if (ext4_should_dioread_nolock(inode))
		ret = __block_write_begin(page, pos, len,
					  ext4_get_block_unwritten);
	else
		ret = __block_write_begin(page, pos, len, ext4_get_block);
#endif
	if (!ret && ext4_should_journal_data(inode)) {
		ret = ext4_walk_page_buffers(handle, page_buffers(page),
					     from, to, NULL,
					     do_journal_get_write_access);
	}

	if (ret) {
		bool extended = (pos + len > inode->i_size) &&
				!ext4_verity_in_progress(inode);

		unlock_page(page);
		/*
		 __block_write_begin 可能在 i_size 之外实例化了几个块,再把这些剪掉。 
		 不需要 i_size_read 因为我们持有 i_mutex。
		 将 inode 添加到孤立列表,以防我们在截断完成之前崩溃
		 */
		if (extended && ext4_can_truncate(inode))
			ext4_orphan_add(handle, inode);

		ext4_journal_stop(handle);
		if (extended) {
			ext4_truncate_failed_write(inode);
			/*
			 * 如果早期截断失败,inode 可能仍然在孤儿列表中; 
			 * 在这种情况下,我们需要确保从孤立列表中删除 inode。
			 */
			if (inode->i_nlink)
				ext4_orphan_del(NULL, inode);
		}

		if (ret == -ENOSPC &&
		    ext4_should_retry_alloc(inode->i_sb, &retries))
			goto retry_journal;
		put_page(page);
		return ret;
	}
	*pagep = page;
	return ret;
}


/*
 * 我们需要获取 generic_commit_write 给我们文件的新 inode 大小可以为 NULL - 例如,当从 page_symlink() 调用时。
 * ext4 从不在 inode->i_mapping->private_list 上放置缓冲区。 元数据缓冲区在内部进行管理。
 */
static int ext4_write_end(struct file *file,
			  struct address_space *mapping,
			  loff_t pos, unsigned len, unsigned copied,
			  struct page *page, void *fsdata)
{
	handle_t *handle = ext4_journal_current_handle();
	struct inode *inode = mapping->host;
	loff_t old_size = inode->i_size;
	int ret = 0, ret2;
	int i_size_changed = 0;
	int inline_data = ext4_has_inline_data(inode);
	bool verity = ext4_verity_in_progress(inode);

	trace_ext4_write_end(inode, pos, len, copied);
	if (inline_data) {
		ret = ext4_write_inline_data_end(inode, pos, len,
						 copied, page);
		if (ret < 0) {
			unlock_page(page);
			put_page(page);
			goto errout;
		}
		copied = ret;
	} else
		copied = block_write_end(file, mapping, pos,
					 len, copied, page, fsdata);
	/*
	 * 在保持页面锁定的同时更新 i_size 很重要:否则页面写出可能会进入并且超过 i_size 为零。
	 * 如果 FS_IOC_ENABLE_VERITY 在此 inode 上运行,则 Merkle 树块将被写入超过 EOF,因此跳过 i_size 更新。
	 */
	if (!verity)
		i_size_changed = ext4_update_inode_size(inode, pos + copied);
	unlock_page(page);
	put_page(page);

	if (old_size < pos && !verity)
		pagecache_isize_extended(inode, old_size, pos);
	/*
	 不要在页面锁定下标记 inode dirty。 
	 首先,它不必要地延长了页锁的保持时间。 
	 其次,它强制对日志文件系统进行页锁和事务启动的锁排序。
	 */
	if (i_size_changed || inline_data)
		ret = ext4_mark_inode_dirty(handle, inode);

	if (pos + len > inode->i_size && !verity && ext4_can_truncate(inode))
		/* 
		 如果我们分配了更多的块并减少了复制,我们将在 inode->i_size 之外分配块。所以截断它们
		 */
		ext4_orphan_add(handle, inode);
errout:
	ret2 = ext4_journal_stop(handle);
	if (!ret)
		ret = ret2;

	if (pos + len > inode->i_size && !verity) {
		ext4_truncate_failed_write(inode);
		/*
		 * 如果早期截断失败,inode 可能仍然在孤儿列表中; 
		 * 在这种情况下,我们需要确保从孤立列表中删除 inode。
		 */
		if (inode->i_nlink)
			ext4_orphan_del(NULL, inode);
	}

	return ret ? ret : copied;
}


/*
 * 为单个集群预留空间
 */
static int ext4_da_reserve_space(struct inode *inode)
{
	struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
	struct ext4_inode_info *ei = EXT4_I(inode);
	int ret;

	/*
	 * 我们将在写出时收取元数据配额; 这使我们免于高估元数据,尽管我们最终可能会略有偏差。 这里我们只保留数据。
	 */
	ret = dquot_reserve_block(inode, EXT4_C2B(sbi, 1));
	if (ret)
		return ret;

	spin_lock(&ei->i_block_reservation_lock);
	if (ext4_claim_free_clusters(sbi, 1, 0)) {
		spin_unlock(&ei->i_block_reservation_lock);
		dquot_release_reservation_block(inode, EXT4_C2B(sbi, 1));
		return -ENOSPC;
	}
	ei->i_reserved_data_blocks++;
	trace_ext4_da_reserve_space(inode);
	spin_unlock(&ei->i_block_reservation_lock);

	return 0;       /* success */
}

void ext4_da_release_space(struct inode *inode, int to_free)
{
	struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
	struct ext4_inode_info *ei = EXT4_I(inode);

	if (!to_free)
		return;		/* Nothing to release, exit */

	spin_lock(&EXT4_I(inode)->i_block_reservation_lock);

	trace_ext4_da_release_space(inode, to_free);
	if (unlikely(to_free > ei->i_reserved_data_blocks)) {
		/*
		 * 如果没有足够的保留块,那么计数器就会在某处搞砸。 
		 * 由于这个函数是从invalidate页面调用的,所以不做任何操作就返回是无害的。
		 */
		ext4_warning(inode->i_sb, "ext4_da_release_space: "
			 "ino %lu, to_free %d with only %d reserved "
			 "data blocks", inode->i_ino, to_free,
			 ei->i_reserved_data_blocks);
		WARN_ON(1);
		to_free = ei->i_reserved_data_blocks;
	}
	ei->i_reserved_data_blocks -= to_free;

	/* 更新 fs dirty数据块计数器 */
	percpu_counter_sub(&sbi->s_dirtyclusters_counter, to_free);

	spin_unlock(&EXT4_I(inode)->i_block_reservation_lock);

	dquot_release_reservation_block(inode, EXT4_C2B(sbi, to_free));
}

/*
 * 延迟分配
 */

struct mpage_da_data {
	struct inode *inode;
	struct writeback_control *wbc;

	pgoff_t first_page;	/* 要写入的第一页 */
	pgoff_t next_page;	/* 当前要检查的页面 */
	pgoff_t last_page;	/* 最后一页检查 */
	/*
	 * 映射范围 - 这可以在 first_page 之后,因为它可以被完全映射。 我们有点滥用 m_flags 来存储范围是 delalloc 还是未写入。
	 */
	struct ext4_map_blocks map;
	struct ext4_io_submit io_submit;	/* IO提交数据 */
	unsigned int do_map:1;
	unsigned int scanned_until_end:1;
};


/*
 * ext4_insert_delayed_block - 将延迟块添加到范围状态树,增加保留的集群/块计数或在需要时进行挂起的保留
 * @inode - 包含新添加块的文件
 * @lblk - 要添加的逻辑块
 * 成功返回 0,失败返回错误代码。
 */
static int ext4_insert_delayed_block(struct inode *inode, ext4_lblk_t lblk)
{
	struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
	int ret;
	bool allocated = false;

	/*
	 如果包含 lblk 的集群与 bigalloc 文件系统中的延迟、写入或未写入范围共享,则它已经被考虑在内,不需要保留。
     如果集群与写入或未写入的范围共享并且还没有,则必须为该集群进行挂起预留。 
	 如果系统处于内存压力下,可以从范围状态树中清除已写入和未写入的范围,
	 因此如果对范围状态树的搜索没有匹配,则有必要检查范围树。
	 */
	if (sbi->s_cluster_ratio == 1) {
		ret = ext4_da_reserve_space(inode);
		if (ret != 0)   /* ENOSPC */
			goto errout;
	} else {   /* bigalloc */
		if (!ext4_es_scan_clu(inode, &ext4_es_is_delonly, lblk)) {
			if (!ext4_es_scan_clu(inode,
					      &ext4_es_is_mapped, lblk)) {
				ret = ext4_clu_mapped(inode,
						      EXT4_B2C(sbi, lblk));
				if (ret < 0)
					goto errout;
				if (ret == 0) {
					ret = ext4_da_reserve_space(inode);
					if (ret != 0)   /* ENOSPC */
						goto errout;
				} else {
					allocated = true;
				}
			} else {
				allocated = true;
			}
		}
	}

	ret = ext4_es_insert_delayed_block(inode, lblk, allocated);

errout:
	return ret;
}

/*
 * 该函数是从 ext4_map_blocks 一开始就抓取代码,但假设调用者来自延迟写入时间。 
 * 该函数在 i_data_sem 的保护下查找请求的块并设置缓冲区延迟位。
 */
static int ext4_da_map_blocks(struct inode *inode, sector_t iblock,
			      struct ext4_map_blocks *map,
			      struct buffer_head *bh)
{
	struct extent_status es;
	int retval;
	sector_t invalid_block = ~((sector_t) 0xffff);
#ifdef ES_AGGRESSIVE_TEST
	struct ext4_map_blocks orig_map;

	memcpy(&orig_map, map, sizeof(*map));
#endif

	if (invalid_block < ext4_blocks_count(EXT4_SB(inode->i_sb)->s_es))
		invalid_block = ~0;

	map->m_flags = 0;
	ext_debug(inode, "max_blocks %u, logical block %lu\n", map->m_len,
		  (unsigned long) map->m_lblk);

	/* 首先查找范围状态树 */
	if (ext4_es_lookup_extent(inode, iblock, NULL, &es)) {
		if (ext4_es_is_hole(&es)) {
			retval = 0;
			down_read(&EXT4_I(inode)->i_data_sem);
			goto add_delayed;
		}

		/*
		 * 延迟范围可以通过 fallocate 分配。 所以我们需要检查一下。
		 */
		if (ext4_es_is_delayed(&es) && !ext4_es_is_unwritten(&es)) {
			map_bh(bh, inode->i_sb, invalid_block);
			set_buffer_new(bh);
			set_buffer_delay(bh);
			return 0;
		}

		map->m_pblk = ext4_es_pblock(&es) + iblock - es.es_lblk;
		retval = es.es_len - (iblock - es.es_lblk);
		if (retval > map->m_len)
			retval = map->m_len;
		map->m_len = retval;
		if (ext4_es_is_written(&es))
			map->m_flags |= EXT4_MAP_MAPPED;
		else if (ext4_es_is_unwritten(&es))
			map->m_flags |= EXT4_MAP_UNWRITTEN;
		else
			BUG();

#ifdef ES_AGGRESSIVE_TEST
		ext4_map_blocks_es_recheck(NULL, inode, map, &orig_map, 0);
#endif
		return retval;
	}

	/*
	 * 尝试看看我们是否可以在不请求新的文件系统块的情况下获得该块。
	 */
	down_read(&EXT4_I(inode)->i_data_sem);
	if (ext4_has_inline_data(inode))
		retval = 0;
	else if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
		retval = ext4_ext_map_blocks(NULL, inode, map, 0);
	else
		retval = ext4_ind_map_blocks(NULL, inode, map, 0);

add_delayed:
	if (retval == 0) {
		int ret;

		/*
		 * XXX: __block_prepare_write() 取消映射传递的块
		 */

		ret = ext4_insert_delayed_block(inode, map->m_lblk);
		if (ret != 0) {
			retval = ret;
			goto out_unlock;
		}

		map_bh(bh, inode->i_sb, invalid_block);
		set_buffer_new(bh);
		set_buffer_delay(bh);
	} else if (retval > 0) {
		int ret;
		unsigned int status;

		if (unlikely(retval != map->m_len)) {
			ext4_warning(inode->i_sb,
				     "ES len assertion failed for inode "
				     "%lu: retval %d != map->m_len %d",
				     inode->i_ino, retval, map->m_len);
			WARN_ON(1);
		}

		status = map->m_flags & EXT4_MAP_UNWRITTEN ?
				EXTENT_STATUS_UNWRITTEN : EXTENT_STATUS_WRITTEN;
		ret = ext4_es_insert_extent(inode, map->m_lblk, map->m_len,
					    map->m_pblk, status);
		if (ret != 0)
			retval = ret;
	}

out_unlock:
	up_read((&EXT4_I(inode)->i_data_sem));

	return retval;
}

/*
 * 这是一个特殊的 get_block_t 回调,由 ext4_da_write_begin() 使用。 它将返回映射块或为单个块保留空间。
 * 对于延迟的buffer_head,我们设置了BH_Mapped、BH_New、BH_Delay。 我们也正确初始化了 b_blocknr = -1 和 b_bdev
 * 对于未写入的 buffer_head,我们设置了 BH_Mapped、BH_New、BH_Unwritten。 
   我们也有 b_blocknr = physicalblock mapping unwritten extent 和 b_bdev 正确初始化。
 */
int ext4_da_get_block_prep(struct inode *inode, sector_t iblock,
			   struct buffer_head *bh, int create)
{
	struct ext4_map_blocks map;
	int ret = 0;

	BUG_ON(create == 0);
	BUG_ON(bh->b_size != inode->i_sb->s_blocksize);

	map.m_lblk = iblock;
	map.m_len = 1;

	/*
	 * 首先,我们需要知道块是否已分配已经预分配的块未映射但应与已分配的块一样对待。
	 */
	ret = ext4_da_map_blocks(inode, iblock, &map, bh);
	if (ret <= 0)
		return ret;

	map_bh(bh, inode->i_sb, map.m_pblk);
	ext4_update_bh_state(bh, map.m_flags);

	if (buffer_unwritten(bh)) {
		/* 
		 延迟写入未写入的 bh 应标记为新的并已映射。 
		 Mapped 确保我们不会在写入相同偏移量时多次执行 get_block,而 new 确保我们对部分写入执行正确的清零。
		 */
		set_buffer_new(bh);
		set_buffer_mapped(bh);
	}
	return 0;
}

未完待续……

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-12-14 16:22:28  更:2021-12-14 16:23:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 4:34:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码