理解內(nèi)存中的Buffer和Cache

上一節(jié),我們梳理了 Linux 內(nèi)存管理的基本原理,并學(xué)會了用 free 和 top 等工具,來查看系統(tǒng)和進(jìn)程的內(nèi)存使用情況。

內(nèi)存和 CPU 的關(guān)系非常緊密,而內(nèi)存管理本身也是很復(fù)雜的機(jī)制,所以感覺知識很硬核、很難啃,都是正常的。但還是那句話,初學(xué)時不用非得理解所有內(nèi)容,繼續(xù)往后學(xué),多理解相關(guān)的概念并配合一定的實(shí)踐之后,再回頭復(fù)習(xí)往往會容易不少。當(dāng)然,基本功不容放棄。

在今天的內(nèi)容開始之前,我們先來回顧一下系統(tǒng)的內(nèi)存使用情況,比如下面這個 free 輸出界面:


# 注意不同版本的free輸出可能會有所不同
$ free
              total        used        free      shared  buff/cache   available
Mem:        8169348      263524     6875352         668     1030472     7611064
Swap:             0           0           0

顯然,這個界面包含了物理內(nèi)存 Mem 和交換分區(qū) Swap 的具體使用情況,比如總內(nèi)存、已用內(nèi)存、緩存、可用內(nèi)存等。其中緩存是 Buffer 和 Cache 兩部分的總和 。

這里的大部分指標(biāo)都比較容易理解,但 Buffer 和 Cache 可能不太好區(qū)分。從字面上來說,Buffer 是緩沖區(qū),而 Cache 是緩存,兩者都是數(shù)據(jù)在內(nèi)存中的臨時存儲。那么,你知道這兩種“臨時存儲”有什么區(qū)別嗎?

注:今天內(nèi)容接下來的部分,Buffer 和 Cache 我會都用英文來表示,避免跟文中的“緩存”一詞混淆。而文中的“緩存”,則通指內(nèi)存中的臨時存儲。

free 數(shù)據(jù)的來源

在我正式講解兩個概念前,你可以先想想,你有沒有什么途徑來進(jìn)一步了解它們?除了中文翻譯直接得到概念,別忘了,Buffer 和 Cache 還是我們用 free 獲得的指標(biāo)。

。用 man 命令查詢 free 的文檔,就可以找到對應(yīng)指標(biāo)的詳細(xì)說明。比如,我們執(zhí)行 man free ,就可以看到下面這個界面。


buffers
              Memory used by kernel buffers (Buffers in /proc/meminfo)

       cache  Memory used by the page cache and slabs (Cached and SReclaimable in /proc/meminfo)

       buff/cache
              Sum of buffers and cache

從 free 的手冊中,你可以看到 buffer 和 cache 的說明。

  1. Buffers 是內(nèi)核緩沖區(qū)用到的內(nèi)存,對應(yīng)的是 /proc/meminfo 中的 Buffers 值。
  2. Cache 是內(nèi)核頁緩存和 Slab 用到的內(nèi)存,對應(yīng)的是 /proc/meminfo 中的 Cached 與 SReclaimable 之和。

這里的說明告訴我們,這些數(shù)值都來自 /proc/meminfo,但更具體的 Buffers、Cached 和 SReclaimable 的含義,還是沒有說清楚。

proc 文件系統(tǒng)

我在前面 CPU 性能模塊就曾經(jīng)提到過,/proc 是 Linux 內(nèi)核提供的一種特殊文件系統(tǒng),是用戶跟內(nèi)核交互的接口。比方說,用戶可以從 /proc 中查詢內(nèi)核的運(yùn)行狀態(tài)和配置選項,查詢進(jìn)程的運(yùn)行狀態(tài)、統(tǒng)計數(shù)據(jù)等,當(dāng)然,你也可以通過 /proc 來修改內(nèi)核的配置。

proc 文件系統(tǒng)同時也是很多性能工具的最終數(shù)據(jù)來源。比如我們剛才看到的 free ,就是通過讀取 /proc/meminfo ,得到內(nèi)存的使用情況。

繼續(xù)說回 /proc/meminfo,既然 Buffers、Cached、SReclaimable 這幾個指標(biāo)不容易理解,那我們還得繼續(xù)查 proc 文件系統(tǒng),獲取它們的詳細(xì)定義。

執(zhí)行 man proc ,你就可以得到 proc 文件系統(tǒng)的詳細(xì)文檔。

