當前位置:首頁 > 公眾號精選 > Linux閱碼場
[導(dǎo)讀]Page?cache和Buffer?cache,?它們到底是什么關(guān)系?

[注: 轉(zhuǎn)載自今日頭條號"閃念基因"] 在我們進行數(shù)據(jù)持久化,對文件內(nèi)容進行落盤處理時,我們時常會使用fsync操作,該操作會將文件關(guān)聯(lián)的臟頁(dirty page)數(shù)據(jù)(實際文件內(nèi)容及元數(shù)據(jù)信息)一同寫回磁盤。這里提到的臟頁(dirty page)即為頁緩存(page cache)。
塊緩存(buffer cache),則是內(nèi)核為了加速對底層存儲介質(zhì)的訪問速度,而構(gòu)建的一層緩存。他緩存部分磁盤數(shù)據(jù),當有磁盤讀取請求時,會首先查看塊緩存中是否有對應(yīng)的數(shù)據(jù),如果有的話,則直接將對應(yīng)數(shù)據(jù)返回,從而減少對磁盤的訪問。兩層緩存各有自己的緩存目標,我好奇的是,這兩者到底是什么關(guān)系。本文主要參考若干kernel資料,對應(yīng)的kernel源碼版本主要包括:linux-0.11, linux-2.2.16, linux-2.4.0, linux-2.4.19, linux-2.6.18

兩類緩存各自的作用

Page Cache

Page Cache以Page為單位,緩存文件內(nèi)容。緩存在Page Cache中的文件數(shù)據(jù),能夠更快的被用戶讀取。同時對于帶buffer的寫入操作,數(shù)據(jù)在寫入到Page Cache中即可立即返回,而不需等待數(shù)據(jù)被實際持久化到磁盤,進而提高了上層應(yīng)用讀寫文件的整體性能。

Buffer Cache

磁盤的最小數(shù)據(jù)單位為sector,每次讀寫磁盤都是以sector為單位對磁盤進行操作。
sector大小跟具體的磁盤類型有關(guān),有的為512Byte, 有的為4K Bytes。無論用戶是希望讀取1個byte,還是10個byte,最終訪問磁盤時,都必須以sector為單位讀取,如果裸讀磁盤,那意味著數(shù)據(jù)讀取的效率會非常低。
同樣,如果用戶希望向磁盤某個位置寫入(更新)1個byte的數(shù)據(jù),他也必須整個刷新一個sector,言下之意,則是在寫入這1個byte之前,我們需要先將該1byte所在的磁盤sector數(shù)據(jù)全部讀出來,在內(nèi)存中,修改對應(yīng)的這1個byte數(shù)據(jù),然后再將整個修改后的sector數(shù)據(jù),一口氣寫入磁盤。
為了降低這類低效訪問,盡可能的提升磁盤訪問性能,內(nèi)核會在磁盤sector上構(gòu)建一層緩存,他以sector的整數(shù)倍力度單位(block),緩存部分sector數(shù)據(jù)在內(nèi)存中,當有數(shù)據(jù)讀取請求時,他能夠直接從內(nèi)存中將對應(yīng)數(shù)據(jù)讀出。當有數(shù)據(jù)寫入時,他可以直接再內(nèi)存中直接更新指定部分的數(shù)據(jù),然后再通過異步方式,把更新后的數(shù)據(jù)寫回到對應(yīng)磁盤的sector中。這層緩存則是塊緩存Buffer Cache。

兩類緩存的邏輯關(guān)系

從linux-2.6.18的內(nèi)核源碼來看, Page Cache和Buffer Cache是一個事物的兩種表現(xiàn):對于一個Page而言,對上,他是某個File的一個Page Cache,而對下,他同樣是一個Device上的一組Buffer Cache 。

File在地址空間上,以4K(page size)為單位進行切分,每一個4k都可能對應(yīng)到一個page上(這里 可能 的含義是指,只有被緩存的部分,才會對應(yīng)到page上,沒有緩存的部分,則不會對應(yīng)),而這個4k的page,就是這個文件的一個Page Cache。而對于落磁盤的一個文件而言,最終,這個4k的page cache,還需要映射到一組磁盤block對應(yīng)的buffer cache上,假設(shè)block為1k,那么每個page cache將對應(yīng)一組(4個)buffer cache,而每一個buffer cache,則有一個對應(yīng)的buffer cache與device block映射關(guān)系的描述符:buffer_head,這個描述符記錄了這個buffer cache對應(yīng)的block在磁盤上的具體位置。

