Linux的Cache Memory(緩存內存)機制
轉:https://blog.csdn.net/kaikai_sk/article/details/79177036
PS:為什麽Linux系統沒運行多少程序,顯示的可用內存這麽少?其實Linux與Win的內存管理不同,會盡量緩存內存以提高讀寫性能,通常叫做Cache Memory。
為什麽Linux系統沒運行多少程序,顯示的可用內存這麽少?其實Linux與Win的內存管理不同,會盡量緩存內存以提高讀寫性能,通常叫做Cache Memory。
有時候你會發現沒有什麽程序在運行,但是使用top或free命令看到可用內存free項會很少,此時查看系統的 /proc/meminfo 文件,會發現有一項 Cached Memory:
輸入cat /proc/meminfo查看:
MemTotal: 16425996 kB MemFree: 5698808 kB Buffers: 380904 kB Cached: 9389356 kB SwapCached: 212 kB Active: 6569200 kB Inactive: 3725364 kB HighTotal: 0 kB HighFree: 0 kB LowTotal: 16425996 kB LowFree: 5698808 kB SwapTotal: 8273464 kB SwapFree: 8273252 kB Dirty: 980 kB Writeback: 0 kB AnonPages: 524108 kB Mapped: 24568 kB Slab: 381776 kB PageTables: 7496 kB NFS_Unstable: 0 kB Bounce: 0 kB CommitLimit: 16486460 kB Committed_AS: 2143856 kB VmallocTotal: 34359738367 kB VmallocUsed: 267656 kB VmallocChunk: 34359469303 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 Hugepagesize: 2048 kB
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
free命令裏各項內存指標說明:
total used free shared buffers cached
Mem: 16425996 10727220 5698776 0 380904 9389832
-/+ buffers/cache: 956484 15469512
Swap: 8273464 212 8273252
- 1
- 2
- 3
- 4
- 5
其中第一行用全局角度描述系統使用的內存狀況:
total——總物理內存
used——已使用內存,一般情況這個值會比較大,因為這個值包括了cache+應用程序使用的內存
free——完全未被使用的內存
shared——應用程序共享內存
buffers——緩存,主要用於目錄方面,inode值等(ls大目錄可看到這個值增加)
cached——緩存,用於已打開的文件
總結:
total=used+free
used=buffers+cached (maybe add shared also)
第二行描述應用程序的內存使用:
前個值表示-buffers/cache——應用程序使用的內存大小,used減去緩存值
後個值表示+buffers/cache——所有可供應用程序使用的內存大小,free加上緩存值
總結:
-buffers/cache=used-buffers-cached
+buffers/cache=free+buffers+cached
第三行表示swap的使用:
used——已使用
free——未使用
什麽是Cache Memory(緩存內存):
當你讀寫文件的時候,Linux內核為了提高讀寫性能與速度,會將文件在內存中進行緩存,這部分內存就是Cache Memory(緩存內存)。即使你的程序運行結束後,Cache Memory也不會自動釋放。這就會導致你在Linux系統中程序頻繁讀寫文件後,你會發現可用物理內存會很少。
其實這緩存內存(Cache Memory)在你需要使用內存的時候會自動釋放,所以你不必擔心沒有內存可用。如果你希望手動去釋放Cache Memory也是有辦法的。
如何釋放Cache Memory(緩存內存):
用下面的命令可以釋放Cache Memory:
To free pagecache:
echo 1 > /proc/sys/vm/drop_caches
To free dentries and inodes:
echo 2 > /proc/sys/vm/drop_caches
To free pagecache, dentries and inodes:
echo 3 > /proc/sys/vm/drop_caches
- 1
- 2
- 3
- 4
- 5
- 6
- 7
註意,釋放前最好sync一下,防止丟失數據。
總結:個人經驗認為沒必要手動釋放,這種內存管理方式也是比win優勝的地方之一!因為Linux的內核內存管理機制,一般情況下不需要特意去釋放已經使用的cache。這些cache起來的內容可以提高文件以及磁盤的讀寫速度。
頁面緩存——內存與文件的那些事兒
提到文件,操作系統必須解決兩個重要的問題。首先是硬盤驅動器的存取速度緩慢得令人頭疼(相對於內存而言),尤其是磁盤的尋道性能。第二個是要滿足‘一次性加載文件內容到物理內存並在程序間共享’的需求。如果你使用進程瀏覽器翻看Windows進程,就會發現大約15MB的共享DLL被加載進了每一個進程。我目前的Windows系統就運行了100個進程,如果沒有共享機制,那將消耗大約1.5GB的物理內存僅僅用於存放公用DLL。這可不怎麽好。同樣的,幾乎所有的Linux程序都需要ld.so和libc,以及其它的公用函數庫。
令人愉快的是,這兩個問題可以被一石二鳥的解決:頁面緩存(page cache),內核用它保存與頁面同等大小的文件數據塊。為了展示頁面緩存,我需要祭出一個名叫render的Linux程序,它會打開一個scene.dat文件,每次讀取其中的512字節,並將這些內容保存到一個建立在堆上的內存塊中。首次的讀取是這樣的:
在讀取了12KB以後,render的堆以及相關的頁幀情況如下:
這看起來很簡單,但還有很多事情會發生。首先,即使這個程序只調用了常規的read函數,此時也會有三個 4KB的頁幀存儲在頁面緩存當中,它們持有scene.dat的一部分數據。盡管有時這令人驚訝,但的確所有的常規文件I/O都是通過頁面緩存來進行的。在x86 Linux裏,內核將文件看作是4KB大小的數據塊的序列。即使你只從文件讀取一個字節,包含此字節的整個4KB數據塊都會被讀取,並放入到頁面緩存當中。這樣做是有道理的,因為磁盤的持續性數據吞吐量很不錯,而且一般說來,程序對於文件中某區域的讀取都不止幾個字節。頁面緩存知道每一個4KB數據塊在文件中的對應位置,如上圖所示的#0, #1等等。與Linux的頁面緩存類似,Windows使用256KB的views。
不幸的是,在一個普通的文件讀取操作中,內核必須復制頁面緩存的內容到一個用戶緩沖區中,這不僅消耗CPU時間,傷害了CPU cache的性能,還因為存儲了重復信息而浪費物理內存。如上面每張圖所示,scene.dat的內容被保存了兩遍,而且程序的每個實例都會保存一份。至此,我們緩和了磁盤延遲的問題,但卻在其余的每個問題上慘敗。內存映射文件(memory-mapped files)將引領我們走出混亂:
當你使用文件映射的時候,內核將你的程序的虛擬內存頁直接映射到頁面緩存上。這將導致一個顯著的性能提升:《Windows系統編程》指出常規的文件讀取操作運行時性能改善30%以上;《Unix環境高級編程》指出類似的情況也發生在Linux和Solaris系統上。你還可能因此而節省下大量的物理內存,這依賴於你的程序的具體情況。
和以前一樣,提到性能,實際測量才是王道,但是內存映射的確值得被程序員們放入工具箱。相關的API也很漂亮,它提供了像訪問內存中的字節一樣的方式來訪問一個文件,不需要你多操心,也不犧牲代碼的可讀性。回憶一下地址空間、還有那個在Unix類系統上關於mmap的實驗,Windows下的CreateFileMapping及其在高級語言中的各種可用封裝。當你映射一個文件時,它的內容並不是立刻就被全部放入內存的,而是依賴頁故障(page fault)按需讀取。在獲取了一個包含所需的文件數據的頁幀後,對應的故障處理函數會將你的虛擬內存頁映射到頁面緩存上。如果所需內容不在緩存當中,此過程還將包含磁盤I/O操作。
現在給你出一個流行的測試題。想象一下,在最後一個render程序的實例退出之時,那些保存了scene.dat的頁面緩存會被立刻清理嗎?人們通常會這樣認為,但這是個壞主意。如果你仔細想想,我們經常會在一個程序中創建一個文件,退出,緊接著在第二個程序中使用這個文件。頁面緩存必須能處理此類情況。如果你再多想想,內核何必總是要舍棄頁面緩存中的內容呢?記住,磁盤比RAM慢5個數量級,因此一個頁面緩存的命中(hit)就意味著巨大的勝利。只要還有足夠的空閑物理內存,緩存就應該盡可能保持滿狀態。所以它與特定的進程並不相關,而是一個系統級的資源。如果你一周前運行過render,而此時scene.dat還在緩存當中,那真令人高興。這就是為什麽內核緩存的大小會穩步增加,直到緩存上限。這並非因為操作系統是破爛貨,吞噬你的RAM,事實上這是種好的行為,反而釋放物理內存才是一種浪費。緩存要利用得越充分越好。
由於使用了頁面緩存體系結構,當一個程序調用write()時,相關的字節被簡單的復制到頁面緩存中,並且將頁面標記為臟的(dirty)。磁盤I/O一般不會立刻發生,因此你的程序的執行不會被打斷去等待磁盤設備。這樣做的缺點是,如果此時計算機死機,那麽你寫入的數據將不會被記錄下來。因此重要的文件,比如數據庫事務記錄必須被fsync() (但是還要小心磁盤控制器的緩存)。另一方面,讀取操作一般會打斷你的程序直到準備好所需的數據。內核通常采用積極加載(eager loading)的方式來緩解這個問題。以提前讀取(read ahead)為例,內核會預先加載一些頁到頁面緩存,並期待你的讀取操作。通過提示系統即將對文件進行的是順序還是隨機讀取操作(參看madvise(), readahead(), Windows緩存提示),你可以幫助內核調整它的積極加載行為。Linux的確會對內存映射文件進行預取,但我不太確定Windows是否也如此。最後需要一提的是,你還可以通過在Linux中使用O_DIRECT或在Windows中使用NO_BUFFERING來繞過頁面緩存,有些數據庫軟件就是這麽做的。
一個文件映射可以是私有的(private)或共享的(shared)。這裏的區別只有在更改(update)內存中的內容時才會顯現出來:在私有映射中,更改並不會被提交到磁盤或對其他進程可見,而這在共享的映射中就會發生。內核使用寫時拷貝(copy on write)技術,通過頁表項(page table entries),實現私有映射。在下面的例子中,render和另一個叫render3d的程序(我是不是很有創意?)同時私有映射了scene.dat。隨後render改寫了映射到此文件的虛擬內存區域:
上圖所示的只讀的頁表項並不意味著映射是只讀的,它們只是內核耍的小把戲,用於共享物理內存直到可能的最後一刻。你會發現‘私有’一詞是多麽的不恰當,你只需記住它只在數據發生更改時起作用。此設計所帶來的一個結果就是,一個以私有方式映射文件的虛擬內存頁可以觀察到其他進程對此文件的改動,只要之前對這個內存頁進行的都是讀取操作。一旦發生過寫時拷貝,就不會再觀察到其他進程對此文件的改動了。此行為不是內核提供的,而是在x86系統上就會如此。另外,從API的角度來說,這也是合理的。與此相反,共享映射只是簡單的映射到頁面緩存,僅此而已。對頁面的所有更改操作對其他進程都可見,而且最終會執行磁盤操作。最後,如果此共享映射是只讀的,那麽頁故障將觸發段錯誤(segmentation fault)而不是寫時拷貝。
被動態加載的函數庫通過文件映射機制放入到你的程序的地址空間中。這裏沒有任何特別之處,同樣是采用私有文件映射,跟提供給你調用的常規API別無二致。下面的例子展示了兩個運行中的render程序的一部分地址空間,還有物理內存。它將我們之前看到的概念都聯系在了一起。
Linux的Cache Memory(緩存內存)機制