自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

頁(yè)面緩存-內(nèi)存與文件的那些事

存儲(chǔ) 存儲(chǔ)軟件
提到文件,操作系統(tǒng)必須解決兩個(gè)重要的問(wèn)題。首先是硬盤驅(qū)動(dòng)器的存取速度緩慢得令人頭疼(相對(duì)于內(nèi)存而言),尤其是磁盤的尋道性能。

 這次我們的討論將涵蓋非常重要且常被誤解的文件與內(nèi)存間關(guān)系的問(wèn)題,以及它對(duì)系統(tǒng)性能的影響。

提到文件,操作系統(tǒng)必須解決兩個(gè)重要的問(wèn)題。首先是硬盤驅(qū)動(dòng)器的存取速度緩慢得令人頭疼(相對(duì)于內(nèi)存而言),尤其是磁盤的尋道性能。第二個(gè)是要滿足'一次性加載文件內(nèi)容到物理內(nèi)存并在程序間共享'的需求。如果你使用進(jìn)程瀏覽器翻看Windows進(jìn)程,就會(huì)發(fā)現(xiàn)大約15MB的共享DLL被加載進(jìn)了每一個(gè)進(jìn)程。我目前的Windows系統(tǒng)就運(yùn)行了100個(gè)進(jìn)程,如果沒(méi)有共享機(jī)制,那將消耗大約1.5GB的物理內(nèi)存僅僅用于存放公用DLL。這可不怎么好。同樣的,幾乎所有的Linux程序都需要ld.so和libc,以及其它的公用函數(shù)庫(kù)。

[[270622]]

令人愉快的是,這兩個(gè)問(wèn)題可以被一石二鳥(niǎo)的解決:頁(yè)面緩存(page cache),內(nèi)核用它保存與頁(yè)面同等大小的文件數(shù)據(jù)塊。為了展示頁(yè)面緩存,我需要祭出一個(gè)名叫render的Linux程序,它會(huì)打開(kāi)一個(gè)scene.dat文件,每次讀取其中的512字節(jié),并將這些內(nèi)容保存到一個(gè)建立在堆上的內(nèi)存塊中。***的讀取是這樣的:

 

頁(yè)面緩存-內(nèi)存與文件的那些事

 

在讀取了12KB以后,render的堆以及相關(guān)的頁(yè)幀情況如下:

頁(yè)面緩存-內(nèi)存與文件的那些事

這看起來(lái)很簡(jiǎn)單,但還有很多事情會(huì)發(fā)生。首先,即使這個(gè)程序只調(diào)用了常規(guī)的read函數(shù),此時(shí)也會(huì)有三個(gè) 4KB的頁(yè)幀存儲(chǔ)在頁(yè)面緩存當(dāng)中,它們持有scene.dat的一部分?jǐn)?shù)據(jù)。盡管有時(shí)這令人驚訝,但的確所有的常規(guī)文件I/O都是通過(guò)頁(yè)面緩存來(lái)進(jìn)行的。在x86 Linux里,內(nèi)核將文件看作是4KB大小的數(shù)據(jù)塊的序列。即使你只從文件讀取一個(gè)字節(jié),包含此字節(jié)的整個(gè)4KB數(shù)據(jù)塊都會(huì)被讀取,并放入到頁(yè)面緩存當(dāng)中。這樣做是有道理的,因?yàn)榇疟P的持續(xù)性數(shù)據(jù)吞吐量很不錯(cuò),而且一般說(shuō)來(lái),程序?qū)τ谖募心硡^(qū)域的讀取都不止幾個(gè)字節(jié)。頁(yè)面緩存知道每一個(gè)4KB數(shù)據(jù)塊在文件中的對(duì)應(yīng)位置,如上圖所示的#0, #1等等。與Linux的頁(yè)面緩存類似,Windows使用256KB的views。

不幸的是,在一個(gè)普通的文件讀取操作中,內(nèi)核必須復(fù)制頁(yè)面緩存的內(nèi)容到一個(gè)用戶緩沖區(qū)中,這不僅消耗CPU時(shí)間,傷害了CPU cache的性能,還因?yàn)榇鎯?chǔ)了重復(fù)信息而浪費(fèi)物理內(nèi)存。如上面每張圖所示,scene.dat的內(nèi)容被保存了兩遍,而且程序的每個(gè)實(shí)例都會(huì)保存一份。至此,我們緩和了磁盤延遲的問(wèn)題,但卻在其余的每個(gè)問(wèn)題上慘敗。內(nèi)存映射文件(memory-mapped files)將***我們走出混亂:

頁(yè)面緩存-內(nèi)存與文件的那些事