上圖只展示了Page Cache與Buffer Cache(buffer_head),以及對應(yīng)的block之間的關(guān)聯(lián)關(guān)系。而從File的角度來看,要想將數(shù)據(jù)寫入磁盤,第一步,則是需要找到file具體位置對應(yīng)的page cache是哪個page?進而才能將數(shù)據(jù)寫入。而要找到對應(yīng)的page,則依賴于inode結(jié)構(gòu)中的 i_mapping 字段:

該字段為一address_space結(jié)構(gòu),而實際上address_space即為一棵radix tree。簡單來說,radix tree即為一個多級索引結(jié)構(gòu),如果將一個文件的大小,以page為單位來切分,假設(shè)一個文件有N個page,這個N是一個32bit的int,那么,這個32bit的N,可以被切分成若干層級:level-0: [0 - 7bit], level-1:[8 - 15bit], level-2: [16 - 23bit], level-3: [24 - 31bit]。在查找File某個位置對應(yīng)的page是否存在時,則拿著這個page所在的位置N,到對應(yīng)的radix-tree上查找。查找時,首先通過N中的level-0部分,到radix tree上的level-0層級索引上去查找,如果不存在,則直接告知不存在,如果存在,則進一步的,拿著N中的level-1部分,到這個level-0下面對應(yīng)的level-1去查找,一級一級查找。這樣,我們可以看出,最多,在4層索引上查找,就能找到N對應(yīng)的page信息。radix-tree及address_space的詳細描述,可參考[12]、[2]中的說明。這里借用[12]、[2]中的各自一張圖,可能會更好說明radix-tree(address_space)結(jié)構(gòu)的樣子:基本的radix-tree映射結(jié)構(gòu):
對應(yīng)的inode上,i_mapping字段(address_space)對page的映射關(guān)系:

兩類緩存的演進歷史

雖然,目前Linux Kernel代碼中,Page Cache和Buffer Cache實際上是統(tǒng)一的,無論是文件的Page Cache還是Block的Buffer Cache最終都統(tǒng)一到Page上。但是,在閱讀較老代碼時,我們能夠看出,這兩塊緩存的實現(xiàn),原本是完全分開的。
是什么原因使得最終這兩類緩存“走到了一起”?[10]中各位的回答,讓我豁然開來。我試著對這一演進的由來做個梳理。

第一階段:僅有Buffer Cache

在Linux-0.11版本的代碼中,我們會看到,buffer cache是完全獨立的實現(xiàn),甚至都還沒有基于page作為內(nèi)存單元,而是以原始指針的系形式出現(xiàn)。每一個block sector,在kernel內(nèi)部對應(yīng)一個獨立的buffer cache單元,這個buffer cache單元通過buffer head來描述:

其中,buffer_head在初始化時,其內(nèi)部的 b_data 指向的是原始的內(nèi)存地址:
其中,b_data指向具體的buffer cache內(nèi)容,而b_dev和b_blocknr則代表了這塊緩存對應(yīng)的device以及device上的block number信息。kernel通過getblk函數(shù),會將一個指定dev, blocknr sector對應(yīng)的buffer cache單元(buffer header)返回給調(diào)用方。上層讀取、寫入這個buffer_header,最終將會映射到對應(yīng)(dev, blocknr) sector的讀取和寫入。
如果一個對應(yīng)的buffer cache單元(dev, blocknr)已經(jīng)在kernel中分配了,則會通過get_hash_table直接返回給用戶,如果沒有,則會首先創(chuàng)建出對應(yīng)的buffer_header,并將其加入到hash_table中( inser_into_queues ),最終返回給用戶。上層對于文件的讀寫,會轉(zhuǎn)化到對于對應(yīng)buffer_header的讀寫:
file_read時,會先通過f_pos計算出實際位于的dev, blocknr位置,并通過bread獲取到對應(yīng)的buffer_head, 而在此之后,則會通過put_fs_byte完成buffer cache單元中的數(shù)據(jù)向目標buf的數(shù)據(jù)回填(數(shù)據(jù)讀取)。同理,在向文件中寫入數(shù)據(jù)時,也是通過f_pos首先計算出對應(yīng)的dev, blocknr位置信息,進而通過bread拿到對應(yīng)的buffer_head,并向buffer_header對應(yīng)的buffer cache單元中寫入數(shù)據(jù)。
從上面file_read, file_write的實現(xiàn)來看,我們會看到bread返回目標buffer_head,讓上層只操作buffer cache單元,而不再關(guān)心block底層。
而 bread 內(nèi)部則是通過上面提到的getblk函數(shù),返回對應(yīng)的buffer_head,接著執(zhí)行數(shù)據(jù)讀取。

