Linux内核文件系统12

2021SC@SDUSC

inode.c(3)


/*
 * 强制为给定的 inode 分配所有延迟分配块。
 */
int ext4_alloc_da_blocks(struct inode *inode)
{
	trace_ext4_alloc_da_blocks(inode);

	if (!EXT4_I(inode)->i_reserved_data_blocks)
		return 0;

	/*
	 * 我们现在做一些简单的事情。 filemap_flush() 也将开始触发数据块的写入,严格来说这不是必需的(对于 notebook_mode 的用户来说,甚至是不可取的)。但是,否则需要在以下位置复制代码路径:
	 * ext4_writepages() ->
	 * write_cache_pages() --->(通过传入的回调函数)
	 * __mpage_da_writepage() -->
	 * mpage_add_bh_to_extent()
	 * mpage_da_map_blocks()
	 *
	 * 问题在于,位于 mm/page-writeback.c 中的 write_cache_pages() 将页面标记为干净以准备进行 I/O,如果我们根本不打算进行 I/O,则这是不可取的。
	 *
	 * 我们可以调用 write_cache_pages(),然后通过调用 redirty_page_for_writepage() 重脏所有页面,但这在极端情况下会很丑陋。
	   因此,我们需要复制上述函数中的部分代码,简化它们,因为我们实际上并不打算写出页面,而是只收集连续的逻辑块范围,调用多块分配器,然后更新具有块分配的缓冲区头。
	 *
	 * 现在,我们将通过调用 filemap_flush() 来作弊,它会映射块并启动 I/O,但实际上并不等待 I/O 完成。
	 */
	return filemap_flush(inode->i_mapping);
}

/*
 * bmap() 很特别。 lilo 等应用程序和交换器使用它来查找特定数据的磁盘块。
  *
  * 当然,如果相关块仍在日志中,这很危险。 
    如果有人在 ext4 数据日志文件系统上创建一个交换文件并启用交换,
	那么当交换到该交换文件的数据突然被先前写出到日志并等待内核写回的原始零覆盖时,他们可能会感到非常震惊 缓冲区缓存。
  *
  * 所以,如果我们在一个修改过的数据日志文件上看到任何 bmap 调用,请采取额外的步骤来刷新可能在缓存中的任何块。
 */
static sector_t ext4_bmap(struct address_space *mapping, sector_t block)
{
	struct inode *inode = mapping->host;
	journal_t *journal;
	int err;

	/*
	 * 我们可以通过 FIBMAP ioctl 获取内联文件
	 */
	if (ext4_has_inline_data(inode))
		return 0;

	if (mapping_tagged(mapping, PAGECACHE_TAG_DIRTY) &&
			test_opt(inode->i_sb, DELALLOC)) {
		/*
		 * 使用 delalloc 我们想要同步文件,以便我们可以确保为文件分配块
		 */
		filemap_write_and_wait(mapping);
	}

	if (EXT4_JOURNAL(inode) &&
	    ext4_test_inode_state(inode, EXT4_STATE_JDATA)) {
		/*
		 * 这是一个真正重量级的方法,但预计在脏文件上使用 bmap 是极其罕见的:只有当我们在新制作的文件上运行 lilo 或 swapon 时,我们才期望这种情况发生。
		 *
		 *(bmap 需要 CAP_SYS_RAWIO 所以这并不代表非特权用户 DOS 攻击——如果凡人用户可以随意触发这个路径,我们就会遇到麻烦。)
		 *
		 *注意。 EXT4_STATE_JDATA 未在常规文件以外的文件上设置。 如果有人想要对目录或符号链接进行 bmap 并因为缓冲区尚未刷新到磁盘而感到困惑,那么他们应该得到他们所得到的一切。
		 */

		ext4_clear_inode_state(inode, EXT4_STATE_JDATA);
		journal = EXT4_JOURNAL(inode);
		jbd2_journal_lock_updates(journal);
		err = jbd2_journal_flush(journal, 0);
		jbd2_journal_unlock_updates(journal);

		if (err)
			return 0;
	}

	return iomap_bmap(mapping, block, &ext4_iomap_ops);
}