注意這個文檔比較長,你最好搜索一下(比如搜索 meminfo),以便更快定位到內(nèi)存部分。


Buffers %lu
    Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB or so).

Cached %lu
   In-memory cache for files read from the disk (the page cache).  Doesn't include SwapCached.
...
SReclaimable %lu (since Linux 2.6.19)
    Part of Slab, that might be reclaimed, such as caches.
    
SUnreclaim %lu (since Linux 2.6.19)
    Part of Slab, that cannot be reclaimed on memory pressure.

通過這個文檔,我們可以看到:

  • Buffers 是對原始磁盤塊的臨時存儲,也就是用來緩存磁盤的數(shù)據(jù),通常不會特別大(20MB 左右)。這樣,內(nèi)核就可以把分散的寫集中起來,統(tǒng)一優(yōu)化磁盤的寫入,比如可以把多次小的寫合并成單次大的寫等等。
  • Cached 是從磁盤讀取文件的頁緩存,也就是用來緩存從文件讀取的數(shù)據(jù)。這樣,下次訪問這些文件數(shù)據(jù)時,就可以直接從內(nèi)存中快速獲取,而不需要再次訪問緩慢的磁盤。
  • SReclaimable 是 Slab 的一部分。Slab 包括兩部分,其中的可回收部分,用 SReclaimable 記錄;而不可回收部分,用 SUnreclaim 記錄。

好了,我們終于找到了這三個指標(biāo)的詳細(xì)定義。到這里,你是不是長舒一口氣,滿意地想著,總算弄明白 Buffer 和 Cache 了。不過,知道這個定義就真的理解了嗎?這里我給你提了兩個問題,你先想想能不能回答出來。

第一個問題,Buffer 的文檔沒有提到這是磁盤讀數(shù)據(jù)還是寫數(shù)據(jù)的緩存,而在很多網(wǎng)絡(luò)搜索的結(jié)果中都會提到 Buffer 只是對將要寫入磁盤數(shù)據(jù)的緩存。那反過來說,它會不會也緩存從磁盤中讀取的數(shù)據(jù)呢?

第二個問題,文檔中提到,Cache 是對從文件讀取數(shù)據(jù)的緩存,那么它是不是也會緩存寫文件的數(shù)據(jù)呢?

為了解答這兩個問題,接下來,我將用幾個案例來展示, Buffer 和 Cache 在不同場景下的使用情況。

案例

預(yù)先安裝 sysstat 包,如 apt install sysstat。

之所以要安裝 sysstat ,是因為我們要用到 vmstat ,來觀察 Buffer 和 Cache 的變化情況。雖然從 /proc/meminfo 里也可以讀到相同的結(jié)果,但畢竟還是 vmstat 的結(jié)果更加直觀。

另外,這幾個案例使用了 dd 來模擬磁盤和文件的 I/O,所以我們也需要觀測 I/O 的變化情況。

上面的工具安裝完成后,你可以打開兩個終端,連接到機(jī)器上。

準(zhǔn)備環(huán)節(jié)的最后一步,為了減少緩存的影響,記得在第一個終端中,運(yùn)行下面的命令來清理系統(tǒng)緩存:


# 清理文件頁、目錄項、Inodes等各種緩存
$ echo 3 > /proc/sys/vm/drop_caches

這里的 /proc/sys/vm/drop_caches ,就是通過 proc 文件系統(tǒng)修改內(nèi)核行為的一個示例,寫入 3 表示清理文件頁、目錄項、Inodes 等各種緩存。這幾種緩存的區(qū)別你暫時不用管,后面我們都會講到。

場景 1:磁盤和文件寫案例

我們先來模擬第一個場景。首先,在第一個終端,運(yùn)行下面這個 vmstat 命令:


# 每隔1秒輸出1組數(shù)據(jù)
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
0  0      0 7743608   1112  92168    0    0     0     0   52  152  0  1 100  0  0
 0  0      0 7743608   1112  92168    0    0     0     0   36   92  0  0 100  0  0

輸出界面里, 內(nèi)存部分的 buff 和 cache ,以及 io 部分的 bi 和 bo 就是我們要關(guān)注的重點(diǎn)。

  • buff 和 cache 就是我們前面看到的 Buffers 和 Cache,單位是 KB。
  • bi 和 bo 則分別表示塊設(shè)備讀取和寫入的大小,單位為塊 / 秒。因為 Linux 中塊的大小是 1KB,所以這個單位也就等價于 KB/s。

