Linux内核文件系统11

这篇博客主要探讨了Linux内核中的核心组件inode.c,详细阐述了其在文件系统中的作用,为运维和服务器管理提供深入了解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2021SC@SDUSC

inode.c(2)

/*
* 请注意,除非我们记录数据,否则我们不需要启动事务,因为我们应该从 ext4_page_mkwrite() 填充漏洞。
  我们甚至不需要在有序模式下将 inode 归档到事务列表中,
  因为如果我们写回通过 write() 添加的数据,inode 已经在那里,
  如果我们写回通过 mmap() 修改的数据,没有人保证数据将在哪个事务中访问磁盘。
  如果我们正在记录数据,我们不能直接启动事务,因为事务启动高于页面锁,所以我们必须做一些magic。
 * 这个函数可以通过...
   - 获取页面锁定后的 ext4_writepages(有日志句柄)
   - journal_submit_inode_data_buffers(无日志句柄)
   - 通过 kswapd/直接回收(无日志句柄)的shrink_page_list
   - 执行write_begin 时grab_page_cache(有日志句柄)
 * 我们在这个函数中不做任何块分配。如果我们有多个块的页面,我们需要写入那些被映射的 buffer_heads。
   这对于基于 mmaped 的写入很重要。因此,如果我们使用块大小 1K truncate(f, 1024); a = mmap(f, 0, 4096); a[0] = 'a';截断(f,4096);
   我们在页面中通过 page_mkwrite 回调映射了第一个 buffer_head,但其他 buffer_heads 将未映射但很脏(通过 do_wp_page 完成)。
   所以 writepage 应该写第一个块。如果我们修改 mmap 区域超过 1024,我们将再次得到 page_fault 并且 page_mkwrite 回调将进行块分配并标记已映射的 buffer_heads。
 * 如果页面中有任何延迟或未写入的 buffer_heads,我们会重脏页面。
 * 我们可以递归调用如下所示。
 * ext4_writepage() -> kmalloc() -> __alloc_pages() -> page_launder() -> ext4_writepage()
 * 但是由于我们不做任何块分配,所以我们不应该死锁。页面也清除了脏标志,因此我们不会获得递归 page_lock。
 */
static int ext4_writepage(struct page *page,
			  struct writeback_control *wbc)
{
	int ret = 0;
	loff_t size;
	unsigned int len;
	struct buffer_head *page_bufs = NULL;
	struct inode *inode = page->mapping->host;
	struct ext4_io_submit io_submit;
	bool keep_towrite = false;

	if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb)))) {
		inode->i_mapping->a_ops->invalidatepage(page, 0, PAGE_SIZE);
		unlock_page(page);
		return -EIO;
	}

	trace_ext4_writepage(page);
	size = i_size_read(inode);
	if (page->index == size >> PAGE_SHIFT &&
	    !ext4_verity_in_progress(inode))
		len = size & ~PAGE_MASK;
	else
		len = PAGE_SIZE;

	page_bufs = page_buffers(page);
	/*
	 * 我们无法在此函数中进行块分配或其他范围处理。 如果有缓冲区需要,我们必须重新脏页面。 
	  但是当我们通过 journal_submit_inode_data_buffers() 进行日志提交时,我们可能会到达这里,
	  在这种情况下,我们必须写入分配的缓冲区以实现 data=ordered 模式保证。
	 * 另外,如果每页只有一个缓冲区(fs块大小==页大小),如果一个缓冲区需要块分配或需要修改extent树清除未写标志,
	   我们知道该页不能 完全被写入,所以我们不妨立即拒绝写入。 
	   不幸的是,如果块大小 != 页面大小,我们不能使用 ext4_walk_page_buffers() 轻松检测这种情况,
	   但对于极其常见的情况,这是一种优化,可以跳过 ext4_bio_write_page() 的无用往返。
	 */
	if (ext4_walk_page_buffers(NULL, page_bufs, 0, len, NULL,
				   ext4_bh_delay_or_unwritten)) {
		redirty_page_for_writepage(wbc, page);
		if ((current->flags & PF_MEMALLOC) ||
		    (inode->i_sb->s_blocksize == PAGE_SIZE)) {
			/*
			 * 对于内存清理,只写一些缓冲区是没有意义的。 所以只能保释。 如果我们从直接回收来到这里,请警告。
			 */
			WARN_ON_ONCE((current->flags & (PF_MEMALLOC|PF_KSWAPD))
							== PF_MEMALLOC);
			unlock_page(page);
			return 0;
		}
		keep_towrite = true;
	}

	if (PageChecked(page) && ext4_should_journal_data(inode))
		/*
		 * 它是映射的页面缓存。 添加缓冲区并记录它。 在这里重新修改页面似乎没有多大意义。
		 */
		return __ext4_journalled_writepage(page, len);

	ext4_io_submit_init(&io_submit, wbc);
	io_submit.io_end = ext4_init_io_end(inode, GFP_NOFS);
	if (!io_submit.io_end) {
		redirty_page_for_writepage(wbc, page);
		unlock_page(page);
		return -ENOMEM;
	}
	ret = ext4_bio_write_page(&io_submit, page, len, keep_towrite);
	ext4_io_submit(&io_submit);
	/* 删除我们从 init 得到的 io_end 引用 */
	ext4_put_io_end_defer(io_submit.io_end);
	return ret;
}