static int ext4_readpage(struct file *file, struct page *page)
{
	int ret = -EAGAIN;
	struct inode *inode = page->mapping->host;

	trace_ext4_readpage(page);

	if (ext4_has_inline_data(inode))
		ret = ext4_readpage_inline(inode, page);

	if (ret == -EAGAIN)
		return ext4_mpage_readpages(inode, NULL, page);

	return ret;
}

static void ext4_readahead(struct readahead_control *rac)
{
	struct inode *inode = rac->mapping->host;

	/* 如果文件有内联数据,则无需预读。 */
	if (ext4_has_inline_data(inode))
		return;

	ext4_mpage_readpages(inode, rac, NULL);
}

static void ext4_invalidatepage(struct page *page, unsigned int offset,
				unsigned int length)
{
	trace_ext4_invalidatepage(page, offset, length);

	/* 使用此函数时,数据缓冲区不会发生日志记录 */
	WARN_ON(page_has_buffers(page) && buffer_jbd(page_buffers(page)));

	block_invalidatepage(page, offset, length);
}

static int __ext4_journalled_invalidatepage(struct page *page,
					    unsigned int offset,
					    unsigned int length)
{
	journal_t *journal = EXT4_JOURNAL(page->mapping->host);

	trace_ext4_journalled_invalidatepage(page, offset, length);

	/*
	 * 如果它是一个完整的截断,我们只是忘记挂起的脏
	 */
	if (offset == 0 && length == PAGE_SIZE)
		ClearPageChecked(page);

	return jbd2_journal_invalidatepage(journal, page, offset, length);
}

/* aops的包装... */
static void ext4_journalled_invalidatepage(struct page *page,
					   unsigned int offset,
					   unsigned int length)
{
	WARN_ON(__ext4_journalled_invalidatepage(page, offset, length) < 0);
}

static int ext4_releasepage(struct page *page, gfp_t wait)
{
	journal_t *journal = EXT4_JOURNAL(page->mapping->host);

	trace_ext4_releasepage(page);

	/* 页面有脏日志数据 -> 无法释放 */
	if (PageChecked(page))
		return 0;
	if (journal)
		return jbd2_journal_try_to_free_buffers(journal, page);
	else
		return try_to_free_buffers(page);
}

static bool ext4_inode_datasync_dirty(struct inode *inode)
{
	journal_t *journal = EXT4_SB(inode->i_sb)->s_journal;

	if (journal) {
		if (jbd2_transaction_committed(journal,
			EXT4_I(inode)->i_datasync_tid))
			return false;
		if (test_opt2(inode->i_sb, JOURNAL_FAST_COMMIT))
			return !list_empty(&EXT4_I(inode)->i_fc_list);
		return true;
	}

	/* 任何要写入的元数据缓冲区? */
	if (!list_empty(&inode->i_mapping->private_list))
		return true;
	return inode->i_state & I_DIRTY_DATASYNC;
}