接下來,到第二個終端執(zhí)行 dd 命令,通過讀取隨機(jī)設(shè)備,生成一個 500MB 大小的文件:


$ dd if=/dev/urandom of=/tmp/file bs=1M count=500

然后再回到第一個終端,觀察 Buffer 和 Cache 的變化情況:


procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
0  0      0 7499460   1344 230484    0    0     0     0   29  145  0  0 100  0  0
 1  0      0 7338088   1752 390512    0    0   488     0   39  558  0 47 53  0  0
 1  0      0 7158872   1752 568800    0    0     0     4   30  376  1 50 49  0  0
 1  0      0 6980308   1752 747860    0    0     0     0   24  360  0 50 50  0  0
 0  0      0 6977448   1752 752072    0    0     0     0   29  138  0  0 100  0  0
 0  0      0 6977440   1760 752080    0    0     0   152   42  212  0  1 99  1  0
...
 0  1      0 6977216   1768 752104    0    0     4 122880   33  234  0  1 51 49  0
 0  1      0 6977440   1768 752108    0    0     0 10240   38  196  0  0 50 50  0

通過觀察 vmstat 的輸出,我們發(fā)現(xiàn),在 dd 命令運(yùn)行時, Cache 在不停地增長,而 Buffer 基本保持不變。

再進(jìn)一步觀察 I/O 的情況,你會看到,

  • 在 Cache 剛開始增長時,塊設(shè)備 I/O 很少,bi 只出現(xiàn)了一次 488 KB/s,bo 則只有一次 4KB。而過一段時間后,才會出現(xiàn)大量的塊設(shè)備寫,比如 bo 變成了 122880。
  • 當(dāng) dd 命令結(jié)束后,Cache 不再增長,但塊設(shè)備寫還會持續(xù)一段時間,并且,多次 I/O 寫的結(jié)果加起來,才是 dd 要寫的 500M 的數(shù)據(jù)。

把這個結(jié)果,跟我們剛剛了解到的 Cache 的定義做個對比,你可能會有點(diǎn)暈乎。為什么前面文檔上說 Cache 是文件讀的頁緩存,怎么現(xiàn)在寫文件也有它的份?

這個疑問,我們暫且先記下來,接著再來看另一個磁盤寫的案例。兩個案例結(jié)束后,我們再統(tǒng)一進(jìn)行分析。

不過,對于接下來的案例,我必須強(qiáng)調(diào)一點(diǎn):

下面的命令對環(huán)境要求很高,需要你的系統(tǒng)配置多塊磁盤,并且磁盤分區(qū) /dev/sdb1 還要處于未使用狀態(tài)。如果你只有一塊磁盤,千萬不要嘗試,否則將會對你的磁盤分區(qū)造成損壞。

如果你的系統(tǒng)符合標(biāo)準(zhǔn),就可以繼續(xù)在第二個終端中,運(yùn)行下面的命令。清理緩存后,向磁盤分區(qū) /dev/sdb1 寫入 2GB 的隨機(jī)數(shù)據(jù):


# 首先清理緩存
$ echo 3 > /proc/sys/vm/drop_caches
# 然后運(yùn)行dd命令向磁盤分區(qū)/dev/sdb1寫入2G數(shù)據(jù)
$ dd if=/dev/urandom of=/dev/sdb1 bs=1M count=2048

然后,再回到終端一,觀察內(nèi)存和 I/O 的變化情況:


procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
1  0      0 7584780 153592  97436    0    0   684     0   31  423  1 48 50  2  0
 1  0      0 7418580 315384 101668    0    0     0     0   32  144  0 50 50  0  0
 1  0      0 7253664 475844 106208    0    0     0     0   20  137  0 50 50  0  0
 1  0      0 7093352 631800 110520    0    0     0     0   23  223  0 50 50  0  0
 1  1      0 6930056 790520 114980    0    0     0 12804   23  168  0 50 42  9  0
 1  0      0 6757204 949240 119396    0    0     0 183804   24  191  0 53 26 21  0
 1  1      0 6591516 1107960 123840    0    0     0 77316   22  232  0 52 16 33  0

從這里你會看到,雖然同是寫數(shù)據(jù),寫磁盤跟寫文件的現(xiàn)象還是不同的。寫磁盤時(也就是 bo 大于 0 時),Buffer 和 Cache 都在增長,但顯然 Buffer 的增長快得多。

