F2FS源碼分析系列文章
主目錄
一、文件系統布局以及元數據結構
二、文件數據的存儲以及讀寫
F2FS文件數據組織方式一般文件寫流程一般文件讀流程目錄文件讀流程(未完成)目錄文件寫流程(未完成)
三、文件與目錄的創建以及刪除(未完成)
四、垃圾回收機制
五、數據恢復機制
六、重要數據結構或者函數的分析
F2FS的讀流程
讀流程介紹
F2FS的讀流程包含了以下幾個子流程:
vfs_read函數generic_file_read_iter函數: 根據訪問類型執行不同的處理generic_file_buffered_read: 根據用戶傳入的文件偏移,讀取尺寸等信息,計算起始位置和頁數,然后遍歷每一個page,通過預讀或者單個讀取的方式從磁盤中讀取出來f2fs_read_data_page&f2fs_read_data_pages函數: 從磁盤讀取1個page或者多個pagef2fs_mpage_readpages函數: f2fs讀取數據的主流程
第一步的vfs_read函數是VFS層面的流程,下面僅針對涉及F2FS的讀流程,且經過簡化的主要流程進行分析。
generic_file_read_iter函數
這個函數的作用是處理普通方式訪問以及direct方式訪問的讀行為,這里僅針對普通方式的讀訪問進行分析:
ssize_t
generic_file_read_iter(struct kiocb
*iocb
, struct iov_iter
*iter
)
{size_t count
= iov_iter_count(iter
); ssize_t retval
= 0;if (!count
)goto out
;if (iocb
->ki_flags
& IOCB_DIRECT
) { ...}retval
= generic_file_buffered_read(iocb
, iter
, retval
);
out
:return retval
;
}
generic_file_buffered_read函數
在介紹這兩個之前,需要先介紹一種VFS提高讀取速度的機制: 預讀(readahead)機制。它的核心原理是,當用戶訪問page 1,系統就會將page 1后續的page 2,page 3,page 4一起讀取到page cache(減少與磁盤這種速度慢設備的交互次數,提高讀性能)。之后用戶再連續讀取page 2,page 3,page 4時,由于已經讀取到內存中,因此可以快速地返回給用戶。
generic_file_buffered_read函數的主要作用是循環地從磁盤或者內存讀取用戶需要的page,同時也會在某些情況調用page_cache_sync_readahead函數進行預讀,由于函數比較復雜,且很多goto語句,簡化后的步驟如下:
情況1: 預讀(readahead)機制成功預讀到用戶需要接下來訪問的page
ind_get_page: 系統無法在cache中找到用戶需要的pagepage_cache_sync_readahead: 系統執行該函數進行預讀,一次性讀取多個pagefind_get_page: 再重新在cache獲取一次page,獲取成功后跳轉到page ok區域page_ok: 復制page的數據去用戶傳入的buffer中,然后判讀是否為最后一個page,如果是則退出讀流程
情況2: 預讀(readahead)機制錯誤預讀到用戶需要接下來訪問的page
find_get_page: 系統無法在cache中找到用戶需要的pagepage_cache_sync_readahead: 系統執行該函數進行預讀,一次性讀取多個pagefind_get_page: 再重新在cache獲取一次page,獲取失敗,跳轉到no_cached_page區域no_cached_page: 創建一個page cache結構,加入到LRU后,跳轉到readpage區域readpage: 執行mapping->a_ops->readpage函數從磁盤讀取數據,成功后跳轉到page ok區域page_ok: 復制page的數據去用戶傳入的buffer中,然后判讀是否為最后一個page,如果是則退出讀流程。
static ssize_t
generic_file_buffered_read(struct kiocb
*iocb
,struct iov_iter
*iter
, ssize_t written
)
{index
= *ppos
>> PAGE_SHIFT
; prev_index
= ra
->prev_pos
>> PAGE_SHIFT
;prev_offset
= ra
->prev_pos
& (PAGE_SIZE
-1);last_index
= (*ppos
+ iter
->count
+ PAGE_SIZE
-1) >> PAGE_SHIFT
;offset
= *ppos
& ~PAGE_MASK
;for (;;) {
find_page
:page
= find_get_page(mapping
, index
); if (!page
) { page_cache_sync_readahead(mapping
, ra
, filp
,index
, last_index
- index
);page
= find_get_page(mapping
, index
); if (unlikely(page
== NULL)) goto no_cached_page
;}
page_ok
: isize
= i_size_read(inode
);end_index
= (isize
- 1) >> PAGE_SHIFT
;nr
= PAGE_SIZE
;if (index
== end_index
) { nr
= ((isize
- 1) & ~PAGE_MASK
) + 1;if (nr
<= offset
) {put_page(page
);goto out
;}}nr
= nr
- offset
;ret
= copy_page_to_iter(page
, offset
, nr
, iter
); offset
+= ret
;index
+= offset
>> PAGE_SHIFT
;offset
&= ~PAGE_MASK
;prev_offset
= offset
;put_page(page
);written
+= ret
;if (!iov_iter_count(iter
)) goto out
;if (ret
< nr
) {error
= -EFAULT
;goto out
;}continue;
readpage
:ClearPageError(page
);error
= mapping
->a_ops
->readpage(filp
, page
); goto page_ok
;
no_cached_page
:page
= page_cache_alloc(mapping
); error
= add_to_page_cache_lru(page
, mapping
, index
,mapping_gfp_constraint(mapping
, GFP_KERNEL
)); goto readpage
;}
out
:ra
->prev_pos
= prev_index
;ra
->prev_pos
<<= PAGE_SHIFT
;ra
->prev_pos
|= prev_offset
;*ppos
= ((loff_t
)index
<< PAGE_SHIFT
) + offset
;file_accessed(filp
);return written
? written
: error
;
}
預讀函數page_cache_sync_readahead的分析由于篇幅有限無法全部展示,因此這里僅分析它的核心調用函數__do_page_cache_readahead:
unsigned int __do_page_cache_readahead(struct address_space
*mapping
,struct file
*filp
, pgoff_t offset
, unsigned long nr_to_read
,unsigned long lookahead_size
)
{end_index
= ((isize
- 1) >> PAGE_SHIFT
); for (page_idx
= 0; page_idx
< nr_to_read
; page_idx
++) { pgoff_t page_offset
= offset
+ page_idx
; if (page_offset
> end_index
) break;page
= __page_cache_alloc(gfp_mask
); page
->index
= page_offset
; list_add(&page
->lru
, &page_pool
); nr_pages
++;}if (nr_pages
)read_pages(mapping
, filp
, &page_pool
, nr_pages
, gfp_mask
); BUG_ON(!list_empty(&page_pool
));
out
:return nr_pages
;
}static int read_pages(struct address_space
*mapping
, struct file
*filp
,struct list_head
*pages
, unsigned int nr_pages
, gfp_t gfp
)
{struct blk_plug plug
;unsigned page_idx
;int ret
;blk_start_plug(&plug
);if (mapping
->a_ops
->readpages
) {ret
= mapping
->a_ops
->readpages(filp
, mapping
, pages
, nr_pages
); put_pages_list(pages
);goto out
;}ret
= 0;out
:blk_finish_plug(&plug
);return ret
;
}
f2fs_read_data_page&f2fs_read_data_pages函數
從上一節可以知道,當預讀機制會調用mapping->a_ops->readpages函數一次性讀取多個page。而當預讀失敗時,也會調用mapping->a_ops->readpage讀取單個page。這兩個函數在f2fs中對應的就是f2fs_read_page和f2fs_read_pages,如下所示:
static int f2fs_read_data_page(struct file
*file
, struct page
*page
)
{struct inode
*inode
= page
->mapping
->host
;int ret
= -EAGAIN
;trace_f2fs_readpage(page
, DATA
);if (f2fs_has_inline_data(inode
)) ret
= f2fs_read_inline_data(inode
, page
);ret
= f2fs_mpage_readpages(page
->mapping
, NULL, page
, 1); return ret
;
}static int f2fs_read_data_pages(struct file
*file
,struct address_space
*mapping
,struct list_head
*pages
, unsigned nr_pages
)
{struct inode
*inode
= mapping
->host
;struct page
*page
= list_last_entry(pages
, struct page
, lru
);trace_f2fs_readpages(inode
, page
, nr_pages
);if (f2fs_has_inline_data(inode
)) return 0;return f2fs_mpage_readpages(mapping
, pages
, NULL, nr_pages
);
}
f2fs_mpage_readpages函數
無論是f2fs_read_page函數還是f2fs_read_pages函數,都是調用f2fs_mpage_readpages函數進行讀取,區別僅在于傳入參數。f2fs_mpage_readpages的定義為:
static int f2fs_mpage_readpages(struct address_space
*mapping
,struct list_head
*pages
, struct page
*page
, unsigned nr_pages
);
第二個參數表示一個鏈表頭,這個鏈表保存了多個page,因此需要寫入多個page的時候,就要傳入一個List。
第三個參數表示單個page,在寫入單個page的時候,通過這個函數寫入。
第四個參數表示需要寫入page的數目。
因此
在寫入多個page的時候,需要設定第二個參數,和第四個參數,然后設定第三個參數為NULL。
在寫入單個page的時候,需要設定第三個參數,和第四個參數,然后設定第二個參數為NULL。
然后分析這個函數的執行流程:
遍歷傳入的page,得到每一個page的index以及inode將page的inode以及index傳入 f2fs_map_blocks 函數獲取到該page的物理地址將物理地址通過 submit_bio 讀取該page在磁盤中的數據
static int f2fs_mpage_readpages(struct address_space
*mapping
,struct list_head
*pages
, struct page
*page
,unsigned nr_pages
)
{struct f2fs_map_blocks map
;map
.m_pblk
= 0;map
.m_lblk
= 0;map
.m_len
= 0;map
.m_flags
= 0;map
.m_next_pgofs
= NULL;for (page_idx
= 0; nr_pages
; page_idx
++, nr_pages
--) {if (pages
) {page
= list_entry(pages
->prev
, struct page
, lru
);list_del(&page
->lru
);if (add_to_page_cache_lru(page
, mapping
,page
->index
, GFP_KERNEL
))goto next_page
;}if ((map
.m_flags
& F2FS_MAP_MAPPED
) && block_in_file
> map
.m_lblk
&&block_in_file
< (map
.m_lblk
+ map
.m_len
))goto got_it
;map
.m_flags
= 0;if (block_in_file
< last_block
) {map
.m_lblk
= block_in_file
; map
.m_len
= last_block
- block_in_file
; if (f2fs_map_blocks(inode
, &map
, 0,F2FS_GET_BLOCK_READ
))goto set_error_page
;}got_it
:if ((map
.m_flags
& F2FS_MAP_MAPPED
)) { block_nr
= map
.m_pblk
+ block_in_file
- map
.m_lblk
;SetPageMappedToDisk(page
);if (!PageUptodate(page
) && !cleancache_get_page(page
)) {SetPageUptodate(page
);goto confused
;}} else { zero_user_segment(page
, 0, PAGE_SIZE
);SetPageUptodate(page
);unlock_page(page
);goto next_page
;}if (bio
&& (last_block_in_bio
!= block_nr
- 1)) {
submit_and_realloc
:submit_bio(READ
, bio
);bio
= NULL;}if (bio
== NULL) {struct fscrypt_ctx
*ctx
= NULL;if (f2fs_encrypted_inode(inode
) &&S_ISREG(inode
->i_mode
)) {ctx
= fscrypt_get_ctx(inode
, GFP_NOFS
);if (IS_ERR(ctx
))goto set_error_page
;f2fs_wait_on_encrypted_page_writeback(F2FS_I_SB(inode
), block_nr
);}bio
= bio_alloc(GFP_KERNEL
,min_t(int, nr_pages
, BIO_MAX_PAGES
)); if (!bio
) {if (ctx
)fscrypt_release_ctx(ctx
);goto set_error_page
;}bio
->bi_bdev
= bdev
;bio
->bi_iter
.bi_sector
= SECTOR_FROM_BLOCK(block_nr
); bio
->bi_end_io
= f2fs_read_end_io
;bio
->bi_private
= ctx
;}if (bio_add_page(bio
, page
, blocksize
, 0) < blocksize
)goto submit_and_realloc
;set_error_page
:SetPageError(page
);zero_user_segment(page
, 0, PAGE_SIZE
);unlock_page(page
);goto next_page
;
confused
: if (bio
) {submit_bio(READ
, bio
);bio
= NULL;}unlock_page(page
);
next_page
:if (pages
)put_page(page
);}BUG_ON(pages
&& !list_empty(pages
));if (bio
)submit_bio(READ
, bio
);return 0;
}
總結
以上是生活随笔為你收集整理的F2FS源码分析-2.3 [F2FS 读写部分] F2FS的一般文件读流程分析的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。