static void ext4_set_iomap(struct inode *inode, struct iomap *iomap,
			   struct ext4_map_blocks *map, loff_t offset,
			   loff_t length)
{
	u8 blkbits = inode->i_blkbits;

	/*
	 * 跨越 EOF 的写入可能会在完成时触发 I/O 大小更新,因此出于 O_DSYNC 的目的考虑它们是脏的,即使没有其他元数据更改正在进行或未决。
	 */
	iomap->flags = 0;
	if (ext4_inode_datasync_dirty(inode) ||
	    offset + length > i_size_read(inode))
		iomap->flags |= IOMAP_F_DIRTY;

	if (map->m_flags & EXT4_MAP_NEW)
		iomap->flags |= IOMAP_F_NEW;

	iomap->bdev = inode->i_sb->s_bdev;
	iomap->dax_dev = EXT4_SB(inode->i_sb)->s_daxdev;
	iomap->offset = (u64) map->m_lblk << blkbits;
	iomap->length = (u64) map->m_len << blkbits;

	if ((map->m_flags & EXT4_MAP_MAPPED) &&
	    !ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
		iomap->flags |= IOMAP_F_MERGED;

	/*
	 * 为直接 I/O 写入传递给 ext4_map_blocks() 的标志可能导致 m_flags 设置了 EXT4_MAP_MAPPED 和 EXT4_MAP_UNWRITTEN 位。 
	 为了在 ->end_io() 处理程序中将任何已分配的未写入范围正确转换为写入范围,我们需要确保适当设置 iomap->type。 
	 因此,我们需要先检查 EXT4_MAP_UNWRITTEN 位是否已设置的原因。
	 */
	if (map->m_flags & EXT4_MAP_UNWRITTEN) {
		iomap->type = IOMAP_UNWRITTEN;
		iomap->addr = (u64) map->m_pblk << blkbits;
	} else if (map->m_flags & EXT4_MAP_MAPPED) {
		iomap->type = IOMAP_MAPPED;
		iomap->addr = (u64) map->m_pblk << blkbits;
	} else {
		iomap->type = IOMAP_HOLE;
		iomap->addr = IOMAP_NULL_ADDR;
	}
}

static int ext4_iomap_alloc(struct inode *inode, struct ext4_map_blocks *map,
			    unsigned int flags)
{
	handle_t *handle;
	u8 blkbits = inode->i_blkbits;
	int ret, dio_credits, m_flags = 0, retries = 0;

	/*
	 * 将映射请求修剪为我们可以立即映射以进行直接 I/O 的最大值。
	 */
	if (map->m_len > DIO_MAX_BLOCKS)
		map->m_len = DIO_MAX_BLOCKS;
	dio_credits = ext4_chunk_trans_blocks(inode, map->m_len);

retry:
	/*
	 * 要么我们分配块,然后就不会得到一个不成文的范围,所以在这种情况下,我们保留了足够的信用。 
	   或者,块已经分配并且未写入。 在这种情况下,范围转换也适用于信用。
	 */
	handle = ext4_journal_start(inode, EXT4_HT_MAP_BLOCKS, dio_credits);
	if (IS_ERR(handle))
		return PTR_ERR(handle);

	/*
	 * DAX 和直接 I/O 是 IOMAP_WRITE 当前支持的仅有的两个操作。
	 */
	WARN_ON(!IS_DAX(inode) && !(flags & IOMAP_DIRECT));
	if (IS_DAX(inode))
		m_flags = EXT4_GET_BLOCKS_CREATE_ZERO;
	/*
	 * 我们在这里使用 i_size 而不是 i_disksize,因为 delalloc 写回可以在 I/O 期间的任何时候完成,随后将 i_disksize 推送到 i_size。 
	   这可能超出了直接 I/O 发生的范围,从而将分配的块暴露给直接 I/O 读取。
	 */
	else if (((loff_t)map->m_lblk << blkbits) >= i_size_read(inode))
		m_flags = EXT4_GET_BLOCKS_CREATE;
	else if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
		m_flags = EXT4_GET_BLOCKS_IO_CREATE_EXT;

	ret = ext4_map_blocks(handle, inode, map, m_flags);

	/*
	 * 我们无法填充基于间接树的 inode 中的漏洞,因为这可能会在崩溃的情况下暴露过时的数据。 使用魔术错误代码回退到缓冲 I/O。
	 */
	if (!m_flags && !ret)
		ret = -ENOTBLK;

	ext4_journal_stop(handle);
	if (ret == -ENOSPC && ext4_should_retry_alloc(inode->i_sb, &retries))
		goto retry;

	return ret;
}


static int ext4_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
		unsigned flags, struct iomap *iomap, struct iomap *srcmap)
{
	int ret;
	struct ext4_map_blocks map;
	u8 blkbits = inode->i_blkbits;

	if ((offset >> blkbits) > EXT4_MAX_LOGICAL_BLOCK)
		return -EINVAL;

	if (WARN_ON_ONCE(ext4_has_inline_data(inode)))
		return -ERANGE;

	/*
	 * 分别计算第一个和最后一个逻辑块。
	 */
	map.m_lblk = offset >> blkbits;
	map.m_len = min_t(loff_t, (offset + length - 1) >> blkbits,
			  EXT4_MAX_LOGICAL_BLOCK) - map.m_lblk + 1;

	if (flags & IOMAP_WRITE) {
		/*
		 * 我们在这里检查是否已经分配了块,那么我们就不需要启动journal txn,我们可以直接返回映射信息。 
		   这可以提高性能,尤其是在多线程覆盖请求中。
		 */
		if (offset + length <= i_size_read(inode)) {
			ret = ext4_map_blocks(NULL, inode, &map, 0);
			if (ret > 0 && (map.m_flags & EXT4_MAP_MAPPED))
				goto out;
		}
		ret = ext4_iomap_alloc(inode, &map, flags);
	} else {
		ret = ext4_map_blocks(NULL, inode, &map, 0);
	}

	if (ret < 0)
		return ret;
out:
	ext4_set_iomap(inode, iomap, &map, offset, length);

	return 0;
}