第二階段:Page Cache、Buffer Cache兩者并存

到Linux-2.2版本時,磁盤文件訪問的高速緩沖仍然是緩沖區(qū)高速緩沖(Buffer Cache)。其訪問模式與上面Linux-0.11版本的訪問邏輯基本類似。但此時,Buffer Cache已基于page來分配內(nèi)存,buffer_head內(nèi)部,已經(jīng)有了關(guān)于所在page的一些信息:

同時,從buffer cache的初始化,以及buffer cache不足時新建buffer cache單元的動作,我們也可以看出,此時buffer cache已經(jīng)完全是基于page來分配內(nèi)存。
當buffer cache不足時,通過grow_buffers來新增buffer cache:
并通過create_buffers來完成對buffer_head的初始化構(gòu)造:
以Linux-2.2.16版本的代碼為例,在執(zhí)行磁盤文件寫入時,會通過xxx_getblk獲取對應(yīng)位置的buffer_head信息,并將對應(yīng)的數(shù)據(jù)寫入該buffer中。在此之后,會執(zhí)行一步update_vm_cache,至于為什么會要執(zhí)行這一步,我們后面再來看。
而對于對應(yīng)的文件讀取,則是同樣,先通過xxx_getblk找到對應(yīng)的buffer_head,在此之后,完成對應(yīng)的數(shù)據(jù)讀?。ㄍㄟ^while循環(huán),一口氣將所有目標block的buffer_head拿出來,再一把讀取所有的數(shù)據(jù))。
而xxx_getblk最終,還是使用的getblk 接口來定位到指定的buffer_head :
從上面的描述我們可以看出,此時的buffer cache基于page來分配內(nèi)存,但是與Page Cache完全獨立,一點關(guān)系都沒有。在Linux-2.2版本中,Page Cache此時用來干什么的?(1). 用于文件的mmap:來自[10]:page cache was used to cache pages of files mapped with mmap MAP_FILE among other things.
來自[11]:read() and write() are implemented using the buffer cache. The read() system call reads file data into a buffer cache buffer and then copies it to the application.The mmap() system call, however, has to use the page cache to store its data since the buffer cache memory is not managed by the VM system and thus not cannot be mapped into an application address space. Therefore the file data in the buffer cache is copied into page cache pages, which are then used to satisfy page faults on the application mappings.用于network-based filesytems:來自[1]:Disk-based filesystems do not directly use the page cache for writing to a regular file. This is a heritage from older versions of Linux, in which the only disk cache was the buffer cache. However, network-based filesystems always use the page cache for writing to a regular file.
此時,Page Cache和Buffer Cache的關(guān)系如下圖所示:
Page Cache僅負責其中mmap部分的處理,而Buffer Cache實際上負責所有對磁盤的IO訪問。從上面圖中,我們也可看出其中一個問題:write繞過了Page Cache,這里導(dǎo)致了一個同步問題。當write發(fā)生時,有效數(shù)據(jù)是在Buffer Cache中,而不是在Page Cache中。這就導(dǎo)致mmap訪問的文件數(shù)據(jù)可能存在不一致問題。為了解決這個問題,所有基于磁盤文件系統(tǒng)的write,都需要調(diào)用 update_vm_cache() 函數(shù),該操作會修改write相關(guān)Buffer Cache對應(yīng)的Page Cache。從代碼中我們可以看到,上述sysv_file_write中,在調(diào)用完copy_from_user之后,會調(diào)用update_vm_cache。同樣,正是這樣Page Cache、Buffer Cache分離的設(shè)計,導(dǎo)致基于磁盤的文件,同一份數(shù)據(jù),可能在Page Cache中有一份,而同時,卻還在Buffer Cache中有一份。

