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(獨占)的頁面緩沖區,如果此頁面中的所有緩沖區都已映射并且沒有累積的緩沖區范圍要映射,
或者將頁面中的緩沖區添加到緩沖區的范圍,則提交頁面以進行 IO 地圖,
如果呼叫者可以繼續處理下一頁,則該函式回傳 1,如果它應該停止向要映射的范圍添加緩沖區,則回傳 0,因為我們無法再擴展它,
它也可以在 IO 提交程序中出錯時回傳值 < 0,
*/
static int mpage_process_page_bufs(struct mpage_da_data *mpd,
struct buffer_head *head,
struct buffer_head *bh,
ext4_lblk_t lblk)
{
struct inode *inode = mpd->inode;
int err;
ext4_lblk_t blocks = (i_size_read(inode) + i_blocksize(inode) - 1)
>> inode->i_blkbits;
if (ext4_verity_in_progress(inode))
blocks = EXT_MAX_BLOCKS;
do {
BUG_ON(buffer_locked(bh));
if (lblk >= blocks || !mpage_add_bh_to_extent(mpd, lblk, bh)) {
/* 找到要映射的范圍 */
if (mpd->map.m_len)
return 0;
/* 緩沖區需要映射,句柄未啟動? */
if (!mpd->do_map)
return 0;
/* 到目前為止一切都已映射,我們達到了 EOF */
break;
}
} while (lblk++, (bh = bh->b_this_page) != head);
/* 到目前為止一切都被映射了嗎? 提交 IO 頁面, */
if (mpd->map.m_len == 0) {
err = mpage_submit_page(mpd, head->b_page);
if (err < 0)
return err;
}
if (lblk >= blocks) {
mpd->scanned_until_end = 1;
return 0;
}
return 1;
}
/*
* mpage_process_page - 更新對應于改變范圍的頁面緩沖區,并且可以為 IO 提交完全映射的頁面
*
* @mpd - 要映射的范圍的描述,回傳下一個要映射的范圍
* @m_lblk - 邏輯塊映射,
* @m_pblk - 對應的物理映射,
* @map_bh - 在回傳時確定此頁面是否需要進一步映射,
* 掃描與改變的范圍對應的給定頁面緩沖區,并根據新的范圍狀態更新緩沖區狀態,
* 我們將 delalloc 緩沖區映射到它們的物理位置,清除未寫入的位,
如果給定頁面沒有完全映射,我們將@map 更新到給定頁面中需要映射的下一個范圍并將@map_bh 回傳為true,
*/
static int mpage_process_page(struct mpage_da_data *mpd, struct page *page,
ext4_lblk_t *m_lblk, ext4_fsblk_t *m_pblk,
bool *map_bh)
{
struct buffer_head *head, *bh;
ext4_io_end_t *io_end = mpd->io_submit.io_end;
ext4_lblk_t lblk = *m_lblk;
ext4_fsblk_t pblock = *m_pblk;
int err = 0;
int blkbits = mpd->inode->i_blkbits;
ssize_t io_end_size = 0;
struct ext4_io_end_vec *io_end_vec = ext4_last_io_end_vec(io_end);
bh = head = page_buffers(page);
do {
if (lblk < mpd->map.m_lblk)
continue;
if (lblk >= mpd->map.m_lblk + mpd->map.m_len) {
/*
* 映射范圍結束后的緩沖區,
* 查找頁面中的下一個緩沖區以進行映射,
*/
mpd->map.m_len = 0;
mpd->map.m_flags = 0;
io_end_vec->size += io_end_size;
io_end_size = 0;
err = mpage_process_page_bufs(mpd, head, bh, lblk);
if (err > 0)
err = 0;
if (!err && mpd->map.m_len && mpd->map.m_lblk > lblk) {
io_end_vec = ext4_alloc_io_end_vec(io_end);
if (IS_ERR(io_end_vec)) {
err = PTR_ERR(io_end_vec);
goto out;
}
io_end_vec->offset = (loff_t)mpd->map.m_lblk << blkbits;
}
*map_bh = true;
goto out;
}
if (buffer_delay(bh)) {
clear_buffer_delay(bh);
bh->b_blocknr = pblock++;
}
clear_buffer_unwritten(bh);
io_end_size += (1 << blkbits);
} while (lblk++, (bh = bh->b_this_page) != head);
io_end_vec->size += io_end_size;
io_end_size = 0;
*map_bh = false;
out:
*m_lblk = lblk;
*m_pblk = pblock;
return err;
}
/*
* mpage_map_buffers - 更新對應于改變范圍的緩沖區并提交完全映射的頁面用于 IO
*
* @mpd - 要映射的范圍的描述,回傳下一個要映射的范圍
*
* 掃描改變范圍對應的緩沖區(我們期望相應的頁面已經被鎖定)并根據新的范圍狀態更新緩沖區狀態,
* 我們將 delalloc 緩沖區映射到它們的物理位置,清除未寫入的位,并在我們對未寫入的盤區執行寫入時將緩沖區標記為 uninit,并在 IO 完成后進行盤區轉換,
如果最后一頁沒有完全映射,我們將@map 更新到最后一頁中需要映射的下一個范圍, 否則我們提交頁面以進行 IO,
*/
static int mpage_map_and_submit_buffers(struct mpage_da_data *mpd)
{
struct pagevec pvec;
int nr_pages, i;
struct inode *inode = mpd->inode;
int bpp_bits = PAGE_SHIFT - inode->i_blkbits;
pgoff_t start, end;
ext4_lblk_t lblk;
ext4_fsblk_t pblock;
int err;
bool map_bh = false;
start = mpd->map.m_lblk >> bpp_bits;
end = (mpd->map.m_lblk + mpd->map.m_len - 1) >> bpp_bits;
lblk = start << bpp_bits;
pblock = mpd->map.m_pblk;
pagevec_init(&pvec);
while (start <= end) {
nr_pages = pagevec_lookup_range(&pvec, inode->i_mapping,
&start, end);
if (nr_pages == 0)
break;
for (i = 0; i < nr_pages; i++) {
struct page *page = pvec.pages[i];
err = mpage_process_page(mpd, page, &lblk, &pblock,
&map_bh);
/*
* 如果 map_bh 為 true,則意味著頁面可能需要進一步的 bh 映射,或者頁面可能已提交用于 IO, 所以我們回傳呼叫進一步的范圍映射,
*/
if (err < 0 || map_bh)
goto out;
/* 頁面完全映射 - 讓 IO 運行 */
err = mpage_submit_page(mpd, page);
if (err < 0)
goto out;
}
pagevec_release(&pvec);
}
/* 范圍完全映射并與頁面邊界匹配 */
mpd->map.m_len = 0;
mpd->map.m_flags = 0;
return 0;
out:
pagevec_release(&pvec);
return err;
}
static int mpage_map_one_extent(handle_t *handle, struct mpage_da_data *mpd)
{
struct inode *inode = mpd->inode;
struct ext4_map_blocks *map = &mpd->map;
int get_blocks_flags;
int err, dioread_nolock;
trace_ext4_da_write_pages_extent(inode, map);
/*
* 呼叫 ext4_map_blocks() 來分配任何延遲的分配塊,或將未寫入的范圍轉換為初始化(在我們已寫入一個或多個預分配塊的情況下),
我們可能需要比以前保留的更多的元資料塊, 但是我們不能失敗,因為我們處于寫回狀態,我們對此無能為力,因此可能會導致資料丟失,
因此,如果可能,請使用保留塊來分配元資料,
*
* 如果有問題的塊是 delalloc 塊,我們將傳入魔法 EXT4_GET_BLOCKS_DELALLOC_RESERVE, 這表明在將資料復制到頁面快取中時已經檢查了塊和配額,
*/
get_blocks_flags = EXT4_GET_BLOCKS_CREATE |
EXT4_GET_BLOCKS_METADATA_NOFAIL |
EXT4_GET_BLOCKS_IO_SUBMIT;
dioread_nolock = ext4_should_dioread_nolock(inode);
if (dioread_nolock)
get_blocks_flags |= EXT4_GET_BLOCKS_IO_CREATE_EXT;
if (map->m_flags & BIT(BH_Delay))
get_blocks_flags |= EXT4_GET_BLOCKS_DELALLOC_RESERVE;
err = ext4_map_blocks(handle, inode, map, get_blocks_flags);
if (err < 0)
return err;
if (dioread_nolock && (map->m_flags & EXT4_MAP_UNWRITTEN)) {
if (!mpd->io_submit.io_end->handle &&
ext4_handle_valid(handle)) {
mpd->io_submit.io_end->handle = handle->h_rsv_handle;
handle->h_rsv_handle = NULL;
}
ext4_set_io_unwritten_flag(inode, mpd->io_submit.io_end);
}
BUG_ON(map->m_len == 0);
return 0;
}
/*
* mpage_map_and_submit_extent - 映射范圍從 mpd->lblk 開始,長度為 mpd->len 并提交它下面的頁面用于 IO
*
* @handle - 日志操作的句柄
* @mpd - 映射范圍
* @give_up_on_write - 如果存在致命錯誤并且沒有希望寫入資料,我們將其設定為 true,呼叫者應該丟棄臟頁以避免無限回圈,
*
* 該函式映射范圍從 mpd->lblk 開始,長度為 mpd->len,如果延遲,則分配塊,如果未寫入,我們可能需要將它們轉換為已初始化或從更大的未寫入范圍拆分描述的范圍,
請注意,我們不需要映射所有描述的范圍,因為分配可以回傳更少的塊或者該范圍被更多未寫入的范圍覆寫,
我們無法映射更多,因為我們受到保留交易信用的限制,另一方面,我們始終確保最后觸摸的頁面已完全映射,以便可以將其寫出(從而保證前進的進度),
映射后,我們提交所有映射頁面進行 IO,
*/
static int mpage_map_and_submit_extent(handle_t *handle,
struct mpage_da_data *mpd,
bool *give_up_on_write)
{
struct inode *inode = mpd->inode;
struct ext4_map_blocks *map = &mpd->map;
int err;
loff_t disksize;
int progress = 0;
ext4_io_end_t *io_end = mpd->io_submit.io_end;
struct ext4_io_end_vec *io_end_vec;
io_end_vec = ext4_alloc_io_end_vec(io_end);
if (IS_ERR(io_end_vec))
return PTR_ERR(io_end_vec);
io_end_vec->offset = ((loff_t)map->m_lblk) << inode->i_blkbits;
do {
err = mpage_map_one_extent(handle, mpd);
if (err < 0) {
struct super_block *sb = inode->i_sb;
if (ext4_forced_shutdown(EXT4_SB(sb)) ||
ext4_test_mount_flag(sb, EXT4_MF_FS_ABORTED))
goto invalidate_dirty_pages;
/*
* 讓上層重試瞬時錯誤,
* 在 ENOSPC 的情況下,如果 ext4_count_free_blocks() 非零,提交應該釋放塊,
*/
if ((err == -ENOMEM) ||
(err == -ENOSPC && ext4_count_free_clusters(sb))) {
if (progress)
goto update_disksize;
return err;
}
ext4_msg(sb, KERN_CRIT,
"Delayed block allocation failed for "
"inode %lu at logical offset %llu with"
" max blocks %u with error %d",
inode->i_ino,
(unsigned long long)map->m_lblk,
(unsigned)map->m_len, -err);
ext4_msg(sb, KERN_CRIT,
"This should not happen!! Data will "
"be lost\n");
if (err == -ENOSPC)
ext4_print_free_blocks(inode);
invalidate_dirty_pages:
*give_up_on_write = true;
return err;
}
progress = 1;
/*
* 更新緩沖區狀態,提交映射頁面,并為我們獲取新的映射范圍
*/
err = mpage_map_and_submit_buffers(mpd);
if (err < 0)
goto update_disksize;
} while (map->m_len);
update_disksize:
/*
* IO 提交后更新磁盤大小, 通過檢查 i_data_sem 下的 i_size 避免了 truncate 的競爭,
*/
disksize = ((loff_t)mpd->first_page) << PAGE_SHIFT;
if (disksize > READ_ONCE(EXT4_I(inode)->i_disksize)) {
int err2;
loff_t i_size;
down_write(&EXT4_I(inode)->i_data_sem);
i_size = i_size_read(inode);
if (disksize > i_size)
disksize = i_size;
if (disksize > EXT4_I(inode)->i_disksize)
EXT4_I(inode)->i_disksize = disksize;
up_write(&EXT4_I(inode)->i_data_sem);
err2 = ext4_mark_inode_dirty(handle, inode);
if (err2) {
ext4_error_err(inode->i_sb, -err2,
"Failed to mark inode %lu dirty",
inode->i_ino);
}
if (!err)
err = err2;
}
return err;
}
/*
* 計算為一次 writepages 迭代保留的積分總數, 這是從 ext4_writepages() 呼叫的,
*我們最多映射 MAX_WRITEPAGES_EXTENT_LEN 塊的范圍,然后我們繼續完成最后部分頁面的映射,
*所以總的來說我們可以在 bpp 不同的范圍內映射 MAX_WRITEPAGES_EXTENT_LEN + bpp - 1 個塊,
*/
static int ext4_da_writepages_trans_blocks(struct inode *inode)
{
int bpp = ext4_journal_blocks_per_page(inode);
return ext4_meta_trans_blocks(inode,
MAX_WRITEPAGES_EXTENT_LEN + bpp - 1, bpp);
}
/*
* mpage_prepare_extent_to_map - 查找并鎖定連續范圍的臟頁和要映射的底層范圍
*
* @mpd - 在哪里尋找頁面
*
* 在映射中走臟頁, 如果它們已完全映射,請立即將它們提交給 IO,
當我們找到一個未被映射的頁面時,我們開始累積這些頁面下需要映射的緩沖區的范圍(由延遲或未寫入的緩沖區形成),
我們還鎖定包含這些緩沖區的頁面, 找到的范圍在@mpd 結構中回傳(從 mpd->lblk 開始,長度為 mpd->len 塊),
*
* 請注意,此函式可以將 bios 附加到一個 io_end 結構上,該結構在邏輯上和物理上都不連續,
盡管這似乎是一種不必要的復雜化,但在塊大小 < 頁大小的情況下實際上是不可避免的,因為我們需要在一個 io_end 中跟蹤到頁面下所有緩沖區的 IO,
*/
static int mpage_prepare_extent_to_map(struct mpage_da_data *mpd)
{
struct address_space *mapping = mpd->inode->i_mapping;
struct pagevec pvec;
unsigned int nr_pages;
long left = mpd->wbc->nr_to_write;
pgoff_t index = mpd->first_page;
pgoff_t end = mpd->last_page;
xa_mark_t tag;
int i, err = 0;
int blkbits = mpd->inode->i_blkbits;
ext4_lblk_t lblk;
struct buffer_head *head;
if (mpd->wbc->sync_mode == WB_SYNC_ALL || mpd->wbc->tagged_writepages)
tag = PAGECACHE_TAG_TOWRITE;
else
tag = PAGECACHE_TAG_DIRTY;
pagevec_init(&pvec);
mpd->map.m_len = 0;
mpd->next_page = index;
while (index <= end) {
nr_pages = pagevec_lookup_range_tag(&pvec, mapping, &index, end,
tag);
if (nr_pages == 0)
break;
for (i = 0; i < nr_pages; i++) {
struct page *page = pvec.pages[i];
/*
* 積累了足夠多的臟頁? 這不適用于 WB_SYNC_ALL 模式,
對于完整性同步,我們必須繼續進行,因為有人可能同時在弄臟頁面,
我們可能同步了很多新出現的臟頁,但還沒有同步所有舊的臟頁,
*/
if (mpd->wbc->sync_mode == WB_SYNC_NONE && left <= 0)
goto out;
/* 如果我們不能合并這個頁面,我們就完成了, */
if (mpd->map.m_len > 0 && mpd->next_page != page->index)
goto out;
lock_page(page);
/*
* 如果頁面不再臟,或者它的映射不再對應我們正在寫入的 inode(這意味著它已經被截斷或失效),
或者頁面已經在寫回并且我們沒有做資料完整性寫回,請跳過該頁
*/
if (!PageDirty(page) ||
(PageWriteback(page) &&
(mpd->wbc->sync_mode == WB_SYNC_NONE)) ||
unlikely(page->mapping != mapping)) {
unlock_page(page);
continue;
}
wait_on_page_writeback(page);
BUG_ON(PageWriteback(page));
if (mpd->map.m_len == 0)
mpd->first_page = page->index;
mpd->next_page = page->index + 1;
/* 將所有臟緩沖區添加到 mpd */
lblk = ((ext4_lblk_t)page->index) <<
(PAGE_SHIFT - blkbits);
head = page_buffers(page);
err = mpage_process_page_bufs(mpd, head, head, lblk);
if (err <= 0)
goto out;
err = 0;
left--;
}
pagevec_release(&pvec);
cond_resched();
}
mpd->scanned_until_end = 1;
return 0;
out:
pagevec_release(&pvec);
return err;
}
static int ext4_writepages(struct address_space *mapping,
struct writeback_control *wbc)
{
pgoff_t writeback_index = 0;
long nr_to_write = wbc->nr_to_write;
int range_whole = 0;
int cycled = 1;
handle_t *handle = NULL;
struct mpage_da_data mpd;
struct inode *inode = mapping->host;
int needed_blocks, rsv_blocks = 0, ret = 0;
struct ext4_sb_info *sbi = EXT4_SB(mapping->host->i_sb);
struct blk_plug plug;
bool give_up_on_write = false;
if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
return -EIO;
percpu_down_read(&sbi->s_writepages_rwsem);
trace_ext4_writepages(inode, wbc);
/*
* 沒有頁面可寫? 這主要是為了避免在最后的 iput() 上為特殊的 inode 啟動事務,
例如日志 inode,因為這可能會違反 umount 上的鎖順序
*/
if (!mapping->nrpages || !mapping_tagged(mapping, PAGECACHE_TAG_DIRTY))
goto out_writepages;
if (ext4_should_journal_data(inode)) {
ret = generic_writepages(mapping, wbc);
goto out_writepages;
}
/*
* 如果檔案系統已中止,則它是只讀的,因此立即回傳而不是稍后轉儲堆疊跟蹤,這將掩蓋問題的真正根源,
我們測驗 EXT4_MF_FS_ABORTED 而不是 sb->s_flag 的 SB_RDONLY,因為如果檔案系統以只讀方式掛載,后者可能為真,
在這種情況下,不應呼叫 ext4_writepages,因此如果發生這種情況,我們將需要堆疊跟蹤,
*/
if (unlikely(ext4_forced_shutdown(EXT4_SB(mapping->host->i_sb)) ||
ext4_test_mount_flag(inode->i_sb, EXT4_MF_FS_ABORTED))) {
ret = -EROFS;
goto out_writepages;
}
/*
* 如果我們有行內資料到達這里,意味著我們很快就會為第一頁創建塊,所以我們最好在這里清除行內資料,
*/
if (ext4_has_inline_data(inode)) {
/* Just inode will be modified... */
handle = ext4_journal_start(inode, EXT4_HT_INODE, 1);
if (IS_ERR(handle)) {
ret = PTR_ERR(handle);
goto out_writepages;
}
BUG_ON(ext4_test_inode_state(inode,
EXT4_STATE_MAY_INLINE_DATA));
ext4_destroy_inline_data(handle, inode);
ext4_journal_stop(handle);
}
if (ext4_should_dioread_nolock(inode)) {
/*
* 我們可能需要為頁面中的每個塊最多轉換一個范圍,并且我們可能會弄臟 inode,
*/
rsv_blocks = 1 + ext4_chunk_trans_blocks(inode,
PAGE_SIZE >> inode->i_blkbits);
}
if (wbc->range_start == 0 && wbc->range_end == LLONG_MAX)
range_whole = 1;
if (wbc->range_cyclic) {
writeback_index = mapping->writeback_index;
if (writeback_index)
cycled = 0;
mpd.first_page = writeback_index;
mpd.last_page = -1;
} else {
mpd.first_page = wbc->range_start >> PAGE_SHIFT;
mpd.last_page = wbc->range_end >> PAGE_SHIFT;
}
mpd.inode = inode;
mpd.wbc = wbc;
ext4_io_submit_init(&mpd.io_submit, wbc);
retry:
if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
tag_pages_for_writeback(mapping, mpd.first_page, mpd.last_page);
blk_start_plug(&plug);
/*
* 不需要映射的第一個回寫頁面 - 我們可以避免不必要地啟動事務,也可以避免在事務啟動時因設備擁塞而在塊層中被阻塞,
*/
mpd.do_map = 0;
mpd.scanned_until_end = 0;
mpd.io_submit.io_end = ext4_init_io_end(inode, GFP_KERNEL);
if (!mpd.io_submit.io_end) {
ret = -ENOMEM;
goto unplug;
}
ret = mpage_prepare_extent_to_map(&mpd);
/* 解鎖我們沒有使用的頁面 */
mpage_release_unused_pages(&mpd, false);
/* 提交準備好的bio */
ext4_io_submit(&mpd.io_submit);
ext4_put_io_end_defer(mpd.io_submit.io_end);
mpd.io_submit.io_end = NULL;
if (ret < 0)
goto unplug;
while (!mpd.scanned_until_end && wbc->nr_to_write > 0) {
/* 對于每個頁面范圍,我們使用新的 io_end */
mpd.io_submit.io_end = ext4_init_io_end(inode, GFP_KERNEL);
if (!mpd.io_submit.io_end) {
ret = -ENOMEM;
break;
}
/*
* 我們有兩個約束:我們找到一個范圍來映射,我們必須總是寫出整個頁面(當blocksize < pagesize 時會有所不同),
這樣當我們嘗試寫出頁面的其余部分時我們不會阻塞IO, delalloc 不支持日志模式,
*/
BUG_ON(ext4_should_journal_data(inode));
needed_blocks = ext4_da_writepages_trans_blocks(inode);
/* 開始一個新的事務 */
handle = ext4_journal_start_with_reserve(inode,
EXT4_HT_WRITE_PAGE, needed_blocks, rsv_blocks);
if (IS_ERR(handle)) {
ret = PTR_ERR(handle);
ext4_msg(inode->i_sb, KERN_CRIT, "%s: jbd2_start: "
"%ld pages, ino %lu; err %d", __func__,
wbc->nr_to_write, inode->i_ino, ret);
/* 釋放分配的 io_end */
ext4_put_io_end(mpd.io_submit.io_end);
mpd.io_submit.io_end = NULL;
break;
}
mpd.do_map = 1;
trace_ext4_da_write_pages(inode, mpd.first_page, mpd.wbc);
ret = mpage_prepare_extent_to_map(&mpd);
if (!ret && mpd.map.m_len)
ret = mpage_map_and_submit_extent(handle, &mpd,
&give_up_on_write);
/*
* 注意:如果句柄是同步的,ext4_journal_stop() 可以等待事務提交完成,這可能取決于頁面的回寫完成或頁面鎖的釋放,
在這種情況下,我們必須等到提交所有 IO、釋放我們持有的頁面鎖并洗掉 io_end 參考(為了能夠完成范圍轉換)之后,才能停止句柄,
*/
if (!ext4_handle_valid(handle) || handle->h_sync == 0) {
ext4_journal_stop(handle);
handle = NULL;
mpd.do_map = 0;
}
/* 解鎖我們沒有使用的頁面 */
mpage_release_unused_pages(&mpd, give_up_on_write);
/* 提交準備好的bio */
ext4_io_submit(&mpd.io_submit);
/*
* 洗掉我們從 init 獲得的 io_end 參考, 如果我們仍然持有交易,我們必須小心并使用延遲的 io_end 完成,
因為我們可以釋放對 io_end 的最后一個參考,這可能最侄訓進行未寫的范圍轉換,
*/
if (handle) {
ext4_put_io_end_defer(mpd.io_submit.io_end);
ext4_journal_stop(handle);
} else
ext4_put_io_end(mpd.io_submit.io_end);
mpd.io_submit.io_end = NULL;
if (ret == -ENOSPC && sbi->s_journal) {
/*
* 提交將釋放交易中釋放的塊的交易并重試
*/
jbd2_journal_force_commit_nested(sbi->s_journal);
ret = 0;
continue;
}
/* Fatal error - ENOMEM, EIO... */
if (ret)
break;
}
unplug:
blk_finish_plug(&plug);
if (!ret && !cycled && wbc->nr_to_write > 0) {
cycled = 1;
mpd.last_page = writeback_index - 1;
mpd.first_page = 0;
goto retry;
}
/* 更新索引 */
if (wbc->range_cyclic || (range_whole && wbc->nr_to_write > 0))
/*
* 設定 writeback_index 以便 range_cyclic 模式稍后將其寫回
*/
mapping->writeback_index = mpd.first_page;
out_writepages:
trace_ext4_writepages_result(inode, wbc, ret,
nr_to_write - wbc->nr_to_write);
percpu_up_read(&sbi->s_writepages_rwsem);
return ret;
}
static int ext4_dax_writepages(struct address_space *mapping,
struct writeback_control *wbc)
{
int ret;
long nr_to_write = wbc->nr_to_write;
struct inode *inode = mapping->host;
struct ext4_sb_info *sbi = EXT4_SB(mapping->host->i_sb);
if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
return -EIO;
percpu_down_read(&sbi->s_writepages_rwsem);
trace_ext4_writepages(inode, wbc);
ret = dax_writeback_mapping_range(mapping, sbi->s_daxdev, wbc);
trace_ext4_writepages_result(inode, wbc, ret,
nr_to_write - wbc->nr_to_write);
percpu_up_read(&sbi->s_writepages_rwsem);
return ret;
}
static int ext4_nonda_switch(struct super_block *sb)
{
s64 free_clusters, dirty_clusters;
struct ext4_sb_info *sbi = EXT4_SB(sb);
/*
* 如果我們在空閑塊上運行不足,則切換到非 delalloc 模式, percpu_counter_batch 在每個 CPU 上累積而不更新全域計數器時,通過 percpu 計數器進行的空閑塊記帳可能會略有錯誤
* Delalloc 需要一個準確的空閑塊記帳, 所以當我們接近錯誤范圍時切換到非 delalloc,
*/
free_clusters =
percpu_counter_read_positive(&sbi->s_freeclusters_counter);
dirty_clusters =
percpu_counter_read_positive(&sbi->s_dirtyclusters_counter);
/*
* 當 1/2 的空閑塊是臟的時,開始推送 delalloc,
*/
if (dirty_clusters && (free_clusters < 2 * dirty_clusters))
try_to_writeback_inodes_sb(sb, WB_REASON_FS_FREE_SPACE);
if (2 * free_clusters < 3 * dirty_clusters ||
free_clusters < (dirty_clusters + EXT4_FREECLUSTERS_WATERMARK)) {
/*
* 空閑塊數小于臟塊的 150% 或空閑塊小于水印
*/
return 1;
}
return 0;
}
/* 我們總是為 inode 更新保留; 超級塊也可能在那里 */
static int ext4_da_write_credits(struct inode *inode, loff_t pos, unsigned len)
{
if (likely(ext4_has_feature_large_file(inode->i_sb)))
return 1;
if (pos + len <= 0x7fffffffULL)
return 1;
/* 我們可能需要更新超級塊來設定 LARGE_FILE */
return 2;
}
static int ext4_da_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned flags,
struct page **pagep, void **fsdata)
{
int ret, retries = 0;
struct page *page;
pgoff_t index;
struct inode *inode = mapping->host;
handle_t *handle;
if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
return -EIO;
index = pos >> PAGE_SHIFT;
if (ext4_nonda_switch(inode->i_sb) || S_ISLNK(inode->i_mode) ||
ext4_verity_in_progress(inode)) {
*fsdata = (void *)FALL_BACK_TO_NONDELALLOC;
return ext4_write_begin(file, mapping, pos,
len, flags, pagep, fsdata);
}
*fsdata = (void *)0;
trace_ext4_da_write_begin(inode, pos, len, flags);
if (ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA)) {
ret = ext4_da_write_inline_data_begin(mapping, inode,
pos, len, flags,
pagep, fsdata);
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);
/*
* 使用延遲分配,如果有延遲塊分配,我們不會記錄 i_disksize 更新, 但是,如果寫入已映射緩沖區的檔案末尾,我們仍然需要記錄 i_disksize 更新,
*/
retry_journal:
handle = ext4_journal_start(inode, EXT4_HT_WRITE_PAGE,
ext4_da_write_credits(inode, pos, len));
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
ret = ext4_block_write_begin(page, pos, len,
ext4_da_get_block_prep);
#else
ret = __block_write_begin(page, pos, len, ext4_da_get_block_prep);
#endif
if (ret < 0) {
unlock_page(page);
ext4_journal_stop(handle);
/*
* block_write_begin 可能在 i_size 之外實體化了幾個塊, 再把這些剪掉, 不需要 i_size_read 因為我們持有 i_mutex,
*/
if (pos + len > inode->i_size)
ext4_truncate_failed_write(inode);
if (ret == -ENOSPC &&
ext4_should_retry_alloc(inode->i_sb, &retries))
goto retry_journal;
put_page(page);
return ret;
}
*pagep = page;
return ret;
}
/*
* 檢查我們是否應該在寫入檔案末尾但不需要塊分配時更新 i_disksize
*/
static int ext4_da_should_update_i_disksize(struct page *page,
unsigned long offset)
{
struct buffer_head *bh;
struct inode *inode = page->mapping->host;
unsigned int idx;
int i;
bh = page_buffers(page);
idx = offset >> inode->i_blkbits;
for (i = 0; i < idx; i++)
bh = bh->b_this_page;
if (!buffer_mapped(bh) || (buffer_delay(bh)) || buffer_unwritten(bh))
return 0;
return 1;
}
static int ext4_da_write_end(struct file *file,
struct address_space *mapping,
loff_t pos, unsigned len, unsigned copied,
struct page *page, void *fsdata)
{
struct inode *inode = mapping->host;
int ret = 0, ret2;
handle_t *handle = ext4_journal_current_handle();
loff_t new_i_size;
unsigned long start, end;
int write_mode = (int)(unsigned long)fsdata;
if (write_mode == FALL_BACK_TO_NONDELALLOC)
return ext4_write_end(file, mapping, pos,
len, copied, page, fsdata);
trace_ext4_da_write_end(inode, pos, len, copied);
start = pos & (PAGE_SIZE - 1);
end = start + copied - 1;
/*
* 如果 i_size 改變, generic_write_end() 將運行 mark_inode_dirty(),
因此,讓我們將 i_disksize mark_inode_dirty 附加到其中,
*/
new_i_size = pos + copied;
if (copied && new_i_size > EXT4_I(inode)->i_disksize) {
if (ext4_has_inline_data(inode) ||
ext4_da_should_update_i_disksize(page, end)) {
ext4_update_i_disksize(inode, new_i_size);
/*
即使 new_i_size 小于 inode->i_size bu 大于 i_disksize,我們也需要將 inode 標記為臟,(提示 delalloc)
*/
ret = ext4_mark_inode_dirty(handle, inode);
}
}
if (write_mode != CONVERT_INLINE_DATA &&
ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA) &&
ext4_has_inline_data(inode))
ret2 = ext4_da_write_inline_data_end(inode, pos, len, copied,
page);
else
ret2 = generic_write_end(file, mapping, pos, len, copied,
page, fsdata);
copied = ret2;
if (ret2 < 0)
ret = ret2;
ret2 = ext4_journal_stop(handle);
if (unlikely(ret2 && !ret))
ret = ret2;
return ret ? ret : copied;
}
未完待續……
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/387135.html
標籤:其他