static int ext4_iomap_overwrite_begin(struct inode *inode, loff_t offset,
		loff_t length, unsigned flags, struct iomap *iomap,
		struct iomap *srcmap)
{
	int ret;

	/*
	 * Even for writes we don't need to allocate blocks, so just pretend
	 * we are reading to save overhead of starting a transaction.
	 */
	flags &= ~IOMAP_WRITE;
	ret = ext4_iomap_begin(inode, offset, length, flags, iomap, srcmap);
	WARN_ON_ONCE(iomap->type != IOMAP_MAPPED);
	return ret;
}

static int ext4_iomap_end(struct inode *inode, loff_t offset, loff_t length,
			  ssize_t written, unsigned flags, struct iomap *iomap)
{
	/*
	 * 检查是否在将数据写入分配块时发生错误。 如果是,则返回魔术错误代码,以便我们回退到缓冲 I/O 并尝试完成 I/O 的其余部分。 
	   为准备直接 I/O 可能已分配的任何块都将在缓冲 I/O 期间重用。
	 */
	if (flags & (IOMAP_WRITE | IOMAP_DIRECT) && written == 0)
		return -ENOTBLK;

	return 0;
}

const struct iomap_ops ext4_iomap_ops = {
	.iomap_begin		= ext4_iomap_begin,
	.iomap_end		= ext4_iomap_end,
};

const struct iomap_ops ext4_iomap_overwrite_ops = {
	.iomap_begin		= ext4_iomap_overwrite_begin,
	.iomap_end		= ext4_iomap_end,
};

static bool ext4_iomap_is_delalloc(struct inode *inode,
				   struct ext4_map_blocks *map)
{
	struct extent_status es;
	ext4_lblk_t offset = 0, end = map->m_lblk + map->m_len - 1;

	ext4_es_find_extent_range(inode, &ext4_es_is_delayed,
				  map->m_lblk, end, &es);

	if (!es.es_len || es.es_lblk > end)
		return false;

	if (es.es_lblk > map->m_lblk) {
		map->m_len = es.es_lblk - map->m_lblk;
		return false;
	}

	offset = map->m_lblk - es.es_lblk;
	map->m_len = es.es_len - offset;

	return true;
}

static int ext4_iomap_begin_report(struct inode *inode, loff_t offset,
				   loff_t length, unsigned int flags,
				   struct iomap *iomap, struct iomap *srcmap)
{
	int ret;
	bool delalloc = false;
	struct ext4_map_blocks map;
	u8 blkbits = inode->i_blkbits;

	if ((offset >> blkbits) > EXT4_MAX_LOGICAL_BLOCK)
		return -EINVAL;

	if (ext4_has_inline_data(inode)) {
		ret = ext4_inline_data_iomap(inode, iomap);
		if (ret != -EAGAIN) {
			if (ret == 0 && offset >= iomap->length)
				ret = -ENOENT;
			return ret;
		}
	}

	/*
	 * 分别计算第一个和最后一个逻辑块。
	 */
	map.m_lblk = offset >> blkbits;
	map.m_len = min_t(loff_t, (offset + length - 1) >> blkbits,
			  EXT4_MAX_LOGICAL_BLOCK) - map.m_lblk + 1;

	/*
	 * Fiemap 调用者可能会调用超出 s_bitmap_maxbytes 的偏移量。 
	   所以在这里自己处理它而不是查询 ext4_map_blocks()。
	   由于 ext4_map_blocks() 会发出警告并返回 -EIO 错误。
	 */
	if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) {
		struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);

		if (offset >= sbi->s_bitmap_maxbytes) {
			map.m_flags = 0;
			goto set_iomap;
		}
	}

	ret = ext4_map_blocks(NULL, inode, &map, 0);
	if (ret < 0)
		return ret;
	if (ret == 0)
		delalloc = ext4_iomap_is_delalloc(inode, &map);

set_iomap:
	ext4_set_iomap(inode, iomap, &map, offset, length);
	if (delalloc && iomap->type == IOMAP_HOLE)
		iomap->type = IOMAP_DELALLOC;

	return 0;
}