當(dāng)你使用文件映射的時(shí)候,內(nèi)核將你的程序的虛擬內(nèi)存頁(yè)直接映射到頁(yè)面緩存上。這將導(dǎo)致一個(gè)顯著的性能提升:《Windows系統(tǒng)編程》指出常規(guī)的文件讀取操作運(yùn)行時(shí)性能改善30%以上;《Unix環(huán)境高級(jí)編程》指出類似的情況也發(fā)生在Linux和Solaris系統(tǒng)上。你還可能因此而節(jié)省下大量的物理內(nèi)存,這依賴于你的程序的具體情況。

和以前一樣,提到性能,實(shí)際測(cè)量才是王道,但是內(nèi)存映射的確值得被程序員們放入工具箱。相關(guān)的API也很漂亮,它提供了像訪問(wèn)內(nèi)存中的字節(jié)一樣的方式來(lái)訪問(wèn)一個(gè)文件,不需要你多操心,也不犧牲代碼的可讀性。回憶一下地址空間、還有那個(gè)在Unix類系統(tǒng)上關(guān)于mmap的實(shí)驗(yàn),Windows下的CreateFileMapping及其在高級(jí)語(yǔ)言中的各種可用封裝。當(dāng)你映射一個(gè)文件時(shí),它的內(nèi)容并不是立刻就被全部放入內(nèi)存的,而是依賴頁(yè)故障(page fault)按需讀取。在獲取了一個(gè)包含所需的文件數(shù)據(jù)的頁(yè)幀后,對(duì)應(yīng)的故障處理函數(shù)會(huì)將你的虛擬內(nèi)存頁(yè)映射到頁(yè)面緩存上。如果所需內(nèi)容不在緩存當(dāng)中,此過(guò)程還將包含磁盤I/O操作。

現(xiàn)在給你出一個(gè)流行的測(cè)試題。想象一下,在***一個(gè)render程序的實(shí)例退出之時(shí),那些保存了scene.dat的頁(yè)面緩存會(huì)被立刻清理嗎?人們通常會(huì)這樣認(rèn)為,但這是個(gè)壞主意。如果你仔細(xì)想想,我們經(jīng)常會(huì)在一個(gè)程序中創(chuàng)建一個(gè)文件,退出,緊接著在第二個(gè)程序中使用這個(gè)文件。頁(yè)面緩存必須能處理此類情況。如果你再多想想,內(nèi)核何必總是要舍棄頁(yè)面緩存中的內(nèi)容呢?記住,磁盤比RAM慢5個(gè)數(shù)量級(jí),因此一個(gè)頁(yè)面緩存的***(hit)就意味著巨大的勝利。只要還有足夠的空閑物理內(nèi)存,緩存就應(yīng)該盡可能保持滿狀態(tài)。所以它與特定的進(jìn)程并不相關(guān),而是一個(gè)系統(tǒng)級(jí)的資源。如果你一周前運(yùn)行過(guò)render,而此時(shí)scene.dat還在緩存當(dāng)中,那真令人高興。這就是為什么內(nèi)核緩存的大小會(huì)穩(wěn)步增加,直到緩存上限。這并非因?yàn)椴僮飨到y(tǒng)是破爛貨,吞噬你的RAM,事實(shí)上這是種好的行為,反而釋放物理內(nèi)存才是一種浪費(fèi)。緩存要利用得越充分越好。

由于使用了頁(yè)面緩存體系結(jié)構(gòu),當(dāng)一個(gè)程序調(diào)用write()時(shí),相關(guān)的字節(jié)被簡(jiǎn)單的復(fù)制到頁(yè)面緩存中,并且將頁(yè)面標(biāo)記為臟的(dirty)。磁盤I/O一般不會(huì)立刻發(fā)生,因此你的程序的執(zhí)行不會(huì)被打斷去等待磁盤設(shè)備。這樣做的缺點(diǎn)是,如果此時(shí)計(jì)算機(jī)死機(jī),那么你寫入的數(shù)據(jù)將不會(huì)被記錄下來(lái)。因此重要的文件,比如數(shù)據(jù)庫(kù)事務(wù)記錄必須被fsync() (但是還要小心磁盤控制器的緩存)。另一方面,讀取操作一般會(huì)打斷你的程序直到準(zhǔn)備好所需的數(shù)據(jù)。內(nèi)核通常采用積極加載(eager loading)的方式來(lái)緩解這個(gè)問(wèn)題。以提前讀取(read ahead)為例,內(nèi)核會(huì)預(yù)先加載一些頁(yè)到頁(yè)面緩存,并期待你的讀取操作。通過(guò)提示系統(tǒng)即將對(duì)文件進(jìn)行的是順序還是隨機(jī)讀取操作(參看madvise(), readahead(), Windows緩存提示),你可以幫助內(nèi)核調(diào)整它的積極加載行為。Linux的確會(huì)對(duì)內(nèi)存映射文件進(jìn)行預(yù)取,但我不太確定Windows是否也如此。***需要一提的是,你還可以通過(guò)在Linux中使用O_DIRECT或在Windows中使用NO_BUFFERING來(lái)繞過(guò)頁(yè)面緩存,有些數(shù)據(jù)庫(kù)軟件就是這么做的。

