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(独占)的页