const struct iomap_ops ext4_iomap_report_ops = {
	.iomap_begin = ext4_iomap_begin_report,
};


/*
 * 可以从 ext4 的日志活动中完全异步地将页面标记为dirty。 
   通过filemap_sync_pte()、try_to_unmap_one()等。
   这里我们不能做太多,因为->set_page_dirty是在VFS锁下调用的。 该页面不一定被锁定。
 * 我们不能只是弄脏页面而让附加的缓冲区保持干净,因为缓冲区的脏状态是“确定的”。 
   我们不能仅仅将缓冲区设置为dirty 或jbddirty,因为所有日志代码都会爆炸。
 * 所以我们要做的是将页面标记为“pending dirty”,下次调用 writepage 时,将其适当地传播到缓冲区中。
 */
static int ext4_journalled_set_page_dirty(struct page *page)
{
	SetPageChecked(page);
	return __set_page_dirty_nobuffers(page);
}

static int ext4_set_page_dirty(struct page *page)
{
	WARN_ON_ONCE(!PageLocked(page) && !PageDirty(page));
	WARN_ON_ONCE(!page_has_buffers(page));
	return __set_page_dirty_buffers(page);
}

static int ext4_iomap_swap_activate(struct swap_info_struct *sis,
				    struct file *file, sector_t *span)
{
	return iomap_swapfile_activate(sis, file, span,
				       &ext4_iomap_report_ops);
}


/*
 * ext4_block_zero_page_range() 将从文件偏移量“from”开始的长度为“length”的映射归零。 
   要归零的范围必须包含在一个块中。 如果指定的范围超过块的末尾,它将被缩短到对应于“from”的块的末尾
 */
static int ext4_block_zero_page_range(handle_t *handle,
		struct address_space *mapping, loff_t from, loff_t length)
{
	struct inode *inode = mapping->host;
	unsigned offset = from & (PAGE_SIZE-1);
	unsigned blocksize = inode->i_sb->s_blocksize;
	unsigned max = blocksize - (offset & (blocksize - 1));

	/*
	 * 正确的长度,如果它不在“from”和块的结尾之间
	 */
	if (length > max || length < 0)
		length = max;

	if (IS_DAX(inode)) {
		return iomap_zero_range(inode, from, length, NULL,
					&ext4_iomap_ops);
	}
	return __ext4_block_zero_page_range(handle, mapping, from, length);
}

/*
 * ext4_block_truncate_page() 将文件偏移量“from”到与“from”对应的块末尾的映射清零。
 * 这在截断期间是必需的。 我们需要将该块的尾端物理归零,以便在文件以后增长时它不会产生旧数据。
 */
static int ext4_block_truncate_page(handle_t *handle,
		struct address_space *mapping, loff_t from)
{
	unsigned offset = from & (PAGE_SIZE-1);
	unsigned length;
	unsigned blocksize;
	struct inode *inode = mapping->host;

	/* 如果我们在孤立列表处理期间处理加密的 inode */
	if (IS_ENCRYPTED(inode) && !fscrypt_has_encryption_key(inode))
		return 0;

	blocksize = inode->i_sb->s_blocksize;
	length = blocksize - (offset & (blocksize - 1));

	return ext4_block_zero_page_range(handle, mapping, from, length);
}

int ext4_zero_partial_blocks(handle_t *handle, struct inode *inode,
			     loff_t lstart, loff_t length)
{
	struct super_block *sb = inode->i_sb;
	struct address_space *mapping = inode->i_mapping;
	unsigned partial_start, partial_end;
	ext4_fsblk_t start, end;
	loff_t byte_end = (lstart + length - 1);
	int err = 0;

	partial_start = lstart & (sb->s_blocksize - 1);
	partial_end = byte_end & (sb->s_blocksize - 1);

	start = lstart >> sb->s_blocksize_bits;
	end = byte_end >> sb->s_blocksize_bits;

	/* 处理单个块内的部分零 */
	if (start == end &&
	    (partial_start || (partial_end != sb->s_blocksize - 1))) {
		err = ext4_block_zero_page_range(handle, mapping,
						 lstart, length);
		return err;
	}
	/* 在范围开始处处理部分零输出 */
	if (partial_start) {
		err = ext4_block_zero_page_range(handle, mapping,
						 lstart, sb->s_blocksize);
		if (err)
			return err;
	}
	/* 在范围末尾处理部分零输出 */
	if (partial_end != sb->s_blocksize - 1)
		err = ext4_block_zero_page_range(handle, mapping,
						 byte_end - partial_end,
						 partial_end + 1);
	return err;
}