一個(gè)文件映射可以是私有的(private)或共享的(shared)。這里的區(qū)別只有在更改(update)內(nèi)存中的內(nèi)容時(shí)才會(huì)顯現(xiàn)出來(lái):在私有映射中,更改并不會(huì)被提交到磁盤或?qū)ζ渌M(jìn)程可見(jiàn),而這在共享的映射中就會(huì)發(fā)生。內(nèi)核使用寫時(shí)拷貝(copy on write)技術(shù),通過(guò)頁(yè)表項(xiàng)(page table entries),實(shí)現(xiàn)私有映射。在下面的例子中,render和另一個(gè)叫render3d的程序(我是不是很有創(chuàng)意?)同時(shí)私有映射了scene.dat。隨后render改寫了映射到此文件的虛擬內(nèi)存區(qū)域:

頁(yè)面緩存-內(nèi)存與文件的那些事

上圖所示的只讀的頁(yè)表項(xiàng)并不意 味著映射是只讀的,它們只是內(nèi)核耍的小把戲,用于共享物理內(nèi)存直到可能的***一刻。你會(huì)發(fā)現(xiàn)'私有'一詞是多么的不恰當(dāng),你只需記住它只在數(shù)據(jù)發(fā)生更改時(shí) 起作用。此設(shè)計(jì)所帶來(lái)的一個(gè)結(jié)果就是,一個(gè)以私有方式映射文件的虛擬內(nèi)存頁(yè)可以觀察到其他進(jìn)程對(duì)此文件的改動(dòng),只要之前對(duì)這個(gè)內(nèi)存頁(yè)進(jìn)行的都是讀取操作。 一旦發(fā)生過(guò)寫時(shí)拷貝,就不會(huì)再觀察到其他進(jìn)程對(duì)此文件的改動(dòng)了。此行為不是內(nèi)核提供的,而是在x86系統(tǒng)上就會(huì)如此。另外,從API的角度來(lái)說(shuō),這也是合理的。與此相反,共享映射只是簡(jiǎn)單的映射到頁(yè)面緩存,僅此而已。對(duì)頁(yè)面的所有更改操作對(duì)其他進(jìn)程都可見(jiàn),而且最終會(huì)執(zhí)行磁盤操作。***,如果此共享映射是只讀的,那么頁(yè)故障將觸發(fā)段錯(cuò)誤(segmentation fault)而不是寫時(shí)拷貝。

被動(dòng)態(tài)加載的函數(shù)庫(kù)通過(guò)文件映射機(jī)制放入到你的程序的地址空間中。這里沒(méi)有任何特別之處,同樣是采用私有文件映射,跟提供給你調(diào)用的常規(guī)API別無(wú)二致。下面的例子展示了兩個(gè)運(yùn)行中的render程序的一部分地址空間,還有物理內(nèi)存。它將我們之前看到的概念都聯(lián)系在了一起。

頁(yè)面緩存-內(nèi)存與文件的那些事
責(zé)任編輯:武曉燕 來(lái)源: 今日頭條
相關(guān)推薦

2018-04-09 08:55:05

LinuxWindows頁(yè)面緩存

2021-05-17 08:18:35

Java內(nèi)存模型JMM

2021-10-19 21:39:51

Unsafe構(gòu)造器內(nèi)存

2013-04-12 09:41:52

MySQL 5.6

2017-12-08 10:20:45

FedoraLinux

2019-11-19 14:48:00

Kafka文件存儲(chǔ)

2022-06-05 13:51:47

SentinelOpenFeign服務(wù)熔斷

2014-06-06 16:08:17

初志科技

2011-09-19 15:40:35

2020-07-29 08:14:59

云計(jì)算云遷移IT

2013-03-12 10:19:20

計(jì)算機(jī)內(nèi)存芯片數(shù)據(jù)

2012-05-31 09:53:38

IT風(fēng)云15年

2011-05-19 16:47:50

軟件測(cè)試

2012-05-01 08:06:49

手機(jī)

2024-02-04 17:03:30

2017-05-15 21:50:54

Linux引號(hào)

2015-08-20 09:17:36

Java線程池

2021-03-18 16:05:20

SSD存儲(chǔ)故障

2021-08-11 21:46:47

MySQL索引join

2012-10-08 11:55:05

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)