這說明,寫磁盤用到了大量的 Buffer,這跟我們在文檔中查到的定義是一樣的。

對比兩個案例,我們發(fā)現(xiàn),寫文件時會用到 Cache 緩存數(shù)據(jù),而寫磁盤則會用到 Buffer 來緩存數(shù)據(jù)。所以,回到剛剛的問題,雖然文檔上只提到,Cache 是文件讀的緩存,但實(shí)際上,Cache 也會緩存寫文件時的數(shù)據(jù)。

場景 2:磁盤和文件讀案例

了解了磁盤和文件寫的情況,我們再反過來想,磁盤和文件讀的時候,又是怎樣的呢?

我們回到第二個終端,運(yùn)行下面的命令。清理緩存后,從文件 /tmp/file 中,讀取數(shù)據(jù)寫入空設(shè)備:


# 首先清理緩存
$ echo 3 > /proc/sys/vm/drop_caches
# 運(yùn)行dd命令讀取文件數(shù)據(jù)
$ dd if=/tmp/file of=/dev/null

然后,再回到終端一,觀察內(nèi)存和 I/O 的變化情況:


procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  1      0 7724164   2380 110844    0    0 16576     0   62  360  2  2 76 21  0
 0  1      0 7691544   2380 143472    0    0 32640     0   46  439  1  3 50 46  0
 0  1      0 7658736   2380 176204    0    0 32640     0   54  407  1  4 50 46  0
 0  1      0 7626052   2380 208908    0    0 32640    40   44  422  2  2 50 46  0

觀察 vmstat 的輸出,你會發(fā)現(xiàn)讀取文件時(也就是 bi 大于 0 時),Buffer 保持不變,而 Cache 則在不停增長。這跟我們查到的定義“Cache 是對文件讀的頁緩存”是一致的。

那么,磁盤讀又是什么情況呢?我們再運(yùn)行第二個案例來看看。

首先,回到第二個終端,運(yùn)行下面的命令。清理緩存后,從磁盤分區(qū) /dev/sda1 中讀取數(shù)據(jù),寫入空設(shè)備:


# 首先清理緩存
$ echo 3 > /proc/sys/vm/drop_caches
# 運(yùn)行dd命令讀取文件
$ dd if=/dev/sda1 of=/dev/null bs=1M count=1024

然后,再回到終端一,觀察內(nèi)存和 I/O 的變化情況:


procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
0  0      0 7225880   2716 608184    0    0     0     0   48  159  0  0 100  0  0
 0  1      0 7199420  28644 608228    0    0 25928     0   60  252  0  1 65 35  0
 0  1      0 7167092  60900 608312    0    0 32256     0   54  269  0  1 50 49  0
 0  1      0 7134416  93572 608376    0    0 32672     0   53  253  0  0 51 49  0
 0  1      0 7101484 126320 608480    0    0 32748     0   80  414  0  1 50 49  0

觀察 vmstat 的輸出,你會發(fā)現(xiàn)讀磁盤時(也就是 bi 大于 0 時),Buffer 和 Cache 都在增長,但顯然 Buffer 的增長快很多。這說明讀磁盤時,數(shù)據(jù)緩存到了 Buffer 中。

當(dāng)然,我想,經(jīng)過上一個場景中兩個案例的分析,你自己也可以對比得出這個結(jié)論:讀文件時數(shù)據(jù)會緩存到 Cache 中,而讀磁盤時數(shù)據(jù)會緩存到 Buffer 中。

到這里你應(yīng)該發(fā)現(xiàn)了,雖然文檔提供了對 Buffer 和 Cache 的說明,但是仍不能覆蓋到所有的細(xì)節(jié)。比如說,今天我們了解到的這兩點(diǎn):

  • Buffer 既可以用作“將要寫入磁盤數(shù)據(jù)的緩存”,也可以用作“從磁盤讀取數(shù)據(jù)的緩存”。
  • Cache 既可以用作“從文件讀取數(shù)據(jù)的頁緩存”,也可以用作“寫文件的頁緩存”。

簡單來說,Buffer 是對磁盤數(shù)據(jù)的緩存,而 Cache 是文件數(shù)據(jù)的緩存,它們既會用在讀請求中,也會用在寫請求中。

小結(jié)

