在 Linux 系統中,我們經常用 free 命令來查看系統內存的使用狀態。在一個 RHEL6 的系統上,free 命令的顯示內容大概是這樣一個狀態:
[root@tencent64 ~]# free total used free shared buffers cached Mem: 132256952 72571772 59685180 0 1762632 53034704 -/+ buffers/cache: 17774436 114482516 Swap: 2101192 508 2100684這裡的默認顯示單位是 kb,我的服務器是 128G 內存,所以數字顯得比較大。這個命令幾乎是每一個使用過 Linux 的人必會的命令,但越是這樣的命令,似乎真正明白的人越少(我是說比例越少)。一般情況下,對此命令輸出的理解可以分這幾個層次:
不了解。這樣的人的第一反應是:天啊,內存用了好多,70個多 G,可是我幾乎沒有運行什麼大程序啊?為什麼會這樣? Linux 好占內存!自以為很了解。這樣的人一般評估過會說:嗯,根據我專業的眼光看的出來,內存才用了 17G 左右,還有很多剩余內存可用。buffers/cache 占用的較多,說明系統中有進程曾經讀寫過文件,但是不要緊,這部分內存是當空閒來用的。
真的很了解。這種人的反應反而讓人感覺最不懂 Linux,他們的反應是:free 顯示的是這樣,好吧我知道了。神馬?你問我這些內存夠不夠,我當然不知道啦!我特麼怎麼知道你程序怎麼寫的?根據目前網絡上技術文檔的內容,我相信絕大多數了解一點 Linux 的人應該處在第二種層次。大家普遍認為,buffers 和 cached 所占用的內存空間是可以在內存壓力較大的時候被釋放當做空閒空間用的。但真的是這樣麼?在論證這個題目之前,我們先簡要介紹一下 buffers 和 cached 是什麼意思:
在系統中除了內存將被耗盡的時候可以清緩存以外,我們還可以使用下面這個文件來人工觸發緩存清除的操作:
[root@tencent64 ~]# cat /proc/sys/vm/drop_caches 1方法是:
echo 1 > /proc/sys/vm/drop_caches當然,這個文件可以設置的值分別為1、2、3。它們所表示的含義為:
echo 1 > /proc/sys/vm/drop_caches:表示清除 page cache。
echo 2 > /proc/sys/vm/drop_caches:表示清除回收 slab 分配器中的對象(包括目錄項緩存和 inode 緩存)。slab 分配器是內核中管理內存的一種機制,其中很多緩存數據實現都是用的 page cache。
echo 3 > /proc/sys/vm/drop_caches:表示清除 page cache 和 slab 分配器中的緩存對象。
[root@tencent64 ~]# mkdir /tmp/tmpfs [root@tencent64 ~]# mount -t tmpfs -o size=20G none /tmp/tmpfs/ [root@tencent64 ~]# df Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda1 10325000 3529604 6270916 37% / /dev/sda3 20646064 9595940 10001360 49% /usr/local /dev/mapper/vg-data 103212320 26244284 71725156 27% /data tmpfs 66128476 14709004 51419472 23% /dev/shm none 20971520 0 20971520 0% /tmp/tmpfs於是我們就創建了一個新的 tmpfs,空間是 20G,我們可以在 /tmp/tmpfs 中創建一個 20G 以內的文件。如果我們創建的文件實際占用的空間是內存的話,那麼這些數據應該占用內存空間的什麼部分呢?根據 page cache 的實現功能可以理解,既然是某種文件系統,那麼自然該使用 page cache 的空間來管理。我們試試是不是這樣?
[root@tencent64 ~]# free -g total used free shared buffers cached Mem: 126 36 89 0 1 19 -/+ buffers/cache: 15 111 Swap: 2 0 2 [root@tencent64 ~]# dd if=/dev/zero of=/tmp/tmpfs/testfile bs=1G count=13 13+0 records in 13+0 records out 13958643712 bytes (14 GB) copied, 9.49858 s, 1.5 GB/s [root@tencent64 ~]# [root@tencent64 ~]# free -g total used free shared buffers cached Mem: 126 49 76 0 1 32 -/+ buffers/cache: 15 110 Swap: 2 0 2我們在 tmpfs 目錄下創建了一個 13G 的文件,並通過前後 free 命令的對比發現,cached 增長了 13G,說明這個文件確實放在了內存裡並且內核使用的是 cache 作為存儲。再看看我們關心的指標: -/+ buffers/cache 那一行。我們發現,在這種情況下 free 命令仍然提示我們有 110G 內存可用,但是真的有這麼多麼?我們可以人工觸發內存回收看看現在到底能回收多少內存:
[root@tencent64 ~]# echo 3 > /proc/sys/vm/drop_caches [root@tencent64 ~]# free -g total used free shared buffers cached Mem: 126 43 82 0 0 29 -/+ buffers/cache: 14 111 Swap: 2 0 2可以看到,cached 占用的空間並沒有像我們想象的那樣完全被釋放,其中 13G 的空間仍然被 /tmp/tmpfs 中的文件占用的。當然,我的系統中還有其他不可釋放的 cache 占用著其余16G內存空間。那麼 tmpfs 占用的 cache 空間什麼時候會被釋放呢?是在其文件被刪除的時候。如果不刪除文件,無論內存耗盡到什麼程度,內核都不會自動幫你把 tmpfs 中的文件刪除來釋放cache空間。
[root@tencent64 ~]# rm /tmp/tmpfs/testfile [root@tencent64 ~]# free -g total used free shared buffers cached Mem: 126 30 95 0 0 16 -/+ buffers/cache: 14 111 Swap: 2 0 2這是我們分析的第一種 cache 不能被回收的情況。還有其他情況,比如:
[root@tencent64 ~]# cat shm.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/shm.h> #include <string.h> #define MEMSIZE 2048*1024*1023 int main() { int shmid; char *ptr; pid_t pid; struct shmid_ds buf; int ret; shmid = shmget(IPC_PRIVATE, MEMSIZE, 0600); if (shmid<0) { perror("shmget()"); exit(1); } ret = shmctl(shmid, IPC_STAT, &buf); if (ret < 0) { perror("shmctl()"); exit(1); } printf("shmid: %d\n", shmid); printf("shmsize: %d\n", buf.shm_segsz); buf.shm_segsz *= 2; ret = shmctl(shmid, IPC_SET, &buf); if (ret < 0) { perror("shmctl()"); exit(1); } ret = shmctl(shmid, IPC_SET, &buf); if (ret < 0) { perror("shmctl()"); exit(1); } printf("shmid: %d\n", shmid); printf("shmsize: %d\n", buf.shm_segsz); pid = fork(); if (pid<0) { perror("fork()"); exit(1); } if (pid==0) { ptr = shmat(shmid, NULL, 0); if (ptr==(void*)-1) { perror("shmat()"); exit(1); } bzero(ptr, MEMSIZE); strcpy(ptr, "Hello!"); exit(0); } else { wait(NULL); ptr = shmat(shmid, NULL, 0); if (ptr==(void*)-1) { perror("shmat()"); exit(1); } puts(ptr); exit(0); } }
程序功能很簡單,就是申請一段不到 2G 共享內存,然後打開一個子進程對這段共享內存做一個初始化操作,父進程等子進程初始化完之後輸出一下共享內存的內容,然後退出。但是退出之前並沒有刪除這段共享內存。我們來看看這個程序執行前後的內存使用:
[root@tencent64 ~]# free -g total used free shared buffers cached Mem: 126 30 95 0 0 16 -/+ buffers/cache: 14 111 Swap: 2 0 2 [root@tencent64 ~]# ./shm shmid: 294918 shmsize: 2145386496 shmid: 294918 shmsize: -4194304 Hello! [root@tencent64 ~]# free -g total used free shared buffers cached Mem: 126 32 93 0 0 18 -/+ buffers/cache: 14 111 Swap: 2 0 2cached 空間由 16G 漲到了 18G。那麼這段 cache 能被回收麼?繼續測試:
[root@tencent64 ~]# echo 3 > /proc/sys/vm/drop_caches [root@tencent64 ~]# free -g total used free shared buffers cached Mem: 126 32 93 0 0 18 -/+ buffers/cache: 14 111 Swap: 2 0 2結果是仍然不可回收。大家可以觀察到,這段共享內存即使沒人使用,仍然會長期存放在 cache 中,直到其被刪除。刪除方法有兩種,一種是程序中使用 shmctl() 去 IPC_RMID,另一種是使用 ipcrm 命令。我們來刪除試試:
[root@tencent64 ~]# ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00005feb 0 root 666 12000 4 0x00005fe7 32769 root 666 524288 2 0x00005fe8 65538 root 666 2097152 2 0x00038c0e 131075 root 777 2072 1 0x00038c14 163844 root 777 5603392 0 0x00038c09 196613 root 777 221248 0 0x00000000 294918 root 600 2145386496 0 [root@tencent64 ~]# ipcrm -m 294918 [root@tencent64 ~]# ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00005feb 0 root 666 12000 4 0x00005fe7 32769 root 666 524288 2 0x00005fe8 65538 root 666 2097152 2 0x00038c0e 131075 root 777 2072 1 0x00038c14 163844 root 777 5603392 0 0x00038c09 196613 root 777 221248 0 [root@tencent64 ~]# free -g total used free shared buffers cached Mem: 126 30 95 0 0 16 -/+ buffers/cache: 14 111 Swap: 2 0 2刪除共享內存後,cache 被正常釋放了。這個行為與 tmpfs 的邏輯類似。內核底層在實現共享內存(shm)、消息隊列(msg)和信號量數組(sem)這些 POSIX:XSI 的 IPC 機制的內存存儲時,使用的都是 tmpfs。這也是為什麼共享內存的操作邏輯與 tmpfs 類似的原因。當然,一般情況下是 shm 占用的內存更多,所以我們在此重點強調共享內存的使用。說到共享內存,Linux 還給我們提供了另外一種共享內存的方法,就是:
[root@tencent64 ~]# cat mmap.c #include <stdlib.h> #include <stdio.h> #include <strings.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> #define MEMSIZE 1024*1024*1023*2 #define MPFILE "./mmapfile" int main() { void *ptr; int fd; fd = open(MPFILE, O_RDWR); if (fd < 0) { perror("open()"); exit(1); } ptr = mmap(NULL, MEMSIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, fd, 0); if (ptr == NULL) { perror("malloc()"); exit(1); } printf("%p\n", ptr); bzero(ptr, MEMSIZE); sleep(100); munmap(ptr, MEMSIZE); close(fd); exit(1); }這次我們干脆不用什麼父子進程的方式了,就一個進程,申請一段 2G 的 mmap 共享內存,然後初始化這段空間之後等待 100 秒,再解除影射所以我們需要在它 sleep 這 100 秒內檢查我們的系統內存使用,看看它用的是什麼空間?當然在這之前要先創建一個 2G 的文件 ./mmapfile。結果如下:
[root@tencent64 ~]# dd if=/dev/zero of=mmapfile bs=1G count=2 [root@tencent64 ~]# echo 3 > /proc/sys/vm/drop_caches [root@tencent64 ~]# free -g total used free shared buffers cached Mem: 126 30 95 0 0 16 -/+ buffers/cache: 14 111 Swap: 2 0 2然後執行測試程序:
[root@tencent64 ~]# ./mmap &[1] 191570x7f1ae3635000
[root@tencent64 ~]# free -g
total used free shared buffers cached
Mem: 126 32 93 0 0 18
-/+ buffers/cache: 14 111
Swap: 2 0 2[root@tencent64 ~]# echo 3 > /proc/sys/vm/drop_caches [root@tencent64 ~]# free -g total used free shared buffers cached Mem: 126 32 93 0 0 18 -/+ buffers/cache: 14 111 Swap: 2 0 2
我們可以看到,在程序執行期間,cached 一直為 18G,比之前漲了 2G,並且此時這段 cache 仍然無法被回收。然後我們等待100秒之後程序結束。
[root@tencent64 ~]# [1]+ Exit 1 ./mmap [root@tencent64 ~]# [root@tencent64 ~]# free -g total used free shared buffers cached Mem: 126 30 95 0 0 16 -/+ buffers/cache: 14 111 Swap: 2 0 2程序退出之後,cached 占用的空間被釋放。這樣我們可以看到,使用 mmap 申請標志狀態為 MAP_SHARED 的內存,內核也是使用的 cache 進行存儲的。在進程對相關內存沒有釋放之前,這段 cache 也是不能被正常釋放的。實際上,mmap 的 MAP_SHARED 方式申請的內存,在內核中也是由 tmpfs 實現的。由此我們也可以推測,由於共享庫的只讀部分在內存中都是以 mmap 的 MAP_SHARED 方式進行管理,實際上它們也都是要占用 cache 且無法被釋放的。
tmpfs 中存儲的文件會占用 cache 空間,除非文件刪除否則這個 cache 不會被自動釋放。
使用 shmget 方式申請的共享內存會占用 cache 空間,除非共享內存被 ipcrm 或者使用 shmctl 去 IPC_RMID,否則相關的 cache 空間都不會被自動釋放。
使用 mmap 方法申請的 MAP_SHARED 標志的內存會占用 cache 空間,除非進程將這段內存 munmap,否則相關的 cache 空間都不會被自動釋放。
實際上 shmget、mmap 的共享內存,在內核層都是通過 tmpfs 實現的,tmpfs 實現的存儲用的都是 cache。
當理解了這些的時候,希望大家對 free 命令的理解可以達到我們說的第三個層次。我們應該明白,內存的使用並不是簡單的概念,cache 也並不是真的可以當成空閒空間用的。如果我們要真正深刻理解你的系統上的內存到底使用的是否合理,是需要理解清楚很多更細節知識,並且對相關業務的實現做更細節判斷的。我們當前實驗場景是 Centos 6 的環境,不同版本的 Linux 的 free 現實的狀態可能不一樣,大家可以自己去找出不同的原因。
當然,本文所述的也不是所有的 cache 不能被釋放的情形。那麼,在你的應用場景下,還有那些 cache 不能被釋放的場景呢?