第三階段:Page Cache、Buffer Cache兩者融合

介于上述Page Cache、Buffer Cache分離設(shè)計的弊端,Linux-2.4版本中對Page Cache、Buffer Cache的實現(xiàn)進行了融合,融合后的Buffer Cache不再以獨立的形式存在,Buffer Cache的內(nèi)容,直接存在于Page Cache中,同時,保留了對Buffer Cache的描述符單元:buffer_head。
page結(jié)構(gòu)中,通過buffers字段是否為空,來判定這個Page是否與一組Buffer Cache關(guān)聯(lián)(在后續(xù)的演進過程中,這個判斷,轉(zhuǎn)變?yōu)橛?private 字段來判定)。
而對應(yīng)的,buffer_head則增加了字段 b_page ,直接指向?qū)?yīng)的page。
至此,兩者的關(guān)系已經(jīng)相互融合如下圖所示:
一個文件的PageCache(page),通過 buffers 字段能夠非常快捷的確定該page對應(yīng)的buffer_head信息,進而明確該page對應(yīng)的device, block等信息。從邏輯上來看,當針對一個文件的write請求進入內(nèi)核時,會執(zhí)行 generic_file_write ,在這一層,通過inode的address_space結(jié)構(gòu) mapping 會分配一個新的page來作為對應(yīng)寫入的page cache(這里我們假設(shè)是一個新的寫入,且數(shù)據(jù)量僅一個page):__grab_cache_page ,而在分配了內(nèi)存空間page之后,則通過 prepare_write ,來完成對應(yīng)的buffer_head的構(gòu)建。
prepare_write實際執(zhí)行的是:__block_prepare_write ,在其中,會針對該page分配對應(yīng)的buffer_head( create_empty_buffers ),并計算實際寫入的在device上的具體位置:blocknr,進而初始化buffer_head( get_block )。
在create_empty_buffers內(nèi)部,則通過create_buffers以及set_bh_page等一系列操作,將page與buffer_head組織成如前圖所示的通過 buffers 、b_page等相互關(guān)聯(lián)的關(guān)系。
通過create_buffers分配一組串聯(lián)好的buffer_head。
通過set_bh_page將各buffer_head關(guān)聯(lián)到對應(yīng)的page,以及data的具體位置。
正是如上的一系列動作,使得Page Cache與Buffer Cache(buffer_head)相互綁定。對上,在文件讀寫時,以page為單位進行處理。而對下,在數(shù)據(jù)向device進行刷新時,則可以以buffer_head(block)為單位進行處理。在后續(xù)的linux-2.5版本中,引入了bio結(jié)構(gòu)來替換基于buffer_head的塊設(shè)備IO操作。[注意] :這里的Page Cache與Buffer Cache的融合,是針對文件這一層面的Page Cache與Buffer Cache的融合。對于跨層的:File層面的Page Cache和裸設(shè)備Buffer Cache,雖然都統(tǒng)一到了基于Page的實現(xiàn),但File的Page Cache和該文件對應(yīng)的Block在裸設(shè)備層訪問的Buffer Cache,這兩個是完全獨立的Page。這種情況下,一個物理磁盤Block上的數(shù)據(jù),仍然對應(yīng)了Linux內(nèi)核中的兩份Page:一個是通過文件層訪問的File的Page Cache(Page Cache);一個是通過裸設(shè)備層訪問的Page Cache(Buffer Cache)。

參考資料

[1]. Understanding the Linux Kernel[2]. Professional Linux Kernel Architecture[3]. The Art of Linux Kernel Design[4]. Linux Kernel Development[5]. A Heavily Commented Linux Kernel Source Code[6]. Linux內(nèi)核源代碼情景分析[7]. 獨辟蹊徑品內(nèi)核:Linux內(nèi)核源代碼導(dǎo)讀[8]. Linux內(nèi)核的文件Cache管理機制介紹[9]. Linux內(nèi)核文件Cache機制[10]. What is the major difference between the buffer cache and the page cache[11]. UBC: An Efficient Unified I/O and Memory Caching Subsystem for NetBSD[12]. Trees I: Radix trees


本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