int ext4_can_truncate(struct inode *inode)
{
	if (S_ISREG(inode->i_mode))
		return 1;
	if (S_ISDIR(inode->i_mode))
		return 1;
	if (S_ISLNK(inode->i_mode))
		return !ext4_inode_is_fast_symlink(inode);
	return 0;
}

/*
 * 我们必须确保 i_disksize 在由于打孔或零范围而截断页面缓存之前得到正确更新。 
   否则 i_disksize 更新可能会丢失,因为它可能已被推迟到提交回写,但在我们截断页面缓存后这将永远不会发生。
 */
int ext4_update_disksize_before_punch(struct inode *inode, loff_t offset,
				      loff_t len)
{
	handle_t *handle;
	int ret;

	loff_t size = i_size_read(inode);

	WARN_ON(!inode_is_locked(inode));
	if (offset > size || offset + len < size)
		return 0;

	if (EXT4_I(inode)->i_disksize >= size)
		return 0;

	handle = ext4_journal_start(inode, EXT4_HT_MISC, 1);
	if (IS_ERR(handle))
		return PTR_ERR(handle);
	ext4_update_i_disksize(inode, size);
	ret = ext4_mark_inode_dirty(handle, inode);
	ext4_journal_stop(handle);

	return ret;
}

static void ext4_wait_dax_page(struct ext4_inode_info *ei)
{
	up_write(&ei->i_mmap_sem);
	schedule();
	down_write(&ei->i_mmap_sem);
}

int ext4_break_layouts(struct inode *inode)
{
	struct ext4_inode_info *ei = EXT4_I(inode);
	struct page *page;
	int error;

	if (WARN_ON_ONCE(!rwsem_is_locked(&ei->i_mmap_sem)))
		return -EINVAL;

	do {
		page = dax_layout_busy_page(inode->i_mapping);
		if (!page)
			return 0;

		error = ___wait_var_event(&page->_refcount,
				atomic_read(&page->_refcount) == 1,
				TASK_INTERRUPTIBLE, 0, 0,
				ext4_wait_dax_page(ei));
	} while (error == 0);

	return error;
}

/*
 * ext4_punch_hole:通过释放与给定偏移量和长度相关的块在文件中打孔
  * @inode: 文件索引节点
  * @offset: 孔开始的偏移量
  * @len: 孔的长度
  * 返回:成功时为 0,失败时为负
 */