今天,我們一起探索了內(nèi)存性能中 Buffer 和 Cache 的詳細(xì)含義。Buffer 和 Cache 分別緩存磁盤和文件系統(tǒng)的讀寫數(shù)據(jù)。

  • 從寫的角度來說,不僅可以優(yōu)化磁盤和文件的寫入,對應(yīng)用程序也有好處,應(yīng)用程序可以在數(shù)據(jù)真正落盤前,就返回去做其他工作。
  • 從讀的角度來說,既可以加速讀取那些需要頻繁訪問的數(shù)據(jù),也降低了頻繁 I/O 對磁盤的壓力。

FAQ

關(guān)于磁盤和文件的區(qū)別,本來以為大家都懂了,所以沒有細(xì)講。磁盤是一個塊設(shè)備,可以劃分為不同的分區(qū);在分區(qū)之上再創(chuàng)建文件系統(tǒng),掛載到某個目錄,之后才可以在這個目錄中讀寫文件。

其實(shí) Linux 中“一切皆文件”,而文章中提到的“文件”是普通文件,磁盤是塊設(shè)備文件,這些大家可以執(zhí)行 "ls -l <路徑>" 查看它們的區(qū)別(輸出的含義如果不懂請 man ls 查詢)。

在讀寫普通文件時,會經(jīng)過文件系統(tǒng),由文件系統(tǒng)負(fù)責(zé)與磁盤交互;而讀寫磁盤或者分區(qū)時,就會跳過文件系統(tǒng),也就是所謂的“裸I/O“。這兩種讀寫方式所使用的緩存是不同的,也就是文中所講的 Cache 和 Buffer 區(qū)別。

理論上,一個文件讀首先到Block Buffer, 然后到Page Cache。有了文件系統(tǒng)才有了Page Cache.
在老的Linux上這兩個Cache是分開的。那這樣對于文件數(shù)據(jù),會被Cache兩次。這種方案雖然簡單,
但低效。后期Linux把這兩個Cache統(tǒng)一了。對于文件,Page Cache指向Block Buffer,對于非文件
則是Block Buffer。這樣就如文件實(shí)驗的結(jié)果,文件操作,只影響Page Cache,Raw操作,則只影響B(tài)uffer. 比如一此VM虛擬機(jī),則會越過File System,只接操作 Disk, 常說的Direct IO.

有一個疑惑:
數(shù)據(jù)寫入到page cache。后續(xù)應(yīng)用程序強(qiáng)制刷盤或者系統(tǒng)自動刷盤的時候,page cache中的數(shù)據(jù)還會經(jīng)過buffer,然后再到塊設(shè)備嗎?還是不會經(jīng)過buffer,直接刷到塊設(shè)備了?

早期的Linux會的,也就是cache兩次。不過現(xiàn)在不會了,只會經(jīng)過一層cache。

“因為 Linux 中塊的大小是 1KB,所以這個單位也就等價于 KB/s?!标P(guān)于這一點(diǎn),想請問下老師,linux block的大小不是4KB呢?

這句話來自vmstat的文檔:https://linux.die.net/man/8/vmstat

通常說的的 Block Size 是磁盤分區(qū)的塊大小,的確都是 4KB 了。

是否繞開文件系統(tǒng),直接對磁盤進(jìn)行讀寫會更快呢?

去掉緩存的話,文件系統(tǒng)比磁盤又多了一層,所以有可能比直接磁盤讀寫慢。但文件系統(tǒng)也有緩存,所以大部分情況下不繞開會更快

怎么能進(jìn)行裸io呢,我什么操作都是在文件系統(tǒng)之上。分區(qū)格式化想的時候是不是裸io

跳過文件系統(tǒng),直接讀寫磁盤就是裸io,比如dd

這章聽得暈。第一次聽讀寫磁盤 和 讀寫文件,之前只聽過讀寫文件。至今不清楚啥算讀寫磁盤?謝謝

比如直接讀寫 /dev/sda 而不是讀寫它掛載之后某個目錄中的文件

總結(jié)要點(diǎn):

  • Buffer 既可以用作“將要寫入磁盤數(shù)據(jù)的緩存”,也可以用作“從磁盤讀取數(shù)據(jù)的緩存”。
  • Cache 既可以用作“從文件讀取數(shù)據(jù)的頁緩存”,也可以用作“寫文件的頁緩存”。
  • 簡單來說,Buffer 是對磁盤數(shù)據(jù)的緩存,而 Cache 是文件數(shù)據(jù)的緩存,它們既會用在讀請求中,也會用在寫請求中。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容