塊I/O流程與 I/O調(diào)度器
- 一個(gè)塊IO的一生:從page cache到bio 到 request
- 塊設(shè)備層的數(shù)據(jù)結(jié)構(gòu)
- page和bio的關(guān)系,request 和bio的關(guān)系
- O_DIRECT 和O_SYNC
- blktrace , ftrace
- IO調(diào)度和CFQ調(diào)度算法
- CFQ和ionice
- cgroup與IO
- io性能測試: iotop, iostat
主要內(nèi)容:從應(yīng)用程序發(fā)起一次IO行為,最終怎么到磁盤,以及在這個(gè)路徑上有什么trace的方法和 配置。每次應(yīng)用程序?qū)懘疟P,都是到pagecache 。三進(jìn)三出 講解 bio的一生,都是在pagecache以下。
首先,在一次普通的sys_write過程中,會觸發(fā)以下的函數(shù)調(diào)用。
sys_write -> vfs_write -> generic_file_write_iter
PageCache: Linux通過局部性原理,使用頁面緩存提高性能。
1) generic_perform_write -> write_begin -> copy_data ->write_end
generic_perform_write: 開始pagecache的寫入過程
write_begin: 在內(nèi)存空間中準(zhǔn)備對應(yīng)index需要的page。
例如: ext4_write_begin 中包含
grab_cache_page_write_begin : 查獲取一個(gè)緩存頁或者創(chuàng)建一個(gè)緩存頁。
-> page_cache_get_page: 從mapping的radix tree中查找緩存頁,假如不存在,則從伙伴系統(tǒng)中申請一個(gè)新頁插入,并添加到LRU鏈表中。
ext4_write_end 首先調(diào)用__block_commit_write提交寫入的數(shù)據(jù),
通過set_buffer_uptodate :
-> mark_buffer_dirty -> set_buffer_dirty/ set_page_dirty/ set_inode_dirty 將該頁設(shè)臟,
通過wb_wakeup_delayed把writeback任務(wù)提交到bdi_writeback隊(duì)列中。
DirectIO:
2) generic_file_direct_write -> filemap_write_and_write_range -> mapping-> a_ops-> direct_IO
如果是buffer IO , 臟頁回寫是異步的,并且由塊設(shè)備層負(fù)責(zé)。
對于新版本的內(nèi)核,ext4注冊的方法是new_sync_write
sys_write -> vfs_write -> __vfs_write -> new_sync_write
-> filp->f_op->write_iter -> ext4_file_write_iter
塊設(shè)備層的數(shù)據(jù)結(jié)構(gòu)
struct page -> struct inode -> struct bio -> struct request
真正開始做IO之前,即操作block子系統(tǒng)之前,包括以下步驟:
應(yīng)用程序讀一個(gè)文件,首先會去查page cache是否命中,下一次再讀page cache是命中的。
應(yīng)用程序去寫硬盤時(shí),首先會去寫page cache,至于什么時(shí)候開始寫硬盤,由linux flush線程通過(pdflush->每一個(gè)設(shè)備的flush ->工作隊(duì)列)實(shí)現(xiàn)。當(dāng)然也可以是線程本身,通過direct IO去寫硬盤。
如上圖三個(gè)task_struct進(jìn)程,同時(shí)打開一個(gè)文件,在內(nèi)存中生成file數(shù)據(jù)結(jié)構(gòu),記錄文件打開的實(shí)例。
inode是真實(shí)存在硬盤里,當(dāng)inode結(jié)構(gòu)體中的i_mapping,對應(yīng)的地址空間address_space。一個(gè)4M的文件,被分成很多4K單元存在于內(nèi)存,通過地址 去radix tree來查page cache是否命中。如果查到了,就從radix tree對應(yīng)的page返回。 如果沒有page cache對應(yīng),就會通過 address_space_operations(文件系統(tǒng)實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)) 去 readpage,從硬盤里的塊讀到pagecache。
free命令看到的buffers和cache有什么區(qū)別?
答:在內(nèi)核層面,全部都是pagecache。前者對應(yīng)的是裸分區(qū)產(chǎn)生的page cache,后者對應(yīng)的是掛載文件系統(tǒng)之后,文件系統(tǒng)目錄產(chǎn)生的page cache。如下圖內(nèi)核代碼的截圖:
文件系統(tǒng)的管理單元是block,內(nèi)存管理是以page為單位,扇區(qū)(section)是硬件讀寫的最小單元。假設(shè)你把ext4文件系統(tǒng)格式化成1k/block,那么一個(gè)page對應(yīng)4個(gè)block,此時(shí)讀一個(gè)內(nèi)存的page,文件系統(tǒng)要操作4個(gè)block。假設(shè)你把ext4文件系統(tǒng)格式化成4k/block,那么page和block可以一一對應(yīng)。
/proc/meminfo中的 buffer ram如何計(jì)算出?
調(diào)用 nr_blockdev_pages(),這個(gè)函數(shù)會遍歷所有原始塊設(shè)備(list_for_each_entry),把所有原始塊設(shè)備的inode中的i_mapping , 指向 address_space_operation。包括radix tree管理的pagecache,和 pagecache讀寫硬盤的硬件操作的接口。nr_pages: page cache中已經(jīng)命中的page。
page和 bio的關(guān)系
所謂的bio,抽象的讀寫block device的請求,指文件系統(tǒng)的block與內(nèi)存page的對應(yīng)。bio要變成實(shí)際的block device access還要通過block device driver再排隊(duì),并受到ioscheduler的控制。
有時(shí)候文件系統(tǒng)格式化block為1k,一次讀寫page的請求,最終轉(zhuǎn)換成操作多個(gè)block。bio為I/O請求提供了一個(gè)輕量級的表示方法,內(nèi)核用一個(gè)bio的結(jié)構(gòu)體來描述一次塊IO操作。
bio結(jié)構(gòu)體如下:
struct bio {
sector_t bi_sector; /*我們想在塊設(shè)備的第幾個(gè)扇區(qū)上進(jìn)行io操作(起始扇區(qū)),此處扇區(qū)大小是按512計(jì)算的*/
struct bio *bi_next;
struct block_device *bi_bdev; /*指向塊設(shè)備描述符的指針,該io操作是針對哪個(gè)塊設(shè)備的*/
unsigned long bi_rw; /*該io操作是讀還是寫*/
unsigned short bi_vcnt; /* bio的bio_vec數(shù)組中段的數(shù)目 */
unsigned short bi_idx; /* bio的bio_vec數(shù)組中段的當(dāng)前索引值 */
unsigned short bi_phys_segments; //合并之后bio中(內(nèi)存)物理段的數(shù)目
unsigned int bi_size; /* 需要傳送的字節(jié)數(shù) */
bio_end_io_t *bi_end_io; /* bio的I/O操作結(jié)束時(shí)調(diào)用的方法 */
void *bi_private; //通用塊層和塊設(shè)備驅(qū)動(dòng)程序的I/O完成方法使用的指針
unsigned int bi_max_vecs; /* bio的bio vec數(shù)組中允許的最大段數(shù) */
atomic_t bi_cnt; /* bio的引用計(jì)數(shù)器 */
struct bio_vec *bi_io_vec; /*指向bio的bio_vec數(shù)組中的段的指針 */
struct bio_set *bi_pool;
struct bio_vec bi_inline_vecs[0];/*一般一個(gè)bio就一個(gè)段,bi_inline_vecs就可滿足,省去了再為bi_io_vec分配空間*/
}
struct bio_vec {
struct page *bv_page; //指向段的頁框?qū)?yīng)頁描述符的指針
unsigned int bv_len; //段的字節(jié)長度,長度可以超過一個(gè)頁
unsigned int bv_offset; //頁框中段數(shù)據(jù)的偏移量
};
一個(gè)bio可能有很多個(gè)bio段,這些bio段在內(nèi)存是可能不連續(xù),位于不同的頁,但在磁盤上對應(yīng)的位置是連續(xù)的。一般上層構(gòu)建bio的時(shí)候都是只有一個(gè)bio段。(新的DMA支持多個(gè)不連續(xù)內(nèi)存的數(shù)據(jù)傳輸)可以看到bio的段可能指向多個(gè)page,而bio也可以在沒有buffer_head的情況下構(gòu)造。
request 和bio的關(guān)系
在IO調(diào)度器中,上層提交的bio被構(gòu)造成request結(jié)構(gòu),每個(gè)物理設(shè)備會對應(yīng)一個(gè)request_queue,里面順序存放著相關(guān)的request。每個(gè)請求包含一個(gè)或多個(gè)bio結(jié)構(gòu),bio之間用有序鏈表連接起來,按bio起始扇區(qū)的位置從小到大,而且這些bio之間在磁盤扇區(qū)是相鄰的,也就是說一個(gè)bio的結(jié)尾剛好是下一個(gè)bio的開頭。
通常,通用塊層創(chuàng)建一個(gè)僅包含一個(gè)bio結(jié)構(gòu)的請求,可能存在新bio與請求中已存在的數(shù)據(jù)物理相鄰的情況,就把bio加入該請求,否則用該bio初始化一個(gè)新的請求。
buffer 和cache,在linux內(nèi)核實(shí)現(xiàn)上沒有區(qū)別,在計(jì)數(shù)上有區(qū)別。最后都是 address_space ->radix tree -> read/write pages。
buffer_head: 是內(nèi)核封裝的數(shù)據(jù)結(jié)構(gòu)。它是內(nèi)核page與磁盤上物理數(shù)據(jù)塊之間的橋梁。一方面,每個(gè)page包含多個(gè)buffer_head(一般4個(gè)),另外一方面,buffer_head中又記錄了底層設(shè)備塊號信息。這樣,通過page->buffer_head->block就能完成數(shù)據(jù)的讀寫。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【865977150】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦!??!
提升技術(shù)跳槽漲薪;
Linux內(nèi)核源碼/內(nèi)存調(diào)優(yōu)/文件系統(tǒng)/進(jìn)程管理/設(shè)備驅(qū)動(dòng)/網(wǎng)絡(luò)協(xié)議棧-學(xué)習(xí)視頻教程-騰訊課堂?ke.qq.com/course/4032547?flowToken=1040236
O_DIRECT 和 O_SYNC
兩者區(qū)別,O_DIRECT 把一片地址配置成不帶cache的空間 , 直接導(dǎo)硬盤 , 而 O_SYNC 類似CPU cache的write through. 當(dāng)應(yīng)用程序使用O_SYNC寫page cache時(shí),直接寫穿到硬盤。當(dāng)應(yīng)用程序同時(shí)使用O_SYNC和 O_DIRECT,可能會出現(xiàn)page cache的不一致問題。
writeback機(jī)制與bdi
write的過程,把要寫的page提交到bdi_writeback 隊(duì)列中,然后由writeback線程將其真正寫到block device上。writeback機(jī)制:一方面加快了write()的速度,另一方面便于合并和排序多個(gè)write請求。
三進(jìn)三出講解bio
page cache只有通過 address_space_operations里的 write_pages和 read_pages,才能產(chǎn)生對文件系統(tǒng)的block產(chǎn)生IO行為。而不管是directIO還是writeback機(jī)制最終都會通過submit_bio方法提交bio到block層。
block io( bio ) : 講文件系統(tǒng)上哪些block 讀到內(nèi)存哪些 page, 文件系統(tǒng)的 block bitmap 和 inode table,bio是從文件系統(tǒng)解析出來,從block到page之間的關(guān)系。
generic_make_request實(shí)現(xiàn)中請求構(gòu)建的關(guān)鍵為 make_request_fn, 該函數(shù)的調(diào)用鏈路為:
blk_init_queue() -> blk_init_queue_node() -> blk_init_allocated_queue -> blk_queue_make_request(q, lk_queue_io), 最后被調(diào)用執(zhí)行的回調(diào)函數(shù)blk_queue_bio實(shí)現(xiàn)如下:
page cache和 硬盤數(shù)據(jù) 通過linux readpages函數(shù)關(guān)聯(lián)時(shí),會把文件系統(tǒng)的四個(gè)block,轉(zhuǎn)化成4個(gè)bio。bio里的指針指向數(shù)據(jù)在硬盤的位置,也會有把這些數(shù)據(jù)讀出來之后放置到page cache的哪些page。
當(dāng)一個(gè)page對應(yīng)多個(gè)block的情況,一個(gè)page 對應(yīng)幾個(gè)bio:size(page) > size(block),
bio --> request每個(gè)進(jìn)程有自己的plug隊(duì)列,先把bio先發(fā)送到plug隊(duì)列。在plug隊(duì)列里,把bio轉(zhuǎn)化成request。第一個(gè)bio一定是一個(gè)request,之后的bio就會查看多個(gè)bio是否可以merge到一個(gè)request,如果不能合并就產(chǎn)生一個(gè)新的request。
bio邊發(fā)plug隊(duì)列,邊轉(zhuǎn)成request。而這些request 先放到 elevator電梯隊(duì)列,IO調(diào)度電梯做的類似路由器中QoS的功能->限制端口的流量。
deadline調(diào)度算法:優(yōu)先考慮讀,認(rèn)為寫不重要。應(yīng)用程序?qū)懙絧agecache以后,就已經(jīng)寫完了。
I/O寫入流程圖
ftrace
vfs_read/vfs_write 慢,到底層操作磁盤差十萬八千里。要用ftrace分析,才能知道具體原因。
對于文件讀寫這種非常復(fù)雜的流程,在工程里面可以使用的調(diào)試方式是 ftrace。
除了用ftrace工具進(jìn)行函數(shù)級的分析之外,使用blktrace去跟蹤整個(gè)block io 生命周期,比如什么時(shí)候進(jìn)入plug隊(duì)列,unplug,什么時(shí)候進(jìn)入電梯調(diào)度,什么時(shí)候進(jìn)到驅(qū)動(dòng)隊(duì)列。
blktrace
apt-get install sleuthkit blktrace
#1:
blktrace -d /dev/sda -o - | blkparse -i - > 1.trace
#2:
root@whale-indextest01-1001:/home/gzzhangyi2015/learningLinuxKernel/io-courses/bio-flow# dd if=read.c of=barry oflag=sync
0+1 records in
0+1 records out
219 bytes (219 B) copied, 0.000854041 s, 256 kB/s
扇區(qū)號(294220992)/8 = block號(36777624)
再用debugfs -R 'icheck 塊號' /dev/sda9
再用debugfs -R 'ncheck inode' /dev/sda9
再用 blkcat /dev/sda9 塊號
總結(jié):
blktrace 是在內(nèi)核里關(guān)鍵函數(shù)點(diǎn)上加了一些記錄,再把記錄抓下來。主要是看操作的流程。
ftrace 看 函數(shù)的流程。
IO調(diào)度 和 CFQ調(diào)度算法
主要從 進(jìn)程優(yōu)先級 , 流量控制 方面考慮,在通用塊層和 I/O調(diào)度層 進(jìn)行限速,限制帶寬和 IOPS。
Noop:空操作調(diào)度算法,也就是沒有任何調(diào)度操作,并不對io請求進(jìn)行排序,僅僅做適當(dāng)?shù)膇o合并的一個(gè)fifo隊(duì)列。合并的技術(shù),不太適用于排序。適用于固態(tài)硬盤,因?yàn)楣虘B(tài)硬盤基本上可以隨機(jī)訪問。
CFQ:完全公平隊(duì)列調(diào)度類似進(jìn)程調(diào)度里的CFS,指定進(jìn)程的nice值。它試圖給所有進(jìn)程提供一個(gè)完全公平的IO操作環(huán)境。它為每個(gè)進(jìn)程創(chuàng)建一個(gè)同步IO調(diào)度隊(duì)列,并默認(rèn)以時(shí)間片和請求數(shù)限定的方式分配IO資源,以此保證每個(gè)進(jìn)程的IO資源占用是公平的,cfq還實(shí)現(xiàn)了針對進(jìn)程級別的優(yōu)先級調(diào)度。
CFQ對于IO密集型場景不適用,尤其是IO壓力集中在某些進(jìn)程上的場景。該場景下需要更多滿足某個(gè)或某幾個(gè)進(jìn)程的IO響應(yīng)速度,而不是讓所有的進(jìn)程公平的使用IO。
此時(shí),deadline調(diào)度(最終期限調(diào)度)就更適應(yīng)這樣的場景。deadline實(shí)現(xiàn)了四個(gè)隊(duì)列,其中兩個(gè)分別處理正常read和write,按扇區(qū)號排序,進(jìn)行正常io的合并處理以提高吞吐量.因?yàn)镮O請求可能會集中在某些磁盤位置,這樣會導(dǎo)致新來的請求一直被合并,于是可能會有其他磁盤位置的io請求被餓死。
于是,實(shí)現(xiàn)了另外兩個(gè)處理超時(shí)read和write的隊(duì)列,按請求創(chuàng)建時(shí)間排序,如果有超時(shí)的請求出現(xiàn),就放進(jìn)這兩個(gè)隊(duì)列,調(diào)度算法保證超時(shí)(達(dá)到最終期限時(shí)間)的隊(duì)列中的請求會優(yōu)先被處理,防止請求被餓死。由于deadline的特點(diǎn),無疑在這里無法區(qū)分進(jìn)程,也就不能實(shí)現(xiàn)針對進(jìn)程的io資源控制。
從原理上看,cfq是一種比較通用的調(diào)度算法,是一種以進(jìn)程為出發(fā)點(diǎn)考慮的調(diào)度算法,保證大家盡量公平。
deadline是一種以提高機(jī)械硬盤吞吐量為思考出發(fā)點(diǎn)的調(diào)度算法,只有當(dāng)有io請求達(dá)到最終期限的時(shí)候才進(jìn)行調(diào)度,非常適合業(yè)務(wù)比較單一并且IO壓力比較重的業(yè)務(wù),比如數(shù)據(jù)庫。
而noop呢?其實(shí)如果我們把我們的思考對象拓展到固態(tài)硬盤,那么你就會發(fā)現(xiàn),無論cfq還是deadline,都是針對機(jī)械硬盤的結(jié)構(gòu)進(jìn)行的隊(duì)列算法調(diào)整,而這種調(diào)整對于固態(tài)硬盤來說,完全沒有意義。對于固態(tài)硬盤來說,IO調(diào)度算法越復(fù)雜,效率就越低,因?yàn)轭~外要處理的邏輯越多。
所以,固態(tài)硬盤這種場景下,使用noop是最好的,deadline次之,而cfq由于復(fù)雜度的原因,無疑效率最低。
CFQ 和 ionice
demo
把linux的IO調(diào)度算法改成CFQ,并且運(yùn)行兩個(gè)不同IO nice值的進(jìn)程:
目前的調(diào)度算法是 deadline
root@whale-indextest01-1001:/sys/block/sda/queue# cat scheduler
noop [deadline] cfq
root@whale-indextest01-1001:/sys/block/sda/queue# echo cfq > scheduler
root@whale-indextest01-1001:/sys/block/sda/queue# cat scheduler
noop deadline [cfq]
root@whale-indextest01-1001:/sys/block/sda/queue# ionice -c 2 -n 0 cat /dev/sda9 > /dev/null&
[1] 6755
root@whale-indextest01-1001:/sys/block/sda/queue# ionice -c 2 -n 7 cat /dev/sda9 > /dev/null&
[2] 6757
root@whale-indextest01-1001:/sys/block/sda/queue# iotop
cgroup與IO
root@whale-indextest01-1001:/sys/fs/cgroup/blkio# cgexec -g blkio:A dd if=/dev/sda9 of=/dev/null iflag=direct &
[3] 7550
root@whale-indextest01-1001:/sys/fs/cgroup/blkio# cgexec -g blkio:B dd if=/dev/sda9 of=/dev/null iflag=direct &
[4] 7552
IO性能測試
1、rrqm/s & wrqm/s 看io合并: 和/sys/block/sdb/queue/scheduler 設(shè)置的io調(diào)度算法有關(guān)。
2、%util與硬盤設(shè)備飽和度: iostat 無法看硬盤設(shè)備的飽和度。
3、即使%util高達(dá)100%,硬盤也仍然有可能還有余力處理更多的I/O請求,即沒有達(dá)到飽和狀態(tài)。
await 是單個(gè)I/O所消耗的時(shí)間,包括硬盤設(shè)備處理I/O的時(shí)間和I/O請求在kernel隊(duì)列中等待的時(shí)間.
實(shí)際場景根據(jù)I/O模式 隨機(jī)/順序與否進(jìn)行判斷。如果磁盤陣列的寫操作不在一兩個(gè)毫秒以內(nèi)就算慢的了;讀操作則未必,不在緩存中的數(shù)據(jù)仍然需要讀取物理硬盤,單個(gè)小數(shù)據(jù)塊的讀取速度跟單盤差不多。
iowait
- %iowait 表示在一個(gè)采樣周期內(nèi)有百分之幾的時(shí)間屬于以下情況:CPU空閑、并且有仍未完成的I/O請求。
- %iowait 升高并不能證明等待I/O的進(jìn)程數(shù)量增多了,也不能證明等待I/O的總時(shí)間增加了。
- %iowait升高有可能僅僅是cpu空閑時(shí)間增加了。
- %iowait 的高低與I/O的多少沒有必然關(guān)系,而是與I/O的并發(fā)度相關(guān)。所以,僅憑 %iowait 的上升不能得出I/O負(fù)載增加 的結(jié)論。
它是一個(gè)非常模糊的指標(biāo),如果看到 %iowait 升高,還需檢查I/O量有沒有明顯增加,avserv/avwait/avque等指標(biāo)有沒有明顯增大,應(yīng)用有沒有感覺變慢。
FAQ:
什么是Bufferd IO/ Direct IO? 如何解釋cgroup的blkio對buffered IO是沒有限速支持的?
答:這里面的buffer的含義跟內(nèi)存中buffer cache有概念上的不同。實(shí)際上這里Buffered IO的含義,相當(dāng)于內(nèi)存中的buffer cache+page cache,就是IO經(jīng)過緩存的意思。
cgroup針對IO的資源限制實(shí)現(xiàn)在了通用塊設(shè)備層,對哪些IO操作有影響呢? 原則上說都有影響,因?yàn)榻^大多數(shù)數(shù)據(jù)都是要經(jīng)過通用塊設(shè)備層寫入存儲的,但是對于應(yīng)用程序來說感受可能不一樣。在一般IO的情況下,應(yīng)用程序很可能很快的就寫完了數(shù)據(jù)(在數(shù)據(jù)量小于緩存空間的情況下),然后去做其他事情了。這時(shí)應(yīng)用程序感受不到自己被限速了,而內(nèi)核在處理write-back的階段,由于沒有相關(guān)page cache中的inode是屬于那個(gè)cgroup的信息記錄,所以所有的page cache的回寫只能放到cgroup的root組中進(jìn)行限制,而不能在其他cgroup中進(jìn)行限制,因?yàn)閞oot組的cgroup一般是不做限制的。
而在Sync IO和Direct IO的情況下,由于應(yīng)用程序?qū)懙臄?shù)據(jù)是不經(jīng)過緩存層的,所以能直接感受到速度被限制,一定要等到整個(gè)數(shù)據(jù)按限制好的速度寫完或者讀完,才能返回。這就是當(dāng)前cgroup的blkio限制所能起作用的環(huán)境限制。
評論
查看更多