int ext4_punch_hole(struct inode *inode, loff_t offset, loff_t length)
{
	struct super_block *sb = inode->i_sb;
	ext4_lblk_t first_block, stop_block;
	struct address_space *mapping = inode->i_mapping;
	loff_t first_block_offset, last_block_offset;
	handle_t *handle;
	unsigned int credits;
	int ret = 0, ret2 = 0;

	trace_ext4_punch_hole(inode, offset, length, 0);

	ext4_clear_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA);
	if (ext4_has_inline_data(inode)) {
		down_write(&EXT4_I(inode)->i_mmap_sem);
		ret = ext4_convert_inline_data(inode);
		up_write(&EXT4_I(inode)->i_mmap_sem);
		if (ret)
			return ret;
	}

	/*
	 * 写出所有脏页以避免竞争条件
	 * 然后释放它们。
	 */
	if (mapping_tagged(mapping, PAGECACHE_TAG_DIRTY)) {
		ret = filemap_write_and_wait_range(mapping, offset,
						   offset + length - 1);
		if (ret)
			return ret;
	}

	inode_lock(inode);

	/* 不需要打孔超过 i_size */
	if (offset >= inode->i_size)
		goto out_mutex;

	/*
	 * 如果孔超出 i_size,则设置孔在包含 i_size 的页面之后结束
	 */
	if (offset + length > inode->i_size) {
		length = inode->i_size +
		   PAGE_SIZE - (inode->i_size & (PAGE_SIZE - 1)) -
		   offset;
	}

	if (offset & (sb->s_blocksize - 1) ||
	    (offset + length) & (sb->s_blocksize - 1)) {
		/*
		 * 如果我们对部分块进行任何归零,则将 jinode 附加到 jbd2 的 inode
		 */
		ret = ext4_inode_attach_jinode(inode);
		if (ret < 0)
			goto out_mutex;

	}

	/* 等待所有现有的 dio workers,新来者将阻塞 i_mutex */
	inode_dio_wait(inode);

	/*
	 * 防止页面错误重新实例化我们从页面缓存中释放的页面。
	 */
	down_write(&EXT4_I(inode)->i_mmap_sem);

	ret = ext4_break_layouts(inode);
	if (ret)
		goto out_dio;

	first_block_offset = round_up(offset, sb->s_blocksize);
	last_block_offset = round_down((offset + length), sb->s_blocksize) - 1;

	/* 现在释放页面和页面的零块对齐部分*/
	if (last_block_offset > first_block_offset) {
		ret = ext4_update_disksize_before_punch(inode, offset, length);
		if (ret)
			goto out_dio;
		truncate_pagecache_range(inode, first_block_offset,
					 last_block_offset);
	}

	if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
		credits = ext4_writepage_trans_blocks(inode);
	else
		credits = ext4_blocks_for_truncate(inode);
	handle = ext4_journal_start(inode, EXT4_HT_TRUNCATE, credits);
	if (IS_ERR(handle)) {
		ret = PTR_ERR(handle);
		ext4_std_error(sb, ret);
		goto out_dio;
	}

	ret = ext4_zero_partial_blocks(handle, inode, offset,
				       length);
	if (ret)
		goto out_stop;

	first_block = (offset + sb->s_blocksize - 1) >>
		EXT4_BLOCK_SIZE_BITS(sb);
	stop_block = (offset + length) >> EXT4_BLOCK_SIZE_BITS(sb);

	/* 如果有块要删除,就删除 */
	if (stop_block > first_block) {

		down_write(&EXT4_I(inode)->i_data_sem);
		ext4_discard_preallocations(inode, 0);

		ret = ext4_es_remove_extent(inode, first_block,
					    stop_block - first_block);
		if (ret) {
			up_write(&EXT4_I(inode)->i_data_sem);
			goto out_stop;
		}

		if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
			ret = ext4_ext_remove_space(inode, first_block,
						    stop_block - 1);
		else
			ret = ext4_ind_remove_space(handle, inode, first_block,
						    stop_block);

		up_write(&EXT4_I(inode)->i_data_sem);
	}
	ext4_fc_track_range(handle, inode, first_block, stop_block);
	if (IS_SYNC(inode))
		ext4_handle_sync(handle);

	inode->i_mtime = inode->i_ctime = current_time(inode);
	ret2 = ext4_mark_inode_dirty(handle, inode);
	if (unlikely(ret2))
		ret = ret2;
	if (ret >= 0)
		ext4_update_inode_fsync_trans(handle, inode, 1);
out_stop:
	ext4_journal_stop(handle);
out_dio:
	up_write(&EXT4_I(inode)->i_mmap_sem);
out_mutex:
	inode_unlock(inode);
	return ret;
}

int ext4_inode_attach_jinode(struct inode *inode)
{
	struct ext4_inode_info *ei = EXT4_I(inode);
	struct jbd2_inode *jinode;

	if (ei->jinode || !EXT4_SB(inode->i_sb)->s_journal)
		return 0;

	jinode = jbd2_alloc_inode(GFP_KERNEL);
	spin_lock(&inode->i_lock);
	if (!ei->jinode) {
		if (!jinode) {
			spin_unlock(&inode->i_lock);
			return -ENOMEM;
		}
		ei->jinode = jinode;
		jbd2_journal_init_jbd_inode(ei->jinode, inode);
		jinode = NULL;
	}
	spin_unlock(&inode->i_lock);
	if (unlikely(jinode != NULL))
		jbd2_free_inode(jinode);
	return 0;
}

