圖片來源:http://www.tomshardware.com/
序言:
Memory(內存)是一台計算機組成的重要部分,也是最基礎的一部分。其它基礎組件有主板、CPU、磁盤、顯卡(可獨立可集成)等。寫這篇文章源自后面的一個案例,出於想搞明白,以及分享以前關於內存方面的一些記錄的知識點。
本文概要主要講了內存的介紹;如何正確查看系統內存使用率;對Swap分區進行介紹;如何將內存當作硬盤來加速數據讀寫,以及分享關於內存異常的案例分析;介紹了oom-killer的機制。
一、內存的介紹
1.何為內存
內存 是一種利用半導體技術制成的存儲數據的電子設備。其電子電路中的數據以二進制方式存儲,內存的每一個存儲單元稱做記憶元。內存根據存儲能力及電源關系又可分為易失性內存(斷電后丟失),非易失性內存(斷電后持久)。
- 易失性內存:是指當電源供應中斷后,內存所存儲的數據將會丟失。有兩類:動態隨機訪問內存(DRAM),靜態隨機訪問內存(SRAM)。后者速度更快。
- 非易失性內存:是指當電源供應中斷后,內存說存儲的數據並不會小時。供電恢復后,數據可用正常讀取(有很小的幾率會出現數據損壞的情況)。有三類,只讀內存、閃存、磁盤(磁盤就是我們最常見的非易失性內存的內存)
- 后文所出現‘內存’字樣,均指易失性內存。
2.內存、CPU、磁盤三者的關系
- 內存與cpu的關系:cpu(中央處理器)就像人類的大腦一樣,負責解釋計算機指令以及處理計算機軟件中的數據。由於cpu需要處理數據,如果直接從硬盤中讀取寫入數據,以現在的機械尋址的硬盤和固態SSD硬盤的速度來比較,兩者差距太大。所以內存作為CPU和硬盤 兩者的橋梁存在,另外CPU與內存之間為了縮小讀寫差距,CPU技術加入了高速緩存概念(L1、L2、L3,三種級別的Cache),一般在CPU產品型號技術參數列表中都會具體的介紹。
- 內存與硬盤的關系:程序是在內存上運行的,為了加快讀速度,內存會將讀取過的數據在自身中進行緩存。直到cache已滿,會釋放最先緩存的數據,將其寫入磁盤,並騰出一定空間來滿足當前正在讀取的新數據。硬盤是為了持久化存儲程序運行的數據,保證數據在機器斷電后不被丟失。
二、如何正確查看內存的使用率
1.free 命令查看內存使用
[root@docker ~]# free total used free shared buff/cache available Mem: 1883740 440288 896080 31024 547372 1256984 Swap: 0 0 0
注:本機已關閉swap分區。
- 物理總內存total:1883740KB / 1024 =1.8G(這里不包括內核在啟動時為其自身保留的一小部分,所以這里是1.8G,而其實是2G內存)
- 使用內存:1883740KB - 896080KB -547372KB = 440288KB(計算公式:total - free - buffers - cache = used)
- free 空閑內存:1883740KB - 440288KB -547372KB = 440288KB(同used計算公式)
- shared共享內存:被tmpfs使用的(大部分)內存
- buffers緩沖區:內核緩沖區使用的內存(寫數據時的緩沖)
- cache緩存:頁面緩存和slab 使用的內存 (注:slab內存分配機制 參考頁面點此)(讀數據時的緩存,設置drop_cache=3,使用time分別計算 讀文件打開時間,動態watch cache的大小)
- available有效內存:估計有多少內存可用於啟動新的應用程序,沒有交換。 與緩存或free字段提供的數據不同,此字段考慮到頁面緩存,並且由於項目正在使用,並不是所有可回收內存板都將被回收。(由系統動態的估算,此值相當於6.x系統里 free+cache+buffers的可用內存)
2.top 查看進程使用的內存
可指定進程查看,不加參數顯示所有的進程,以及概覽。
[root@docker ~]# top -p `pgrep nginx|head -n 1` ...省略 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 26826 root 20 0 45816 1000 0 S 0.0 0.1 0:00.00 nginx
注釋:
第一行,顯示了默認的字段,另top狀態下,可輸入 f 鍵,進行選擇其它字段,如DATA、SWAP等
第二行,顯示字段的值。這里說下進程RES (駐留內存空間)和%MEM的關系,%MEM 是該進程占用系統物理total內存的百分比。RES計算方式:total*%MEM=RES;VIRT包括所需要的代碼、數據和共享庫。
3.vmstat 查看虛擬內存使用以及swap分區 和系統、cpu的io 報告
[root@docker ~]# vmstat procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 4 0 0 858708 15756 571772 0 0 6 49 16 0 8 0 92 0 0
三、Swap——與內存緊密相關的交換空間
1.Swap 用途介紹
當物理內存(RAM)的數量已滿時,會使用Linux中的swap交換空間。如果系統需要更多內存資源並且RAM已滿,則內存中的非活動頁面將移動到交換空間中。雖然交換空間可以幫助機器使用少量的RAM,但不應該被認為是更多RAM的替代品。因為交換空間位於硬盤驅動器上,訪問時間比物理內存慢的很多。發生的交換越多,系統越慢。
Linux有兩種形式的交換空間:交換分區和交換文件。交換分區是僅用於交換的硬盤的獨立部分,沒有其他文件可以存放在那里。交換文件是位於系統和數據文件之間在文件系統中的一個特殊文件。另外,swap 空間一般設置物理內存的2倍。
2.啟用或禁止交換空間
- 啟用:如果覺得服務器應用對內存的讀寫不是特別在意,但服務器內存又很小的話,可設置開啟swap 交換空間。
- 禁止:一種情況是當主機內存夠大時,如果開啟swap空間,當發生物理內存使用到達內核設定的一個臨界值時(稍后會講),內存中的數據會移動到swap交換空間內,而這時,物理內存還是有很多的。由於swap分區是走磁盤IO,所以我們有必要修改這個臨界值或者禁止使用swap分區。還有一種情況就是我們的客戶機操作系統是虛擬機(如kvm虛擬化上的),這個時候是要一定禁止swap分區的。
3.如何啟用或禁用交換空間
查看當前swap空間狀態
root@docker ~]# swapon -s 文件名 類型 大小 已用 權限 /mnt/os_swap file 4194300 0 -1
禁用swap空間
[root@docker ~]# swapoff -a
啟用swap空間(這里使用的文件形式作為swap分區)
[root@docker ~]# swapon /mnt/os_swap
4.創建swap分區大小
- 用文件形式來創建swap
在當前的文件系統里,用dd或者fallocate(更快更簡單)創建一個用於swap分區的文件:
[root@docker ~]# dd if=/dev/zero of=/mnt/os_swap bs=1M count=4096
或
[root@docker ~]# fallocate -l 4G /mnt/os_swap
修改該文件權限為600
[root@docker ~]# chmod 600 /mnt/os_swap
使用mkswap命令將文件建成一個交換空間(也可在最后用 -p size 指定划分給swap空間的大小,默認使用整個file 大小)
[root@docker ~]# mkswap /mnt/os_swap mkswap: /mnt/os_swap: warning: wiping old swap signature. 正在設置交換空間版本 1,大小 = 4194300 KiB 無標簽,UUID=fc870bd5-c823-4e70-9d14-966543a52db2
使用swap分區,並檢查swap空間大小
[root@docker ~]# swapon /mnt/os_swap [root@docker ~]# swapon -s 文件名 類型 大小 已用 權限 /mnt/os_swap file 4194300 0 -1
- 用磁盤分區的方式創建swap
首先找到一個大小合適的磁盤分區,分區類型選擇82-Linux Swap。然后之后和上述類似,用mkswap命令創建啟用swap分區。並用swapon /dev/vdb1(vdb1為分區設備)命令啟用swap分區。
5.swappiness-系統什么時候開始使用交換空間
當我們設置啟用swap分區后,系統會在什么情況下使用交換空間?這要從物理RAM內存使用情況來看,系統內核參數定義了這個界限:vm.swappiness,該參數取值范圍在0-100之間,它定義了當系統剩余內存是總內存的多少百分比后,即開始使用交換空間。(暫時對這句話保持質疑,經過多方內存使用的測試,系統在由RAM轉向Swap空間時並沒有一個固定的剩余內存值!如你有相關計算方式,還望不吝賜教,謝謝!)
vm.swappiness 默認值是60。swappiness參數的值越高,表示內核將會更積極地從內存到交換空間中移動數據。經測試
當值為0時,系統可用內存在使用完后再使用swap進行交換。
當值為1時,和0的區別不大。
推薦在數據庫讀寫數據密集的應用中禁用swap交換空間或者設置更低的vm.swappiness值,避免更多的硬盤IO操作,以此作為提高讀寫速度的一個方式。
四、如何將內存當作磁盤使用來加速數據讀寫
1.內存文件系統介紹
圖片來源:www.thomas-krenn.com
如上圖所示,利用內存作為特殊文件系統,目前知道的方式有3種,RAMdisk、ramfs、tmpfs。
ramdisk:RAM disk是使用主系統內存作為塊設備的一種方式。它也可用於臨時文件系統的加密工作,因為內容在重新啟動時將被擦除。由於這個塊設備大小是固定的,因此安裝存放在其上面的文件系統是固定的大小。
ramfs:Ramfs是一個非常簡單的文件系統,用於導出Linux的磁盤緩存 機制(頁面緩存和dentry緩存)基於RAM的文件系統,可以動態的根據需要調整大小(前提不超過總RAM的情況)。通常,文件都被Linux緩存在內存中,數據頁從后備存儲(通常是安裝文件系統的塊設備)中讀取並被保存 防止它再次需要,但標記為干凈(可自由使用的),以防萬一 虛擬內存系統需要其他內存。但ramfs 是沒有后備存儲的,像往常一樣,寫入ramfs的文件分配 dentry和頁面緩存,但是沒有地方可寫。這意味着頁面從未被標記為干凈,因此當VM正在尋找回收內存時,它們不能被釋放 。除非關機斷電。
tmpfs:將所有文件保存在虛擬內存中的文件系統。tmpfs中的所有內容都是臨時的,因為在你的硬盤上不會有文件被創建。如果卸載tmpfs實例, 存儲在其中的一切都丟失。如果你umount tmpfs文件系統,存儲在里面的內容將丟失。tmpfs可以將所有內容放入內核內部緩存並增長收縮以容納它包含的文件,並能夠交換不需要的頁面到swap交換空間。由於tmpfs 完全位於頁面緩存和交換中,所有tmpfs頁面將在/proc/meminfo 和‘shared’ 中顯示為 ”Shmem“
2.它們三的簡單比較
ramfs與ramdisk比較:使用ram disk還需要不必要地將偽造塊設備中的內存復制到頁面緩存(並復制更改),以及創建和銷毀dentries。此外,它還需要一個文件系統驅動程序(如ext2)來格式化和解釋這些數據。與ramfs相比,這浪費了內存(和內存總線帶寬),為CPU創造了不必要的工作,並污染了CPU高速緩存。更重要的一點,ramfs的所有的工作都發生在_anyway_,因為所有文件訪問都通過頁面和dentry緩存。 RAM disk是不必要的; ramfs在內部更簡單,使用起來更靈活,大小可隨着需要的空間而動態增加或減少。
本文原文出自飛走不可,如有轉載,還請注明出處,如 飛走不可:http://www.cnblogs.com/hanyifeng/p/6915399.html
ramfs與tmpfs比較:ramfs的一個缺點是你可以持續的往里面寫入數據,直到填滿所有的內存,並且VM無法釋放它,因為VM認為文件應該寫入后備存儲(而不是交換空間),但是ramfs 它沒有任何后備存儲。因此,只有root(或受信任的用戶)才允許對ramfs mount進行寫訪問。創建一個名為tmpfs的ramfs派生物,以增加大小限制和能力,將數據寫入swap交換空間。普通用戶也可以允許寫入權限 tmpfs掛載。
3.如何設置ramfs
創建一個目錄,用於掛載ramfs
[root@docker ~]# mkdir /ramfs_test
掛載ramfs到上一步創建的目錄中
[root@docker ~]# mount -t ramfs ramfs /ramfs_test/ 檢查 [root@docker ~]# mount |grep ramfs_test ramfs on /ramfs_test type ramfs (rw,relatime)
測試一下ramfs與磁盤io讀寫的比較,一目了然。
[root@docker ~]# dd if=/dev/zero of=/ramfs_test/testfile.ramfs bs=1M count=1000 記錄了1000+0 的讀入 記錄了1000+0 的寫出 1048576000字節(1.0 GB)已復制,0.60369 秒,1.7 GB/秒 [root@docker ~]# dd if=/dev/zero of=/tmp/testfile.ramfs bs=1M count=1000 記錄了1000+0 的讀入 記錄了1000+0 的寫出 1048576000字節(1.0 GB)已復制,13.3286 秒,78.7 MB/秒
另外一個需要說明,網上大部分文章說掛載ramfs時,可以指定maxsize,即使用的最大的內存,經過測試,size(maxsize)參數沒有生效,我使用以下命令進行掛載:
[root@docker ~]# mount -t ramfs ramfs /ramfs_test -o size=1024M && mount | grep ramfs ramfs on /ramfs_test type ramfs (rw,relatime,size=1024M)
然后放入一個大於1G的文件,並檢查大小
[root@docker ~]# dd if=/dev/zero of=/ramfs_test/testramfs.file bs=1M count=1200 記錄了1200+0 的讀入 記錄了1200+0 的寫出 1258291200字節(1.3 GB)已復制,0.78763 秒,1.6 GB/秒 [root@docker ~]# ll -h /ramfs_test/testramfs.file -rw-r--r-- 1 root root 1.2G 6月 2 09:04 /ramfs_test/testramfs.file
從上面可以看出,使用ramfs作文件系統,並沒有受到限制,所以它有可能占用系統全部的RAM,並導致系統死鎖,無法進行操作,系統內核將崩潰。所以這里在使用ramfs時,要慎重考慮使用場景,避免程序故障或內存溢出導致系統崩潰,必須重啟才能解決!另外查看了mount的man 手冊,掛載ramfs內容時只有以下內容:
Mount options for ramfs Ramfs is a memory based filesystem. Mount it and you have it. Unmount it and it is gone. Present since Linux 2.3.99pre4. There are no mount options.
而,size參數只適用於tmpfs!
Mount options for tmpfs size=nbytes Override default maximum size of the filesystem. The size is given in bytes, and rounded up to entire pages. The default is half of the memory. The size parameter also accepts a suffix % to limit this tmpfs instance to that percentage of your physical RAM: the default, when neither size nor nr_blocks is specified, is size=50%
4.如何設置tmpfs
和ramfs有點類似,先創建一個目錄,用於掛載tmpfs
[root@docker ~]# mkdir /tmpfs_test
使用mount命令掛載到tmpfs_test目錄中,並檢查掛載情況
[root@docker ~]# mount -t tmpfs -o size=1G tmpfs /tmpfs_test && mount |grep tmpfs_test tmpfs on /tmpfs_test type tmpfs (rw,relatime,size=1048576k)
現在我們使用dd來測試一下速度,並檢查下size 限制空間的效果
[root@docker ~]# dd if=/dev/zero of=/tmpfs_test/testtmpfs.file bs=1M count=1100 dd: error writing ‘/tmpfs_test/testtmpfs.file’: No space left on device 1025+0 records in 1024+0 records out 1073741824 bytes (1.1 GB) copied, 0.497443 s, 2.2 GB/s
從上面的提示可以看出,空間已經不夠,現在看下實際存入的文件大小
[root@docker ~]# ls -l --block-size=K /tmpfs_test/testtmpfs.file -rw-r--r-- 1 root root 1048576K Jun 2 12:16 /tmpfs_test/testtmpfs.file
從ls的輸出可以看到,實際存入的文件大小,剛好是size限制的大小。很明顯,size起到了避免系統RAM/SWAP內存被tmpfs 全部填滿的情況。也體現了tmpfs 比ramfs的優勢所在。所以推薦使用tmpfs。
下面是之前tmpfs介紹中所說的tmpfs在/proc/meminfo 以及shared 顯示。
[root@docker ~]# free -m && echo '-------------------------/proc/meminfo' && cat /proc/meminfo |grep Shmem total used free shared buff/cache available Mem: 1839 268 174 1024 1397 393 Swap: 0 0 0 -------------------------/proc/meminfo Shmem: 1048964 kB
五、常見內存不足或內存溢出導致的故障分析
1.一次內存異常導致應用被kill的案例
某台應用無法提供服務,主機sshd無法訪問,通信異常。使用vnc連接到本地終端后,發現終端界面上報以下錯誤日志,也無法進行操作:
INFO: task sh:12628 blocked for more than 120 seconds.Not tainted 2.6.32-431.el6.x86 #1 "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
上述錯誤,只是警告提示,跟系統內核參數vm.dirty_ratio有關系,后面會專門的介紹。 由於該主機無法響應任何操作,系統也登陸不進去,只有斷電重啟了(這里也可看出系統日志收集到某個中心節點的好處Y(^_^)Y)。重啟后觀察系統messages日志,下面是日志的一部分,這里有完整的日志,有興趣的可以下載看下:

May 19 00:31:07 robot-web kernel: VFS: file-max limit 65535 reached May 19 00:31:30 robot-web kernel: VFS: file-max limit 65535 reached May 19 00:32:24 robot-web kernel: sh invoked oom-killer: gfp_mask=0x84d0, order=0, oom_adj=0, oom_score_adj=0 May 19 00:32:24 robot-web kernel: sh cpuset=/ mems_allowed=0 May 19 00:32:24 robot-web kernel: Pid: 20387, comm: sh Not tainted 2.6.32-431.el6.x86_64 #1 May 19 00:32:24 robot-web kernel: Call Trace: May 19 00:32:24 robot-web kernel: [<ffffffff810d05b1>] ? cpuset_print_task_mems_allowed+0x91/0xb0 May 19 00:32:24 robot-web kernel: [<ffffffff81122960>] ? dump_header+0x90/0x1b0 May 19 00:32:24 robot-web kernel: [<ffffffff8122798c>] ? security_real_capable_noaudit+0x3c/0x70 May 19 00:32:24 robot-web kernel: [<ffffffff81122de2>] ? oom_kill_process+0x82/0x2a0 May 19 00:32:24 robot-web kernel: [<ffffffff81122d21>] ? select_bad_process+0xe1/0x120 May 19 00:32:24 robot-web kernel: [<ffffffff81123220>] ? out_of_memory+0x220/0x3c0 May 19 00:32:24 robot-web kernel: [<ffffffff8112fb3c>] ? __alloc_pages_nodemask+0x8ac/0x8d0 May 19 00:32:24 robot-web kernel: [<ffffffff81167a9a>] ? alloc_pages_current+0xaa/0x110 May 19 00:32:24 robot-web kernel: [<ffffffff8104ee9b>] ? pte_alloc_one+0x1b/0x50 May 19 00:32:24 robot-web kernel: [<ffffffff81146412>] ? __pte_alloc+0x32/0x160 May 19 00:32:24 robot-web kernel: [<ffffffff8114b220>] ? handle_mm_fault+0x1c0/0x300 May 19 00:32:24 robot-web kernel: [<ffffffff8104a8d8>] ? __do_page_fault+0x138/0x480 May 19 00:32:24 robot-web kernel: [<ffffffff8152d45e>] ? do_page_fault+0x3e/0xa0 May 19 00:32:24 robot-web kernel: [<ffffffff8152a815>] ? page_fault+0x25/0x30 May 19 00:32:24 robot-web kernel: [<ffffffff8152d45e>] ? do_page_fault+0x3e/0xa0 May 19 00:32:24 robot-web kernel: [<ffffffff8152a815>] ? page_fault+0x25/0x30 May 19 00:32:24 robot-web kernel: Mem-Info: May 19 00:32:24 robot-web kernel: Node 0 DMA per-cpu: May 19 00:32:24 robot-web kernel: CPU 0: hi: 0, btch: 1 usd: 0 May 19 00:32:24 robot-web kernel: CPU 1: hi: 0, btch: 1 usd: 0 May 19 00:32:24 robot-web kernel: CPU 2: hi: 0, btch: 1 usd: 0 May 19 00:32:24 robot-web kernel: CPU 3: hi: 0, btch: 1 usd: 0 May 19 00:32:24 robot-web kernel: Node 0 DMA32 per-cpu: May 19 00:32:24 robot-web kernel: CPU 0: hi: 186, btch: 31 usd: 0 May 19 00:32:24 robot-web kernel: CPU 1: hi: 186, btch: 31 usd: 0 May 19 00:32:24 robot-web kernel: CPU 2: hi: 186, btch: 31 usd: 0 May 19 00:32:24 robot-web kernel: CPU 3: hi: 186, btch: 31 usd: 0 May 19 00:32:24 robot-web kernel: Node 0 Normal per-cpu: May 19 00:32:24 robot-web kernel: CPU 0: hi: 186, btch: 31 usd: 0 May 19 00:32:24 robot-web kernel: CPU 1: hi: 186, btch: 31 usd: 0 May 19 00:32:24 robot-web kernel: CPU 2: hi: 186, btch: 31 usd: 0 May 19 00:32:24 robot-web kernel: CPU 3: hi: 186, btch: 31 usd: 0 May 19 00:32:24 robot-web kernel: active_anon:1459499 inactive_anon:284686 isolated_anon:0 May 19 00:32:24 robot-web kernel: active_file:54 inactive_file:45 isolated_file:0 May 19 00:32:24 robot-web kernel: unevictable:0 dirty:0 writeback:0 unstable:0 May 19 00:32:24 robot-web kernel: free:26212 slab_reclaimable:6599 slab_unreclaimable:53001 May 19 00:32:24 robot-web kernel: mapped:697 shmem:793 pagetables:131666 bounce:0 May 19 00:32:24 robot-web kernel: Node 0 DMA free:15728kB min:124kB low:152kB high:184kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:15340kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:0kB slab_reclaimable:0kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:0kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? yes May 19 00:32:24 robot-web kernel: lowmem_reserve[]: 0 3000 8050 8050 May 19 00:32:24 robot-web kernel: Node 0 DMA32 free:45816kB min:25140kB low:31424kB high:37708kB active_anon:1983180kB inactive_anon:496216kB active_file:28kB inactive_file:72kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:3072092kB mlocked:0kB dirty:0kB writeback:0kB mapped:2000kB shmem:1912kB slab_reclaimable:3200kB slab_unreclaimable:82768kB kernel_stack:10296kB pagetables:147684kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:1 all_unreclaimable? no May 19 00:32:24 robot-web kernel: lowmem_reserve[]: 0 0 5050 5050 May 19 00:32:24 robot-web kernel: Node 0 Normal free:43304kB min:42316kB low:52892kB high:63472kB active_anon:3854816kB inactive_anon:642528kB active_file:188kB inactive_file:108kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:5171200kB mlocked:0kB dirty:0kB writeback:0kB mapped:788kB shmem:1260kB slab_reclaimable:23196kB slab_unreclaimable:129236kB kernel_stack:26800kB pagetables:378980kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:59 all_unreclaimable? no May 19 00:32:24 robot-web kernel: lowmem_reserve[]: 0 0 0 0 May 19 00:32:24 robot-web kernel: Node 0 DMA: 2*4kB 1*8kB 2*16kB 2*32kB 2*64kB 1*128kB 0*256kB 0*512kB 1*1024kB 1*2048kB 3*4096kB = 15728kB May 19 00:32:24 robot-web kernel: Node 0 DMA32: 340*4kB 76*8kB 461*16kB 264*32kB 132*64kB 62*128kB 12*256kB 5*512kB 0*1024kB 1*2048kB 1*4096kB = 45952kB May 19 00:32:24 robot-web kernel: Node 0 Normal: 9334*4kB 326*8kB 2*16kB 8*32kB 4*64kB 2*128kB 2*256kB 0*512kB 0*1024kB 1*2048kB 0*4096kB = 43304kB May 19 00:32:24 robot-web kernel: 30346 total pagecache pages May 19 00:32:24 robot-web kernel: 29453 pages in swap cache May 19 00:32:24 robot-web kernel: Swap cache stats: add 691531, delete 662078, find 1720814/1738009 May 19 00:32:24 robot-web kernel: Free swap = 0kB May 19 00:32:24 robot-web kernel: Total swap = 2064376kB May 19 00:32:24 robot-web kernel: 0 8384 26517 56 1 0 0 sh May 19 00:32:24 robot-web kernel: [ 8385] 0 8385 64694 2309 2 0 0 php May 19 00:32:24 robot-web kernel: [ 8386] 0 8386 26517 56 2 0 0 sh May 19 00:32:24 robot-web kernel: [ 8387] 0 8387 26517 57 0 0 0 sh May 19 00:32:24 robot-web kernel: [ 8388] 0 8388 26517 55 1 0 0 sh May 19 00:32:24 robot-web kernel: [ 8402] 0 8402 26517 56 0 0 0 sh May 19 00:32:24 robot-web kernel: [ 8887] 93 8887 23967 272 1 0 0 sendmail May 19 00:32:24 robot-web kernel: [15917] 0 15917 64760 2347 3 0 0 php May 19 00:32:24 robot-web kernel: [15921] 0 15921 64760 2351 3 0 0 php May 19 00:32:24 robot-web kernel: [15923] 0 15923 64760 2349 2 0 0 php May 19 00:32:24 robot-web kernel: [15925] 0 15925 64696 2343 3 0 0 php May 19 00:32:24 robot-web kernel: [15950] 0 15950 64760 2346 3 0 0 php
我篩選了幾條,主要內容有以下部分:
1 May 19 00:31:30 robot-web kernel: VFS: file-max limit 65535 reached 2 ... 3 4 May 19 00:32:24 robot-web kernel: Free swap = 0kB 5 May 19 00:32:24 robot-web kernel: Total swap = 2064376kB 6 ... 7 8 May 19 00:32:24 robot-web kernel: [20477] 0 20477 26518 55 3 0 0 sh 9 May 19 00:32:24 robot-web kernel: Out of memory: Kill process 1572 (mysqld) score 3 or sacrifice child 10 May 19 00:32:24 robot-web kernel: Killed process 1572, UID 500, (mysqld) total-vm:452564kB, anon-rss:5400kB, file-rss:40kB 11 May 19 00:32:24 robot-web kernel: sh invoked oom-killer: gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0
從日志中可以看到兩個問題,1是系統打開文件句柄數到達設置上限;2是內存不足,swap交換空間都用完了,觸發了內存Oom-Killer,由oom-killer根據算法選擇了mysqld,將其殺死(稍后將會說下oom-killer是怎么選擇的)。按理應該系統這時就可以恢復的,但是當時系統內的crontabd 里有每10秒執行的sh任務,且這些命令運行完沒有正常釋放,導致進程連接數太多,占用的內存超過了系統的可用內存。所以猜測這是系統無法響應任何操作的根本原因,只能重啟。
重啟系統恢復正常后,根據以上出現的問題,對系統進行了調優工作。主要包括以下幾個部分:
- 按照提示修改 /proc/sys/kernel/hung_task_timeout_secs 值為 0
臨時: echo 0 > /proc/sys/kernel/hung_task_timeout_secs 或 sysctl -w hung_task_timeout_secs=0 永久 echo “kernel.hung_task_timeout_secs = 0” >>/etc/sysctl.conf sysctl -p
增加系統物理內存到16G,並修改用戶open files限制數為1048576 計算是每4M內存打開256個文件數,即:(16G*1024)M/4M *256。
臨時: ulimit -n 1048576 或 echo "ulimit -n 1048576" >> /etc/profile 永久:編寫/etc/security/limits.conf,根據提示在末行添加
- 增加file-max值為1048576
臨時: echo 1048576 > /proc/sys/fs/file-max 或 sysctl -w fs.file-max=1048576 永久 echo “fs.file-max=1048576” >> /etc/sysctl.conf sysctl -p
- 優化修改系統對臟數據的刷新策略
臨時: sysctl -w vm.dirty_background_ratio=5 sysctl -w vm.dirty_ratio=10 永久: echo "vm.dirty_background_ratio=5" >> /etc/sysctl.conf echo "vm.dirty_ratio=10" >> /etc/sysctl.conf
sysctl -p - 優化crontab里的任務計划,檢查,確保執行后釋放
- 由於該系統是虛擬機,關閉swap分區。(虛擬機里使用磁盤做swap分區,IO性能會更低)
做了以上修改優化后,系統到目前為止,沒有出現過異常。
2.對以上參數修改進行分析
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message
先說下這個吧。關於內核為什么提示我們要“修改這個時間為0,來禁用這條消息”的提示。先看下hung_task_timeout_secs的介紹,參考內核文檔:
hung_task_timeout_secs: Check interval. When a task in D state did not get scheduled for more than this value report a warning. This file shows up if CONFIG_DETECT_HUNG_TASK is enabled. 0: means infinite timeout - no checking done. Possible values to set are in range {0..LONG_MAX/HZ}.
內核定時檢測系統中處於D狀態的進程,如果其處於D狀態的時間超過了指定時間(默認120s,可以配置),則打印相關堆棧信息,也可以通過proc參數配置使其直接panic。
默認情況下,Linux使用高達40%的可用內存將進行文件系統緩存。達到此標記后,文件系統將所有未完成的數據刷新到磁盤,導致所有的IO進行同步。要將此數據刷新到磁盤,默認情況下有120秒的時間限制。在這種情況下,IO子系統速度不夠快,可以有120秒的時間刷新數據。由於IO子系統響應緩慢,並且用戶進程又提出了更多的請求,系統內存將被填滿,從而導致上述截圖中的錯誤提示。
將其值修改為 0,意味着表示可以無限超時--不檢查完成情況。但此並不能真正的解決問題根源。
與hung_task其相關的幾個參數
[root@docker ~]# sysctl -a|grep vm.dirty vm.dirty_background_bytes = 0 vm.dirty_background_ratio = 10 vm.dirty_bytes = 0 vm.dirty_expire_centisecs = 3000 vm.dirty_ratio = 30 vm.dirty_writeback_centisecs = 500
這里只說下其中兩個相關的參數:vm.dirty_ratio 、vm.dirty_background_ratio,以下內容出自Linux內核文檔介紹:
dirty_background_ratio Contains, as a percentage of total available memory that contains free pages and reclaimable pages, the number of pages at which the background kernel flusher threads will start writing out dirty data. The total available memory is not equal to total system memory. ============================================================== dirty_ratio Contains, as a percentage of total available memory that contains free pages and reclaimable pages, the number of pages at which a process which is generating disk writes will itself start writing out dirty data. The total available memory is not equal to total system memory.
dirty_background_ratio:是系統總內存的占用百分比,其中包括空閑頁面和可回收頁面。后台內核刷新線程的頁面開始寫入臟數據。即dirty data如果達到了dirty_background_ratio,則內核將在后台執行回寫磁盤,但是應用程序仍然可以在不阻塞的情況下寫入頁面緩存。
dirty_ratio:包含可用頁面和可回收頁面的占 總可用內存的百分比,產生磁盤寫入的進程將自動開始寫入臟數據。這時應用程序將阻塞並將臟頁面寫入磁盤。直到系統內的dirty page低於該值。
即先達到dirty_background_ratio,這是程序不會阻塞,等到達dirty_ratio時,程序將阻塞請求,直到將數據寫入磁盤中。這些參數的值取決於系統的運行的什么程序,如果運行大型數據庫,建議將這些值保持小數值 background_ratio < dirty_ratio,以避免I/O瓶頸以及增加系統負載。這也是作為系統VM優化的一個點。
說下file-max
sysctl -w fs.file-max=1048576
file-max中的值表示Linux內核將要分配的文件句柄的最大數量。 每當應用程序請求文件句柄時,內核會動態分配文件句柄,但內核在應用程序關閉時並不會釋放銷毀這些文件句柄。內核會循環使用這些文件句柄。也意味着隨着時間的推移,分配的文件句柄的總數將增加,即使當前使用的文件句柄的數量可能較低。所以當您收到大量關於運行文件句柄的錯誤消息時,可能需要增加file-max值的大小。
3.什么是oom-killer ?
介紹
OOM(Out Of Memory) Management:它有一個簡單的任務->檢查系統是否有足夠的可用內存來滿足應用程序,驗證系統是否真的是內存不足,如果是這樣,請‘選擇’一個進程來殺死它。
當檢查可用內存時,所需頁面的數量作為參數傳遞給vm_enough_memory()。除非系統管理員指定系統應該過度使用內存,否則將檢查可用內存的安裝。為了確定有多少頁是潛在可用的,Linux總結了以下幾個關注數據:
Total page cache 因為頁面緩存容易回收
Total free pages 因為它們已經是有效可利用的
Total free swap pages 作為用戶空間的頁面可能會被分頁換出
Total pages managed by swapper_space 盡管這重復計算了空閑的交換頁面。這是平衡的,因為事實上,插槽有時是保留的,但沒有在使用。
Total pages used by the dentry cache 易於回收的
Total pages used by the inode cache 易於回收的
如果以上添加的總頁數足以滿足請求,vm_enough_memory()函數將返回true給調用者。如果返回false,調用者就知道內存不可用,通常決定將-ENOMEM返回給用戶空間。這時機器內存不足時,舊的頁幀將被回收,但是盡管回收頁面可能會發現還是無法釋放足夠的頁面來滿足請求,如果它無法釋放頁面幀,則調用out_of_memory() 函數來查看系統是否內存不足,並且還需要kill某進程。
不巧的是,有可能系統在沒有空閑內存可用時,只需要等待IO完成 或頁面交換到后備存儲器中(如硬盤)。所以為了防止誤殺情況,在決定殺死一個進程之前,它將通過以下清單檢查:
- 是否有足夠的交換空間 (nr_swap_pages > 0) ? 如果是,不執行OOM
- 自上次失敗以來是否超過5秒? 如果是, 不執行OOM
- 我們在最后一刻失敗了嗎? 如果否,不執行OOM
- 如果至少在過去5秒內沒有發生10次故障, 我們不執行OOM
- 一個進程在最近5秒內被殺死了么? 如果是, 不執行OOM
只有上述測試通過了,才能調用oom_kill()來選擇一個進程kill。
以上是2.6內核以前的幾個關注數據,2.6內核以后的,將以下信息片段添加在一起以確定可用內存:
Total page cache 因為頁面緩存容易回收
Total free pages 因為它們已經是有效可利用的
Total free swap pages 作為用戶空間的頁面可能會被分頁換出
Slab pages with SLAB_RECLAIM_ACCOUNT set 他們是容易回收的
這些頁面減去根進程的3%預留內存,就是可用於請求的總內存量。如果內存可用,則進行檢查以確保已提交內存的總量不超過允許的閾值。
允許的閾值為TotalRam *(OverCommitRatio / 100)+ TotalSwapPage,其中OverCommitRatio 由系統管理員設置。
以上內容參考Mel Gorman的書籍<<Understanding the Linux Virtual Memory Manager>>
注:
對swapper_space作個解釋備注,當釋放不用的物理頁面時,內核並不會立即將其放入空閑隊列(free_area),而是將其插入非活動隊列lru,便於再次時能夠快速的得到。swapper_space則用於記錄換入/換出到磁盤交換文件中的物理頁面。swapper_space是3個全局的LRU隊列其中之一,可參考作者zszmhd的內存管理的文章
對dentry(翻譯:目錄項)的解釋:它們用來實現名稱和 inode 之間的映射,有一個目錄緩存用來保存最近使用的 dentry。dentry 還維護目錄和文件之間的關系,從而支持在文件系統中移動
4.為什么oom_killer選擇了mysqld進程?
函數select_bad_process()負責選擇要殺死的進程。badness() 已改oom_badness()通過為其檢查每個進程累加的“ponits”並將其返回給select_bad_process()。'points'數值最高的進程,最終會被銷毀kill,除非它已經在自己釋放內存中。
進程的得分值始於其常駐內存的大小,任何子進程(內核線程除外)的獨立內存大小也會添加到該分數中;另外root 進程在計算時將會有3%的優惠,確保超級用戶的進程不被殺掉。進程的nice值會乘2計算得分,如果nice大於0即更有可能被選中;長時間運行的進程的分數會降低,運行時間越長,被選中機率越低;直接訪問硬件設備的進程分數會降低。 (刪除原因,是在Github上看到新的oom_kill.c文件中,已去掉了相關代碼)。最后,累積的得分由/proc/pid/oom_score存儲,且該文件對用戶只讀權限。
以下是oom_kill.c文件中關於oom_badness部分的代碼:

1 unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg, 2 const nodemask_t *nodemask, unsigned long totalpages) 3 { 4 long points; 5 long adj; 6 7 if (oom_unkillable_task(p, memcg, nodemask)) 8 return 0; 9 10 p = find_lock_task_mm(p); 11 if (!p) 12 return 0; 13 14 /* 15 * Do not even consider tasks which are explicitly marked oom 16 * unkillable or have been already oom reaped or the are in 17 * the middle of vfork 18 */ 19 adj = (long)p->signal->oom_score_adj; 20 if (adj == OOM_SCORE_ADJ_MIN || 21 test_bit(MMF_OOM_SKIP, &p->mm->flags) || 22 in_vfork(p)) { 23 task_unlock(p); 24 return 0; 25 } 26 27 /* 28 * The baseline for the badness score is the proportion of RAM that each 29 * task's rss, pagetable and swap space use. 30 */ 31 points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS) + 32 atomic_long_read(&p->mm->nr_ptes) + mm_nr_pmds(p->mm); 33 task_unlock(p); 34 35 /* 36 * Root processes get 3% bonus, just like the __vm_enough_memory() 37 * implementation used by LSMs. 38 */ 39 if (has_capability_noaudit(p, CAP_SYS_ADMIN)) 40 points -= (points * 3) / 100; 41 42 /* Normalize to oom_score_adj units */ 43 adj *= totalpages / 1000; 44 points += adj; 45 46 /* 47 * Never return 0 for an eligible task regardless of the root bonus and 48 * oom_score_adj (oom_score_adj can't be OOM_SCORE_ADJ_MIN here). 49 */ 50 return points > 0 ? points : 1; 51 }
另外,在oom_kill.h文件中,/proc/pid/oom_adj 有取值范圍 -16~15 之間。 -17表示對此進程禁用oom。我們可以修改oom_adj的值,來保護某指定的重要程序。
如何檢測系統有無被oom-killer殺死的程序:可查看系統messages日志,以及用dmesg命令。另外還可以用dstat命令來查看當前系統中,被oom機制選中的候選者,如下:
[root@docker ~]# dstat --top-oom --out-of-memory--- kill score mysqld 61 mysqld 61
六、總結
內存管理是Linux系統內核中核心技術,重要性不言而喻,對內存的了解越深,更有利於我們應用程序的開發以及運行。文中的內容部分理論知識來自互聯網(鏈接在文末),另外也加入了自己的分析測試理解,本着深入學習的心理,寫下並總結了這篇文章分享給大家。如果文章中有錯誤的地方,或者可以修改成更好的話來表達,還望大家不吝賜教,留言私信都可以哈。另外感謝虎神給文章起的名字😏 (:對,你沒看錯,確實是寫完之后才想的名字,主要是內存涉及了太多知識,我覺得本文也只是描述了個大概。)
本文原文出自飛走不可,如有轉載,還請注明出處,如 飛走不可:http://www.cnblogs.com/hanyifeng/p/6915399.html
參考連接:
https://serverfault.com/questions/85470/meaning-of-the-buffers-cache-line-in-the-output-of-free
https://www.linux.com/news/all-about-linux-swap-space
https://askubuntu.com/questions/103915/how-do-i-configure-swappiness
https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt
https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt
https://www.kernel.org/doc/Documentation/blockdev/ramdisk.txt
http://www.vpsee.com/2013/10/how-to-configure-the-linux-oom-killer/
http://www.adminlinux.org/2009/09/tuning-rhel-4xx-5xx-file-max-maximum.html
https://www.kernel.org/doc/Documentation/sysctl/fs.txt
https://www.blackmoreops.com/2014/09/22/linux-kernel-panic-issue-fix-hung_task_timeout_secs-blocked-120-seconds-problem/
https://www.kernel.org/doc/Documentation/sysctl/vm.txt
https://msdn.microsoft.com/en-us/library/bb742613.aspx
http://www.linuxatemyram.com/index.html
http://andylin02.iteye.com/blog/858708
https://lonesysadmin.net/2013/12/22/better-linux-disk-caching-performance-vm-dirty_ratio/
https://linux-mm.org/OOM_Killer
https://www.kernel.org/doc/gorman/html/understand/understand016.html
https://github.com/torvalds/linux/blob/dd23f273d9a765d7f092c1bb0d1cd7aaf668077e/Documentation/filesystems/proc.txt