難得擁有大塊的空閑時(shí)間挟秤,所以近期對(duì)操作系統(tǒng)的部分核心知識(shí)進(jìn)行了系統(tǒng)的學(xué)習(xí)以及總結(jié)。借助平臺(tái)將一些收獲進(jìn)行整理并分享給讀者,希望文章中的內(nèi)容能帶來一些收獲追迟。
本文深入文件讀寫底層,引出I/O行為背后涉及到的頁高速緩存骚腥、內(nèi)存與塊設(shè)備直接的交互敦间、Mmap等知識(shí)點(diǎn)。
我們知道束铭,當(dāng)提到文件時(shí)有兩個(gè)問題需要我們注意:第一是由于存儲(chǔ)塊設(shè)備與內(nèi)存直接的速度不匹配從而導(dǎo)致的性能問題廓块、第二個(gè)是多個(gè)進(jìn)程有時(shí)需要讀取同一個(gè)文件內(nèi)容,即一旦數(shù)據(jù)被訪問契沫,就很有可能在短時(shí)間被二次訪問带猴。
于是便引出了我們今天要講述的主角:頁緩存(Page Cache)。
下面我們來看一個(gè)文件讀取的例子:
假設(shè)系統(tǒng)中現(xiàn)在存在一個(gè)名為render的進(jìn)程懈万,該進(jìn)程打開了文件scene.dat拴清,并且每次讀取其中的512B(一個(gè)扇區(qū)的大小),將讀取的文件數(shù)據(jù)放入到堆分配的塊中(每個(gè)進(jìn)程自己的地址空間對(duì)應(yīng)的物理內(nèi)存)会通。
1.進(jìn)程調(diào)用庫函數(shù)read()向內(nèi)核發(fā)起讀文件的請(qǐng)求口予,希望讀取scene.data的512B內(nèi)容;
2.這里我們假設(shè)cache中并沒有所需要讀取的數(shù)據(jù)所以需要向Disk中尋找數(shù)據(jù)涕侈,于是內(nèi)核通過檢查進(jìn)程的文件描述符定位到虛擬文件系統(tǒng)已經(jīng)打開的文件列表項(xiàng)沪停,調(diào)用該文件系統(tǒng)對(duì)VFS的read()調(diào)用提供的接口;
3.通過文件表項(xiàng)鏈接到目錄項(xiàng)模塊裳涛,根據(jù)傳入的文件路徑在目錄項(xiàng)中檢索木张,找到該文件的inode;
4.根據(jù)inode找到文件调违,通過文件內(nèi)容偏移量計(jì)算出要讀取的頁窟哺,將頁中對(duì)應(yīng)的512B數(shù)據(jù)先讀取到page cache處;
5.最后再通過該inode的i_mapping指針找到對(duì)應(yīng)的address_space頁緩存樹---基樹技肩,查找對(duì)應(yīng)的頁緩存節(jié)點(diǎn)且轨;
簡單來說:
(1)如果頁緩存節(jié)點(diǎn)命中,那么直接返回文件內(nèi)容虚婿;
(2)如果頁緩存缺失旋奢,那么產(chǎn)生一個(gè)缺頁異常,首先創(chuàng)建一個(gè)新的空的物理頁框然痊,通過該inode找到文件中該頁的磁盤地址至朗,讀取相應(yīng)的頁填充該頁緩存(DMA的方式將數(shù)據(jù)讀取到頁緩存),更新頁表項(xiàng)剧浸,最后再從頁緩存中讀锹引;
所有的文件內(nèi)容的讀却V印(無論一開始是命中頁緩存還是沒有命中頁緩存)最終都是直接來源于頁緩存。當(dāng)將數(shù)據(jù)從磁盤復(fù)制到頁緩存之后嫌变,還要將頁緩存的數(shù)據(jù)通過CPU復(fù)制到read調(diào)用提供的緩沖區(qū)中吨艇,這就是普通文件IO需要的兩次復(fù)制數(shù)據(jù)復(fù)制過程。其中第一次是通過DMA的方式將數(shù)據(jù)從磁盤復(fù)制到頁緩存中腾啥,本次過程只需要CPU在一開始的時(shí)候讓出總線东涡、結(jié)束之后處理DMA中斷即可,中間不需要CPU的直接干預(yù)倘待,CPU可以去做別的事情疮跑;第二次是將數(shù)據(jù)從頁緩存復(fù)制到進(jìn)程自己的的地址空間對(duì)應(yīng)的物理內(nèi)存中,這個(gè)過程中需要CPU的全程干預(yù)凸舵,浪費(fèi)CPU的時(shí)間和額外的物理內(nèi)存空間祖娘。
此時(shí)對(duì)于render進(jìn)程來說,其堆會(huì)指向Page Frames的三個(gè)存放數(shù)據(jù)的區(qū)域贞间,從而完成數(shù)據(jù)的讀取贿条。
在上面的內(nèi)容中我們提到了——address_space與基樹(radix tree)
Address_space對(duì)象
在Page Cache中存在多個(gè)不連續(xù)的物理磁盤塊(512B,而頁大小為4KB)增热,所以如何通過頁來定位我們所需要的數(shù)據(jù)是一個(gè)很重要并且很困難的事情整以。(如果能用設(shè)備號(hào)+塊號(hào)來定位是最好的,但是頁與塊大小不匹配峻仇,所以不太行)
于是這里就需要我們引入address_space對(duì)象來幫助我們:
struct address_space {
每一個(gè)所有者(可以理解為一個(gè)具體的文件公黑,一個(gè)inode指向的文件)對(duì)應(yīng)著一個(gè)address_space對(duì)象,頁高速緩存的多個(gè)頁可能屬于一個(gè)所有者摄咆,從而可以鏈接到一個(gè)address_space對(duì)象凡蚜。那么一個(gè)頁(page)怎么和一個(gè)address_space產(chǎn)生關(guān)聯(lián)的呢?
page中有兩個(gè)字段:mapping和index吭从。其中mapping指向該頁所有者的address_mapping(內(nèi)存inode結(jié)構(gòu)有一個(gè)i_mapping指向?qū)?yīng)address_space對(duì)象)朝蜘,index字段表示所有者地址空間中以頁大小為單位的偏移量。用這兩個(gè)字段就能在頁高速緩存中查找頁涩金。(這里注意一點(diǎn)谱醇,一個(gè)頁中所包含的磁盤塊在物理上不一定是相鄰的)——即調(diào)用函數(shù) find_get_page(mapping, index) 在page cache中查找所需數(shù)據(jù)。
而address_space中頁表的總數(shù)由nrpages描述步做。
address_space中有一個(gè)host字段副渴,該字段指向其所屬的inode,也就是address_space中的host字段 與 對(duì)應(yīng)inode中的 i_data字段形成互相指向的關(guān)系全度。
基樹(Radix樹)
由address_space的知識(shí)中我們知道煮剧,若需要在page cache中找到對(duì)應(yīng)數(shù)據(jù)需要根據(jù)其中的radix_tree_root來找到基樹的root,從而能快速搜索到所需要的值的位置。而在緩存中要求這個(gè)查詢速度需要非趁阒眩快佑颇,如果開銷太大會(huì)抵消緩存的好處。
(Radix樹就類似于傳統(tǒng)的二叉樹菇篡,如下圖)
不過這里就不介紹過多漩符,后期會(huì)專門出一篇Radix樹的詳細(xì)內(nèi)容一喘。
對(duì)于寫操作來說驱还,當(dāng)一個(gè)進(jìn)程調(diào)用write系統(tǒng)調(diào)用的時(shí)候,對(duì)于文件的更新僅僅是被寫到了文件的頁緩存中凸克,相應(yīng)的頁被標(biāo)記為dirty议蟆。
在address_space中查詢對(duì)應(yīng)頁的頁緩存是否存在:如果頁緩存命中,直接把文件內(nèi)容修改寫在頁緩存的頁中萎战。寫文件就結(jié)束了咐容。這時(shí)候文件修改位于頁緩存,并沒有寫回到磁盤文件中去蚂维;
如果頁緩存缺失戳粒,那么產(chǎn)生一個(gè)頁缺失異常,創(chuàng)建一個(gè)頁緩存頁虫啥,同時(shí)通過inode找到該文件頁的磁盤地址蔚约,讀取相應(yīng)的頁填充頁緩存。
由于緩存涂籽,寫操作實(shí)際上會(huì)被延遲苹祟。而那些在頁緩存中修改過的數(shù)據(jù)叫做臟數(shù)據(jù),而這些數(shù)據(jù)必須要被寫回磁盤评雌,那什么時(shí)候?qū)懙模?/p>
1 當(dāng)空閑內(nèi)存<閾值树枫;
2 臟頁停留時(shí)間>一定閾值時(shí);
這里就需要調(diào)用Flusher線程進(jìn)行刷新操作了景东;而操作系統(tǒng)會(huì)定時(shí)調(diào)用這個(gè)線程砂轻。
Mmap
在一次文件讀取的過程中,必須將文件的內(nèi)容從頁緩存拷貝到用戶的空間斤吐。這個(gè)過程和缺頁異常(通過DMA調(diào)入需要的頁)不一樣搔涝,這個(gè)拷貝過程需要通過CPU進(jìn)行,因此浪費(fèi)了CPU的時(shí)間曲初。另一個(gè)弊端就是浪費(fèi)了物理內(nèi)存体谒,因?yàn)樾枰獮橥瑯拥臄?shù)據(jù)在內(nèi)存中維護(hù)兩個(gè)副本,如下圖render進(jìn)程的heap所對(duì)應(yīng)的堆中的數(shù)據(jù)和頁緩存中的數(shù)據(jù)存在重復(fù)臼婆,并且如果系統(tǒng)中有多個(gè)這樣的進(jìn)程的話抒痒,那么需要為每個(gè)進(jìn)程維護(hù)同樣的一份數(shù)據(jù)副本,嚴(yán)重浪費(fèi)了CPU的時(shí)間和物理內(nèi)存空間颁褂。
于是此處需要Mmap方案故响。通過mmap傀广,進(jìn)程不但可以直接操作文件對(duì)應(yīng)的物理內(nèi)存,減少從內(nèi)核空間到用戶空間的數(shù)據(jù)復(fù)制過程彩届,同時(shí)可以和別的進(jìn)程共享頁緩存中的數(shù)據(jù)伪冰,達(dá)到節(jié)約內(nèi)存的作用。(如下圖中就存在多余的數(shù)據(jù)樟蠕,浪費(fèi)了空間)
對(duì)于寫操作來說贮聂,普通的IO操作需要將寫的數(shù)據(jù)從自己的進(jìn)程地址空間復(fù)制到頁緩存中,完成對(duì)頁緩存的寫入寨辩;但是mmap通過虛擬地址可以直接完成對(duì)頁緩存的寫入吓懈,減少了從用戶空間到頁緩存的復(fù)制。
普通文件IO中所有的文件內(nèi)容的讀让夷(無論一開始是命中頁緩存還是沒有命中頁緩存)最終都是直接來源于頁緩存耻警。當(dāng)將數(shù)據(jù)通過缺頁中斷從磁盤復(fù)制到頁緩存之后,還要將頁緩沖的數(shù)據(jù)通過CPU復(fù)制到read調(diào)用提供的緩沖區(qū)中甸怕。這樣甘穿,必須通過兩次數(shù)據(jù)拷貝過程,才能完成用戶進(jìn)程對(duì)文件內(nèi)容的獲取任務(wù)梢杭。寫操作也是一樣的温兼,待寫入的buffer在用戶空間,必須將其先拷貝到內(nèi)核空間對(duì)應(yīng)的主存中式曲,再寫回到磁盤中妨托,也是需要兩次數(shù)據(jù)拷貝。mmap的使用減少了數(shù)據(jù)從用戶空間到頁緩存的復(fù)制過程吝羞,提高了IO的效率兰伤,尤其是對(duì)于大文件而言;對(duì)于比較小的文件而言钧排,由于mmap執(zhí)行了更多的內(nèi)核操作敦腔,因此其效率可能比普通的文件IO更差。
?
mmap和常規(guī)文件操作的區(qū)別
我們首先簡單的回顧一下常規(guī)文件系統(tǒng)操作(調(diào)用read/fread等類函數(shù))中恨溜,函數(shù)的調(diào)用過程:
1符衔、進(jìn)程發(fā)起讀文件請(qǐng)求。
2糟袁、內(nèi)核通過查找進(jìn)程文件符表判族,定位到內(nèi)核已打開文件集上的文件信息,從而找到此文件的inode项戴。
3形帮、inode在address_space上查找要請(qǐng)求的文件頁是否已經(jīng)緩存在頁緩存中。如果存在,則直接返回文件頁的內(nèi)容辩撑。
4界斜、如果不存在,則通過inode定位到文件磁盤地址合冀,將數(shù)據(jù)從磁盤復(fù)制到頁緩存各薇。之后再次發(fā)起讀頁面過程,進(jìn)而將頁緩存中的數(shù)據(jù)發(fā)給用戶進(jìn)程君躺。
總結(jié)來說峭判,常規(guī)文件操作為了提高讀寫效率和保護(hù)磁盤,使用了頁緩存機(jī)制晰洒。這樣造成讀文件時(shí)需要先將文件頁從磁盤拷貝到頁緩存中朝抖,由于頁緩存處在內(nèi)核空間,不能被用戶進(jìn)程直接尋址谍珊,所以還需要將頁緩存中數(shù)據(jù)頁再次拷貝到內(nèi)存對(duì)應(yīng)的用戶空間中。這樣急侥,通過了兩次數(shù)據(jù)拷貝過程砌滞,才能完成進(jìn)程對(duì)文件內(nèi)容的獲取任務(wù)。寫操作也是一樣坏怪,待寫入的buffer在內(nèi)核空間不能直接訪問贝润,必須要先拷貝至內(nèi)核空間對(duì)應(yīng)的主存,再寫回磁盤中(延遲寫回)铝宵,也是需要兩次數(shù)據(jù)拷貝打掘。
而使用mmap操作文件中,創(chuàng)建新的虛擬內(nèi)存區(qū)域和建立文件磁盤地址和虛擬內(nèi)存區(qū)域映射這兩步鹏秋,沒有任何文件拷貝操作尊蚁。而之后訪問數(shù)據(jù)時(shí)發(fā)現(xiàn)內(nèi)存中并無數(shù)據(jù)而發(fā)起的缺頁異常過程,可以通過已經(jīng)建立好的映射關(guān)系侣夷,只使用一次數(shù)據(jù)拷貝横朋,就從磁盤中將數(shù)據(jù)傳入內(nèi)存的用戶空間中,供進(jìn)程使用百拓。
總而言之琴锭,常規(guī)文件操作需要從磁盤到頁緩存再到用戶主存的兩次數(shù)據(jù)拷貝。而mmap操控文件衙传,只需要從磁盤到用戶主存的一次數(shù)據(jù)拷貝過程决帖。說白了,mmap的關(guān)鍵點(diǎn)是實(shí)現(xiàn)了用戶空間和內(nèi)核空間的數(shù)據(jù)直接交互而省去了空間不同數(shù)據(jù)不同的繁瑣過程蓖捶。因此mmap效率更高地回。
文章在閱讀操作系統(tǒng)相關(guān)數(shù)據(jù)的基礎(chǔ)上還參考了一些blog:
https://blog.csdn.net/icycode/article/details/80211207#flush%E5%86%85%E6%A0%B8%E7%BA%BF%E7%A8%8B
https://manybutfinite.com/post/page-cache-the-affair-between-memory-and-files/
https://blog.csdn.net/gdj0001/article/details/80136364
內(nèi)核這個(gè)東西,真的有點(diǎn)復(fù)雜。學(xué)習(xí)與總結(jié)都是需要慢慢進(jìn)行落君。一天過去了穿香,我也就學(xué)了這點(diǎn)東西。效率還是要提高绎速。歡迎批評(píng)指正