/*
 * ext4_truncate()
 * 我们在整个事务中阻止了 ext4_get_block() 块实例化,并且 VFS/VM 确保 ext4_truncate() 不能代表同一个 inode 同时运行。
 * 当我们完成截断并将其位提交到日志时,有一个核心指导原则:文件的树必须始终在磁盘上保持一致。我们必须能够在崩溃后重新启动截断。
 * 文件树在内存中可能暂时不一致(尽管可能不是),但是每当我们关闭并提交日志事务时,(文件系统 + 日志)的内容必须一致且可重新启动。自下而上,从右到左。
 * 请注意,在恢复时,日志重播发生在*之前* 针对孤立 inode 列表的截断重新启动。
 * 提交的 inode 具有新的、所需的 i_size(在这种情况下与 i_disksize 相同)。
   崩溃后, ext4_orphan_cleanup() 将看到此 inode 的截断未完成,它将再次调用 ext4_truncate() 以进行另一次操作。
   因此,在崩溃的 ext4 文件系统中,截断点右侧将有实例化块。但这很好——只要它们是从 inode 链接的,崩溃后的 ext4_truncate() 运行就会找到它们并释放它们。
 */
int ext4_truncate(struct inode *inode)
{
	struct ext4_inode_info *ei = EXT4_I(inode);
	unsigned int credits;
	int err = 0, err2;
	handle_t *handle;
	struct address_space *mapping = inode->i_mapping;

	/*
	 * 有可能我们正在释放 inode 或者它是一个全新的 inode。 在这些情况下,我们可能没有锁定 i_mutex,因为它没有必要。
	 */
	if (!(inode->i_state & (I_NEW|I_FREEING)))
		WARN_ON(!inode_is_locked(inode));
	trace_ext4_truncate_enter(inode);

	if (!ext4_can_truncate(inode))
		goto out_trace;

	if (inode->i_size == 0 && !test_opt(inode->i_sb, NO_AUTO_DA_ALLOC))
		ext4_set_inode_state(inode, EXT4_STATE_DA_ALLOC_CLOSE);

	if (ext4_has_inline_data(inode)) {
		int has_inline = 1;

		err = ext4_inline_data_truncate(inode, &has_inline);
		if (err || has_inline)
			goto out_trace;
	}

	/* 如果我们将页面尾部置零,我们必须为 jbd2 创建 jinode */
	if (inode->i_size & (inode->i_sb->s_blocksize - 1)) {
		if (ext4_inode_attach_jinode(inode) < 0)
			goto out_trace;
	}

	if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
		credits = ext4_writepage_trans_blocks(inode);
	else
		credits = ext4_blocks_for_truncate(inode);

	handle = ext4_journal_start(inode, EXT4_HT_TRUNCATE, credits);
	if (IS_ERR(handle)) {
		err = PTR_ERR(handle);
		goto out_trace;
	}

	if (inode->i_size & (inode->i_sb->s_blocksize - 1))
		ext4_block_truncate_page(handle, mapping, inode->i_size);

	/*
	 * 我们将 inode 添加到孤儿列表中,这样如果这个 truncate 跨越多个事务,并且我们崩溃,我们将在文件系统恢复时恢复 truncate。 
	   它还标记 inode 脏,以捕获新大小。
	 * 含义:当每个事务提交时,文件必须始终处于正常、一致的可截断状态。
	 */
	err = ext4_orphan_add(handle, inode);
	if (err)
		goto out_stop;

	down_write(&EXT4_I(inode)->i_data_sem);

	ext4_discard_preallocations(inode, 0);

	if (ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))
		err = ext4_ext_truncate(handle, inode);
	else
		ext4_ind_truncate(handle, inode);

	up_write(&ei->i_data_sem);
	if (err)
		goto out_stop;

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

out_stop:
	/*
	 *如果这是一个简单的ftruncate()并且文件仍然存在,那么我们需要清除上面创建的孤儿记录。
	 *然而,如果这是一个真正的断开链接,那么我们会被ext4_evict_inode()调用,我们允许该函数为我们清理孤立的信息。
	 */
	if (inode->i_nlink)
		ext4_orphan_del(handle, inode);

	inode->i_mtime = inode->i_ctime = current_time(inode);
	err2 = ext4_mark_inode_dirty(handle, inode);
	if (unlikely(err2 && !err))
		err = err2;
	ext4_journal_stop(handle);

out_trace:
	trace_ext4_truncate_exit(inode);
	return err;
}


未完待续……

你可能感兴趣的:(Linux内核文件系统,linux,运维,服务器)