static int mpage_submit_page(struct mpage_da_data *mpd, struct page *page)
{
	int len;
	loff_t size;
	int err;

	BUG_ON(page->index != mpd->first_page);
	clear_page_dirty_for_io(page);
	/*
	 *我们在这里必须非常小心! 没有什么可以保护写回路径免受 i_size 更改的影响,
	  并且页面可以可写地映射到页表中。
	  因此,应用程序可以在写回运行时增加 i_size 并通过 mmap 写入数据。 
	  clear_page_dirty_for_io() 在页表中对我们的页进行写保护,
	  并且在我们释放页锁之前无法再次写入该页。 
	  因此,只有在 clear_page_dirty_for_io() 之后,
	  我们才能安全地对 ext4_bio_write_page() 的 i_size 进行采样,以将写入页面的尾部清零。 
	  我们依靠在 clear_page_dirty_for_io() 中 TestClearPageDirty 提供的屏障来确保 i_size 仅在页表更新后才真正被采样。
	 */
	size = i_size_read(mpd->inode);
	if (page->index == size >> PAGE_SHIFT &&
	    !ext4_verity_in_progress(mpd->inode))
		len = size & ~PAGE_MASK;
	else
		len = PAGE_SIZE;
	err = ext4_bio_write_page(&mpd->io_submit, page, len, false);
	if (!err)
		mpd->wbc->nr_to_write--;
	mpd->first_page++;

	return err;
}

#define BH_FLAGS (BIT(BH_Unwritten) | BIT(BH_Delay))

/*
 * mballoc 最多给我们这个数量的块...
 * XXX:这似乎只是 ext4_mb_normalize_request() 的限制。
 * mballoc 的其余部分似乎可以处理最大组大小的块。
 */
#define MAX_WRITEPAGES_EXTENT_LEN 2048

/*
 * mpage_add_bh_to_extent - 尝试将 bh 添加到要映射的块范围
 * @mpd - 块的范围
 * @lblk - 文件中块的逻辑编号
 * @bh - 我们要添加到范围的缓冲区头
 * 该函数用于收集contig。 块处于相同状态。 
   如果缓冲区不需要回写映射,并且我们还没有开始映射缓冲区的范围,则该函数立即返回true——调用者可以立即写入缓冲区。 
   否则,如果块已添加到范围,则函数返回 true,如果无法添加块,则返回 false。
 */
static bool mpage_add_bh_to_extent(struct mpage_da_data *mpd, ext4_lblk_t lblk,
				   struct buffer_head *bh)
{
	struct ext4_map_blocks *map = &mpd->map;

	/* 写回不需要映射的缓冲区 */
	if (!buffer_dirty(bh) || !buffer_mapped(bh) ||
	    (!buffer_delay(bh) && !buffer_unwritten(bh))) {
		/* 到目前为止还没有映射的范围 => 我们立即写入缓冲区 */
		if (map->m_len == 0)
			return true;
		return false;
	}

	/* 范围内的第一个块? */
	if (map->m_len == 0) {
		/* 除非句柄启动,否则我们无法映射... */
		if (!mpd->do_map)
			return false;
		map->m_lblk = lblk;
		map->m_len = 1;
		map->m_flags = bh->b_state & BH_FLAGS;
		return true;
	}

	/* 不要超过 mballoc 愿意分配的大小 */
	if (map->m_len >= MAX_WRITEPAGES_EXTENT_LEN)
		return false;

	/* 我们可以将块合并到我们的大范围吗? */
	if (lblk == map->m_lblk + map->m_len &&
	    (bh->b_state & BH_FLAGS) == map->m_flags) {
		map->m_len++;
		return true;
	}
	return false;
}

/*
 * mpage_process_page_bufs - 为 IO 提交页面缓冲区或将它们添加到范围
  *
  * @mpd - 映射块的范围
  * @head - 页面中的第一个缓冲区
  * @bh - 我们应该从缓冲区开始处理
  * @lblk - 文件中与@bh 对应的块的逻辑编号
  *
  * 遍历从@bh 到@head(独占)的页
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值