大疆嵌入式面試:Linux內(nèi)存泄漏與高占用排查方法
Linux 內(nèi)存泄漏與高占用問題是大疆嵌入式面試中的重點內(nèi)容,掌握有效的排查方法和工具至關(guān)重要。Valgrind 和 AddressSanitizer 是排查內(nèi)存泄漏的利器,top、htop 以及 /proc 文件系統(tǒng)則是監(jiān)控和分析內(nèi)存占用的得力助手 。在實際面試中,不僅要熟悉這些工具和方法的理論知識,更要能夠結(jié)合具體的場景和問題,靈活運用,展示出自己解決問題的能力。
對于準備大疆嵌入式面試的同學,建議大家在復習時,多進行實際操作和案例分析,加深對這些排查方法的理解和掌握。可以自己編寫一些包含內(nèi)存問題的測試程序,然后使用各種工具進行排查和解決,積累實踐經(jīng)驗。同時,要關(guān)注內(nèi)存管理在嵌入式系統(tǒng)中的重要性,理解內(nèi)存泄漏和高占用對系統(tǒng)性能的影響機制,這樣在面試中才能更加深入地回答問題,展現(xiàn)出自己的專業(yè)素養(yǎng)和技術(shù)能力 。
Part1.內(nèi)存泄漏:悄無聲息的系統(tǒng)殺手
在 Linux 系統(tǒng)中,內(nèi)存泄漏就像是一個悄無聲息的殺手,慢慢侵蝕著系統(tǒng)的資源。簡單來說,內(nèi)存泄漏是指程序在申請內(nèi)存后,當該內(nèi)存不再被使用時,卻沒有將其釋放回系統(tǒng) ,導致這部分內(nèi)存一直被占用,無法被其他程序使用。就好比你向圖書館借了一本書,看完后卻不歸還,隨著時間推移,越來越多的人借書不還,圖書館的書就會越來越少,可供其他人借閱的資源也就越來越稀缺。
在嵌入式系統(tǒng)里,內(nèi)存資源本就十分有限,內(nèi)存泄漏帶來的后果往往更加嚴重。每一次內(nèi)存泄漏,都像是從系統(tǒng)的 “內(nèi)存儲備庫” 中偷走了一部分資源,隨著泄漏的不斷積累,系統(tǒng)可用內(nèi)存越來越少。這會導致系統(tǒng)頻繁進行內(nèi)存交換操作,從磁盤的虛擬內(nèi)存中讀寫數(shù)據(jù),而磁盤的讀寫速度遠遠慢于內(nèi)存,從而使得系統(tǒng)性能急劇下降,響應(yīng)變得遲緩,原本流暢運行的程序可能變得卡頓甚至無響應(yīng)。當內(nèi)存泄漏嚴重到一定程度,系統(tǒng)再也無法分配到足夠的內(nèi)存來滿足正常的運行需求,就如同水庫干涸,無法為下游提供足夠的水源,系統(tǒng)便會陷入崩潰,造成無人機飛行異常、工業(yè)控制設(shè)備故障等嚴重問題。
(1)內(nèi)存占用過大為什么?
內(nèi)存占用過大的原因可能有很多,以下是一些常見的情況:
- 內(nèi)存泄漏:當程序在運行時動態(tài)分配了內(nèi)存但未正確釋放時,會導致內(nèi)存泄漏。這意味著那部分內(nèi)存將無法再被其他代碼使用,最終導致內(nèi)存占用增加。
- 頻繁的動態(tài)內(nèi)存分配和釋放:如果程序中頻繁進行大量的動態(tài)內(nèi)存分配和釋放操作,可能會導致內(nèi)存碎片化問題。這樣系統(tǒng)將難以有效地管理可用的物理內(nèi)存空間。
- 數(shù)據(jù)結(jié)構(gòu)和算法選擇不當:某些數(shù)據(jù)結(jié)構(gòu)或算法可能對特定場景具有較高的空間復雜度,從而導致內(nèi)存占用過大。在設(shè)計和選擇數(shù)據(jù)結(jié)構(gòu)和算法時應(yīng)綜合考慮時間效率和空間效率。
- 緩存未及時清理:如果程序中使用了緩存機制,并且沒有及時清理或管理緩存大小,就會導致緩存占用過多的內(nèi)存空間。
- 高并發(fā)環(huán)境下資源競爭:在高并發(fā)環(huán)境下,多個線程同時訪問共享資源(包括對內(nèi)存的申請和釋放)可能引發(fā)資源競爭問題。若沒有適當?shù)耐綑C制或鎖策略,可能導致內(nèi)存占用過大。
- 第三方庫或框架問題:使用的第三方庫或框架可能存在內(nèi)存管理不當、內(nèi)存泄漏等問題,從而導致整體程序的內(nèi)存占用過大。
(2)內(nèi)存泄露和內(nèi)存占用過大區(qū)別?
內(nèi)存泄漏指的是在程序運行過程中,動態(tài)分配的內(nèi)存空間沒有被正確釋放,導致這些內(nèi)存無法再被其他代碼使用。每次發(fā)生內(nèi)存泄漏時,系統(tǒng)可用的物理內(nèi)存空間就會減少一部分,最終導致整體的內(nèi)存占用量增加。
而內(nèi)存占用過大則是指程序在運行時所消耗的物理內(nèi)存超出了合理范圍或預期值。除了因為內(nèi)存泄漏導致的額外占用外,其他原因如頻繁的動態(tài)內(nèi)存分配和釋放、數(shù)據(jù)結(jié)構(gòu)和算法選擇不當、緩存管理問題等都可能導致程序的內(nèi)存占用過大。
可以說,內(nèi)存在被正確管理和使用時,即使有一定程度的動態(tài)分配和釋放操作,也不會造成明顯的長期累積效應(yīng),即不會出現(xiàn)持續(xù)性的內(nèi)存占用過大情況。而如果存在未及時釋放或回收的資源(即發(fā)生了內(nèi)存泄漏),隨著時間推移會逐漸積累并導致整體的內(nèi)存占用越來越高。
因此,在排查和解決內(nèi)存占用過大問題時,需要注意是否存在內(nèi)存泄漏,并且還需綜合考慮其他可能導致內(nèi)存占用過大的因素。
(3)產(chǎn)生的原因
我們在進行程序開發(fā)的過程使用動態(tài)存儲變量時,不可避免地面對內(nèi)存管理的問題。程序中動態(tài)分配的存儲空間,在程序執(zhí)行完畢后需要進行釋放。沒有釋放動態(tài)分配的存儲空間而造成內(nèi)存泄漏,是使用動態(tài)存儲變量的主要問題。
一般情況下,作為開發(fā)人員會經(jīng)常使用系統(tǒng)提供的內(nèi)存管理基本函數(shù),如malloc、realloc、calloc、free等,完成動態(tài)存儲變量存儲空間的分配和釋放。但是,當開發(fā)程序中使用動態(tài)存儲變量較多和頻繁使用函數(shù)調(diào)用時,就會經(jīng)常發(fā)生內(nèi)存管理錯誤。
Part2.虛擬內(nèi)存泄露
一般來說,我們觀察系統(tǒng)的內(nèi)存占用喜歡用top命令,然后輸入m,對系統(tǒng)中整體的內(nèi)存占用情況做個排序,然后在重點觀察,內(nèi)存占用排在前幾位的進程,再逐步的分析。
[root@VM-0-2-centos ~]# top -p 5576
top - 18:21:46 up 198 days, 20:07, 2 users, load average: 0.10, 0.04, 0.05
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.7 us, 0.3 sy, 0.0 ni, 99.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 1882008 total, 78532 free, 116516 used, 1686960 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 1606660 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
5576 root 20 0 184064 11248 1124 S 0.0 0.6 10:34.98 nginx雖然top 也可以觀察到單獨的進程的內(nèi)存變化,不過一般不太好比較內(nèi)存變化的規(guī)律,推薦使用pidstat工具,此工具需要先安裝,通過命令:
yum install sysstatpidstat 基本說明如下:
- u:默認的參數(shù),顯示各個進程的cpu使用統(tǒng)計
- r:顯示各個進程的內(nèi)存使用統(tǒng)計
- d:顯示各個進程的IO使用情況
- p:指定進程號
- w:顯示每個進程的上下文切換情況
- t:顯示選擇任務(wù)的線程的統(tǒng)計信息外的額外信息
- T { TASK | CHILD | ALL }
假如我們觀察到如下的內(nèi)存占用情況:pidstat -r -p pid 5
[root@VM-0-2-centos ~]# pidstat -r -p 5981 5
Linux 3.10.0-1127.19.1.el7.x86_64 (VM-0-2-centos) 07/24/2021 _x86_64_ (1 CPU)
06:25:55 PM UID PID minflt/s majflt/s VSZ RSS %MEM Command
06:26:00 PM 0 5981 0.20 0.00 4416 352 0.02 a.out
06:26:05 PM 0 5981 0.00 0.00 4416 352 0.02 a.out
06:26:10 PM 0 5981 0.20 0.00 4456 352 0.02 a.out
06:26:15 PM 0 5981 0.00 0.00 4456 352 0.02 a.out
06:26:20 PM 0 5981 0.00 0.00 4456 352 0.02 a.out
06:26:25 PM 0 5981 0.20 0.00 4496 352 0.02 a.out
06:26:30 PM 0 5981 0.00 0.00 4496 352 0.02 a.out
06:26:35 PM 0 5981 0.20 0.00 4536 352 0.02 a.out
06:26:40 PM 0 5981 0.00 0.00 4536 352 0.02 a.out
06:26:45 PM 0 5981 0.20 0.00 4576 352 0.02 a.out
06:26:50 PM 0 5981 0.00 0.00 4576 352 0.02 a.out
06:26:55 PM 0 5981 0.20 0.00 4616 352 0.02 a.out我們注意下,VSZ即虛擬內(nèi)存的占用每10s增加40,單位為k,即10s增加40k的虛擬內(nèi)存,下面來具體分析下這個程序的內(nèi)存泄露情況。
Part3.分析泄露原因
我們來分析這個進程的內(nèi)存分布情況,來分析這泄露的內(nèi)存有什么特點:
[root@VM-0-2-centos ~]# pmap -x 5981
5981: ./a.out
Address Kbytes RSS Dirty Mode Mapping
0000000000400000 4 4 0 r-x-- a.out
0000000000600000 4 4 4 r---- a.out
0000000000601000 4 4 4 rw--- a.out
00007faab436e000 2720 272 272 rw--- [ anon ]
00007faab4616000 1804 260 0 r-x-- libc-2.17.so
00007faab47d9000 2048 0 0 ----- libc-2.17.so
00007faab49d9000 16 16 16 r---- libc-2.17.so
00007faab49dd000 8 8 8 rw--- libc-2.17.so
00007faab49df000 20 12 12 rw--- [ anon ]
00007faab49e4000 136 108 0 r-x-- ld-2.17.so
00007faab4a06000 2012 212 212 rw--- [ anon ]
00007faab4c03000 8 8 8 rw--- [ anon ]
00007faab4c05000 4 4 4 r---- ld-2.17.so
00007faab4c06000 4 4 4 rw--- ld-2.17.so
00007faab4c07000 4 4 4 rw--- [ anon ]
00007ffe0f3f5000 132 16 16 rw--- [ stack ]
00007ffe0f47c000 8 4 0 r-x-- [ anon ]
ffffffffff600000 4 0 0 r-x-- [ anon ]
---------------- ------- ------- -------
total kB 8940 940 564其中Address為開始的地址,Kbytes是虛擬內(nèi)存的大小,RSS為真實內(nèi)存的大小,Dirty為未同步到磁盤上的臟頁,Mode為內(nèi)存的權(quán)限,rw為可寫可讀,rx為可讀和可執(zhí)行。通過幾次觀察,我們發(fā)現(xiàn):
00007faab436e000 2720 272 272 rw--- [ anon ]為泄露部分,此為匿名內(nèi)存區(qū),也就是沒有映射文件,為malloc或mmap分配的內(nèi)存。同樣是每次增加40K。
此時還是只能大概知道內(nèi)存泄露的位置,我們還先找到具體的代碼位置,這個該怎么分析?代碼的申請,無非是通過malloc和brk這些庫函數(shù)進行內(nèi)存調(diào)用,我們可以用strace跟蹤下。
[root@VM-0-2-centos ~]# strace -f -t -p 5981 -o trace.strace
strace: Process 5981 attached
strace: Process 8519 attached
strace: Process 8533 attached
strace: Process 8547 attached
strace: Process 8557 attached
strace: Process 8575 attached
^Cstrace: Process 5981 detached我們通過-t選項來顯示時間,-f來跟蹤子進程。直接用cat命令查看跟蹤的文件內(nèi)容,會發(fā)現(xiàn)內(nèi)容相當多,只要是系統(tǒng)調(diào)用都打印了出來,可以通過每次增加40k這個有用的信息搜索下:
[root@VM-0-2-centos ~]# grep 40960 trace.strace
5981 19:01:44 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab403a000
5981 19:01:55 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab4030000
5981 19:02:06 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab4026000
5981 19:02:17 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab401c000
5981 19:02:28 mmap(NULL, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7faab4012000至此我們找到了具體的泄露代碼位置。看下這個測試代碼:
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#define _SCHED_H
#define __USE_GNU
#include <bits/sched.h>
#define STACK_SIZE 40960
int func(void *arg)
{
printf("thread enter.\n");
sleep(1);
printf("thread exit.\n");
return 0;
}
int main()
{
int thread_pid;
int status;
int w;
while (1) {
void *addr = mmap(NULL, STACK_SIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0);
if (addr == NULL) {
perror("mmap");
goto error;
}
printf("creat new thread...\n");
thread_pid = clone(&func, addr + STACK_SIZE, CLONE_SIGHAND|CLONE_FS|CLONE_VM|CLONE_FILES, NULL);
printf("Done! Thread pid: %d\n", thread_pid);
if (thread_pid != -1) {
do {
w = waitpid(-1, NULL, __WCLONE | __WALL);
if (w == -1) {
perror("waitpid");
goto error;
}
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
sleep(10);
}
error:
return 0;
}這個測試程序利用mmap申請一塊匿名私有的內(nèi)存,clone為系統(tǒng)函數(shù),pthread_create 和fork底層都是調(diào)用它,用來創(chuàng)建進程/線程,將func的地址指針存放在子進程堆棧的某個位置處,該位置就是該封裝函數(shù)本身返回地址存放的位置,最后一個參數(shù)為func的執(zhí)行參數(shù)。clone可以更靈活控制共享,比如可以控制是否共享內(nèi)存空間,是否共享打開文件,是否共享相同的信號處理函數(shù)等。
我們可以看到,mmap申請內(nèi)存后,需要通過munmap來釋放,這里面沒有釋放,所以導致了虛擬內(nèi)存泄露,這里面申請的內(nèi)存只實際使用了4個字節(jié),即復制了func的指針,其他的內(nèi)存均沒有使用,其實仔細觀察會發(fā)現(xiàn)還有部分的物理內(nèi)存泄露,每次4個字節(jié),可以通過pmap -x 查到。
在 waitpid后面添加:munmap(addr,STACK_SIZE); 即可以實現(xiàn)內(nèi)存的釋放。
如何排查內(nèi)存泄漏
我們平時開發(fā)過程中不可避免的會遇到內(nèi)存泄漏問題,這是常見的問題。既然發(fā)生了內(nèi)存泄漏,我們就要排查內(nèi)存泄漏的問題。想必大家也經(jīng)常會用到以下排查內(nèi)存問題的工具,如下:
- memwatch
- mtrace
- dmalloc
- ccmalloc
- valgrind
- debug_new
Part4.Valgrind 排查工具
在眾多排查 Linux 內(nèi)存泄漏與高占用的工具中,Valgrind 堪稱一把鋒利的 “寶劍”,在嵌入式開發(fā)中被廣泛使用。Valgrind 是一套 Linux 下開放源代碼(GPL V2)的仿真調(diào)試工具的集合 ,它由內(nèi)核(core)以及基于內(nèi)核的其他調(diào)試工具組成。其內(nèi)核就像一個精心搭建的舞臺框架,模擬出一個 CPU 環(huán)境,為其他工具提供表演的場地和基礎(chǔ)服務(wù);而其他工具則如同一個個身懷絕技的演員,在這個舞臺上利用內(nèi)核提供的服務(wù),完成各種特定的內(nèi)存調(diào)試任務(wù)。
(1)安裝與準備
在使用 Valgrind 之前,我們首先得把它安裝到系統(tǒng)中。安裝方法很簡單,對于基于 Debian 或 Ubuntu 的系統(tǒng),只需在終端中輸入 “sudo apt-get install valgrind” ,系統(tǒng)就會自動從軟件源中下載并安裝 Valgrind 及其依賴項,就像從云端倉庫中取來一件趁手的工具。而對于 CentOS 等基于 Red Hat 的系統(tǒng),安裝命令則變?yōu)?“sudo yum install valgrind” ,同樣能快速將這個強大的工具收入囊中。
(2)基本使用命令
安裝完成后,就可以使用 Valgrind 來檢測程序的內(nèi)存問題了。它的基本使用命令格式為:“valgrind [valgrind-options] your-prog [your-prog-options]” 。這里的 “valgrind-options” 是 Valgrind 自身的一些選項,用于控制它的行為和輸出;“your-prog” 是你要檢測的目標程序;“your-prog-options” 則是目標程序運行時需要的參數(shù)。
其中,最常用的選項是 “--tool=memcheck” ,它指定使用 Memcheck 工具,這是 Valgrind 應(yīng)用最廣泛的工具,一個重量級的內(nèi)存檢查器,能夠發(fā)現(xiàn)開發(fā)中絕大多數(shù)內(nèi)存錯誤使用情況。比如使用未初始化的內(nèi)存,使用已經(jīng)釋放了的內(nèi)存,內(nèi)存訪問越界等。若想讓 Valgrind 在程序結(jié)束時詳細敘述每一個內(nèi)存泄漏情況,就可以加上 “--leak-check=full” 選項;如果希望在錯誤報告中顯示可到達的內(nèi)存塊(即雖然沒有釋放,但仍有指針指向的內(nèi)存塊),則可以使用 “--show-reachable=yes” 選項。
(3)工作原理
Valgrind 中的 Memcheck 工具能夠檢測出內(nèi)存問題,關(guān)鍵在于其建立了兩個全局表。第一個是 Valid-Value 表,對于進程的整個地址空間中的每一個字節(jié) (byte),都有與之對應(yīng)的 8 個 bits;對于 CPU 的每個寄存器,也有一個與之對應(yīng)的 bit 向量 。這些 bits 就像一個個小衛(wèi)士,負責記錄該字節(jié)或者寄存器值是否具有有效的、已初始化的值。第二個是 Valid-Address 表,對于進程整個地址空間中的每一個字節(jié) (byte),還有與之對應(yīng)的 1 個 bit,它就像一把地址鎖,負責記錄該地址是否能夠被讀寫。
當程序要讀寫內(nèi)存中某個字節(jié)時,Memcheck 首先會檢查這個字節(jié)對應(yīng)的 A bit。如果該 A bit 顯示該位置是無效位置,就如同試圖打開一把被鎖住的門,Memcheck 則會報告讀寫錯誤。內(nèi)核(core)類似于一個虛擬的 CPU 環(huán)境,這樣當內(nèi)存中的某個字節(jié)被加載到真實的 CPU 中時,該字節(jié)對應(yīng)的 V bit 也會被加載到虛擬的 CPU 環(huán)境中。一旦寄存器中的值被用來產(chǎn)生內(nèi)存地址,或者該值能夠影響程序輸出,Memcheck 就會像一個警惕的監(jiān)察員,檢查對應(yīng)的 V bits,如果該值尚未初始化,則會報告使用未初始化內(nèi)存錯誤。
(4)實際案例分析
為了更直觀地感受 Valgrind 的強大功能,我們來看一個實際案例。假設(shè)有如下一段簡單的 C 語言代碼:
#include <stdio.h>
#include <stdlib.h>
void leak() {
int* ptr = (int*)malloc(10 * sizeof(int));
// 這里沒有釋放ptr指向的內(nèi)存,造成內(nèi)存泄漏
}
void uninit() {
int x;
if (x > 0) {
// 使用了未初始化的變量x,這是一個錯誤
}
}
int main() {
int* arr = (int*)malloc(20 * sizeof(int));
arr[20] = 0; // 數(shù)組越界訪問,訪問了arr[20],而數(shù)組arr只有20個元素,合法索引范圍是0到19
leak();
uninit();
free(arr);
return 0;
}這段代碼中存在內(nèi)存泄漏、使用未初始化變量以及數(shù)組越界訪問的問題。我們使用 Valgrind 來檢測它。首先,使用 “gcc -g -O0 -Wall test.c -o test” 命令進行編譯,其中 “-g” 選項用于生成調(diào)試信息,這對于 Valgrind 準確報告錯誤位置至關(guān)重要;“-O0” 選項表示不進行優(yōu)化,防止優(yōu)化影響調(diào)試結(jié)果;“-Wall” 選項用于開啟所有常見的警告信息。
編譯完成后,執(zhí)行 “valgrind --tool=memcheck --leak-check=full --show-reachable=yes./test” 命令。Valgrind 運行后,會輸出詳細的錯誤報告:
==2596== Memcheck, a memory error detector
==2596== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2596== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==2596== Command:./test
==2596==
==2596== Invalid write of size 4
==2596== at 0x4005C3: main (test.c:16)
==2596== Address 0x51f3050 is 0 bytes after a block of size 80 alloc'd
==2596== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2596== at 0x4005A9: main (test.c:14)
==2596==
==2596== Conditional jump or move depends on uninitialised value(s)
==2596== at 0x4005F5: uninit (test.c:9)
==2596== by 0x40060A: main (test.c:18)
==2596==
==2596== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==2596== at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==2596== by 0x4005D8: leak (test.c:5)
==2596== by 0x400605: main (test.c:17)
==2596==
==2596== LEAK SUMMARY:
==2596== definitely lost: 100 bytes in 1 blocks
==2596== indirectly lost: 0 bytes in 0 blocks
==2596== possibly lost: 0 bytes in 0 blocks
==2596== still reachable: 0 bytes in 0 blocks
==2596== suppressed: 0 bytes in 0 blocks
==2596==
==2596== For counts of detected and suppressed errors, rerun with: -v
==2596== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)從報告中可以清晰地看到,Valgrind 準確地指出了代碼中的數(shù)組越界訪問錯誤(“Invalid write of size 4”),發(fā)生在 “test.c” 文件的 16 行;使用未初始化變量錯誤(“Conditional jump or move depends on uninitialised value (s)”),發(fā)生在 “test.c” 文件的 9 行;以及內(nèi)存泄漏錯誤(“100 bytes in 1 blocks are definitely lost”),發(fā)生在 “test.c” 文件的 5 行,泄漏的內(nèi)存大小為 100 字節(jié)(因為分配了 10 個 int 類型的內(nèi)存空間,每個 int 通常為 4 字節(jié),共 40 字節(jié),但這里報告 100 字節(jié)可能包含了一些內(nèi)部管理信息)。
根據(jù)這些詳細的錯誤信息,我們就可以輕松地定位并修復代碼中的問題,讓程序更加健壯和穩(wěn)定 。通過這個案例,我們可以看到 Valgrind 在排查內(nèi)存問題時的高效和精準,它就像一位經(jīng)驗豐富的偵探,能夠在復雜的代碼迷宮中,準確地找出內(nèi)存相關(guān)的 “罪犯”,為我們解決內(nèi)存泄漏和高占用問題提供了有力的支持。
Part5.AddressSanitizer(ASan)排查工具
AddressSanitizer(ASAN)是一款由 Google 開發(fā)的內(nèi)存錯誤檢測器 ,在檢測內(nèi)存泄漏方面展現(xiàn)出獨特的優(yōu)勢。它主要通過編譯器插樁的方式,在程序編譯階段將內(nèi)存訪問檢查代碼巧妙地插入到目標程序中,就像在程序的關(guān)鍵位置安插了許多 “小哨兵”,時刻監(jiān)視著內(nèi)存的使用情況。
(1)特點與優(yōu)勢
與傳統(tǒng)的內(nèi)存檢測工具相比,AddressSanitizer 最大的特點就是快。它對程序性能的影響相對較小,在運行時的開銷僅會使程序速度減慢約 2 倍 ,而 Valgrind 則可能使程序減慢 10 倍之多。這使得開發(fā)者在使用 ASAN 進行內(nèi)存檢測時,不必花費過多時間等待程序運行結(jié)果,大大提高了調(diào)試效率。
ASAN 能夠檢測到多種類型的內(nèi)存錯誤,除了內(nèi)存泄漏,還包括釋放后使用(懸空指針)、堆緩沖區(qū)溢出、堆棧緩沖區(qū)溢出、全局緩沖區(qū)溢出、在作用域之后使用、初始化順序錯誤等 。它就像一個全能的內(nèi)存問題探測器,能夠全面地掃描程序中的內(nèi)存隱患,為開發(fā)者提供更全面的內(nèi)存錯誤信息。
(2)使用步驟
使用 AddressSanitizer 進行內(nèi)存檢測,步驟也相對簡潔明了。首先是編譯階段,需要在編譯命令中添加 “-fsanitize=address” 選項。例如,使用 GCC 編譯 C++ 程序時,可以這樣操作:
gcc -fsanitize=address -g -O0 -Wall test.c -o test這里的 “-g” 選項用于生成調(diào)試信息,“-O0” 表示不進行優(yōu)化,“-Wall” 用于開啟所有常見的警告信息,這些選項與 Valgrind 編譯時類似,都是為了確保在調(diào)試過程中能夠獲取更準確的信息。
編譯完成后,直接運行生成的可執(zhí)行文件即可。當程序運行過程中出現(xiàn)內(nèi)存錯誤時,AddressSanitizer 會立即捕獲并在控制臺上輸出詳細的錯誤報告。例如,對于下面這段存在內(nèi)存泄漏的代碼:
#include <stdio.h>
#include <stdlib.h>
int main() {
int* ptr = (int*)malloc(10 * sizeof(int));
// 這里沒有釋放ptr指向的內(nèi)存,造成內(nèi)存泄漏
return 0;
}運行編譯后的程序,ASAN 會輸出類似如下的錯誤報告:
=================================================================
==1234==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 40 byte(s) in 1 object(s) allocated from:
#0 0x7f919d0c8b97 in malloc (/lib/x86_64-linux-gnu/libasan.so.5+0x10eb97)
#1 0x40059d in main /home/user/test.c:5
SUMMARY: AddressSanitizer: 40 byte(s) leaked in 1 allocation(s).從報告中可以清晰地看到,ASAN 準確地指出了內(nèi)存泄漏的位置在 “test.c” 文件的第 5 行,泄漏的內(nèi)存大小為 40 字節(jié)(因為分配了 10 個 int 類型的內(nèi)存空間,每個 int 通常為 4 字節(jié),共 40 字節(jié)),并且還給出了內(nèi)存分配的調(diào)用棧信息,方便開發(fā)者快速定位問題根源。
Part6.其他內(nèi)存泄露分析
其實上面的內(nèi)存泄露是我們知道了具體的泄露的進程,然后再做詳細分析。那么如果不知道哪里內(nèi)存泄露了,有什么辦法,可以通過分析meminfo文件,來觀察泄露的類型。
[root@VM-0-2-centos test]# cat /proc/meminfo
MemTotal: 1882008 kB
MemFree: 752948 kB
MemAvailable: 1610108 kB
Buffers: 564900 kB
Cached: 399584 kB
SwapCached: 0 kB
Active: 808140 kB
Inactive: 220812 kB
Active(anon): 64548 kB
Inactive(anon): 488 kB
Active(file): 743592 kB
Inactive(file): 220324 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 0 kB
SwapFree: 0 kB
....meminfo文件是Linux系統(tǒng)中用于顯示內(nèi)存使用情況的詳細信息文件,它位于/proc目錄下,提供了關(guān)于系統(tǒng)內(nèi)存使用的全面信息。通過查看和分析meminfo文件的內(nèi)容,可以了解系統(tǒng)的內(nèi)存使用狀況,包括總內(nèi)存、空閑內(nèi)存、緩存、交換分區(qū)等信息,這對于排查內(nèi)存相關(guān)的問題非常有幫助。
meminfo文件包含的主要信息及其含義如下:
- MemTotal:系統(tǒng)總內(nèi)存大小。
- MemFree:系統(tǒng)空閑內(nèi)存大小。
- MemAvailable:可用內(nèi)存大小,包括空閑內(nèi)存和緩存。
- Buffers:用于緩存數(shù)據(jù)的內(nèi)存大小。
- Cached:用于緩存文件系統(tǒng)的內(nèi)存大小。
- SwapCached:用于緩存交換分區(qū)的內(nèi)存大小。
- Active 和 Inactive:分別表示活動和非活動內(nèi)存大小,即正在使用或最近使用的內(nèi)存和最近沒有使用的內(nèi)存。
- SwapTotal 和 SwapFree:交換分區(qū)總大小和空閑大小。
- Dirty 和 Writeback:等待寫回到磁盤的內(nèi)存大小和正在寫回到磁盤的內(nèi)存大小。
- AnonPages、Mapped、Shmem 等:分別表示用于匿名映射、已映射到文件的內(nèi)存、共享內(nèi)存大小等。
- Slab、SReclaimable、SUnreclaim 等:內(nèi)核數(shù)據(jù)結(jié)構(gòu)緩存的內(nèi)存大小以及可回收和不可回收的Slab內(nèi)存大小。
- KernelStack、PageTables 等:內(nèi)核棧的內(nèi)存大小和頁面表的內(nèi)存大小。
- CommitLimit 和 Committed_AS:可用內(nèi)存可支持的最大內(nèi)存大小和已分配的內(nèi)存大小,包括內(nèi)存和交換分區(qū)。
- VmallocTotal、VmallocUsed 等:虛擬內(nèi)存總大小和已使用的虛擬內(nèi)存大小。
排查內(nèi)存問題時,可以通過以下步驟進行:
- 首先,使用
cat /proc/meminfo命令查看meminfo文件的內(nèi)容,了解系統(tǒng)的整體內(nèi)存使用情況。 - 分析MemTotal和MemFree的值,了解系統(tǒng)的總內(nèi)存和可用空閑內(nèi)存。
- 注意MemAvailable的值,它表示應(yīng)用程序可用的內(nèi)存,與MemFree的區(qū)別在于MemAvailable考慮了Buffers和Cached的大小,這些通常在系統(tǒng)需要時可以被回收。
- 檢查SwapUsage(雖然meminfo文件中沒有直接顯示SwapUsage,但可以通過SwapTotal和SwapFree計算得出),如果Swap空間被大量使用,可能意味著物理內(nèi)存不足。
- 注意Active、Inactive、Dirty和Writeback等值,這些指標可以幫助你了解系統(tǒng)當前的內(nèi)存使用模式和可能的性能瓶頸。
- 如果發(fā)現(xiàn)某些特定類型的內(nèi)存使用異常高(如AnonPages、Shmem等),可能需要進一步調(diào)查這些類型的內(nèi)存使用情況,以確定是否存在內(nèi)存泄漏或其他問題。
- 使用其他工具如
free、vmstat、top或htop等命令提供的信息與meminfo文件的內(nèi)容進行對比,以獲得更全面的系統(tǒng)內(nèi)存使用情況視圖。
通過綜合分析meminfo文件的內(nèi)容和其他相關(guān)工具的輸出,可以有效地排查和解決Linux系統(tǒng)中的內(nèi)存相關(guān)問題
Part7.高內(nèi)存占用:系統(tǒng)性能的隱形障礙
高內(nèi)存占用就像是系統(tǒng)性能的隱形障礙,雖然不像內(nèi)存泄漏那樣直觀,但同樣會給系統(tǒng)帶來嚴重的影響。當系統(tǒng)內(nèi)存被大量占用時,就好比一個原本寬敞的房間堆滿了雜物,活動空間變得狹小,行動也變得困難。
在嵌入式系統(tǒng)中,高內(nèi)存占用可能會導致系統(tǒng)響應(yīng)速度明顯變慢。當系統(tǒng)需要處理新的任務(wù)或響應(yīng)用戶的操作時,由于內(nèi)存中已經(jīng)充滿了各種數(shù)據(jù)和程序,沒有足夠的空閑內(nèi)存來快速加載和運行新的任務(wù),就像倉庫里沒有足夠的空間存放新的貨物,只能花費時間去整理和騰出空間,這就使得系統(tǒng)的響應(yīng)變得遲緩。原本能夠迅速執(zhí)行的指令,現(xiàn)在可能需要等待較長時間才能完成,用戶會明顯感覺到設(shè)備的操作變得卡頓,例如無人機在執(zhí)行復雜飛行指令時,反應(yīng)變得不及時,工業(yè)控制設(shè)備對傳感器數(shù)據(jù)的處理出現(xiàn)延遲等。
高內(nèi)存占用還會極大地降低系統(tǒng)的多任務(wù)處理能力。在現(xiàn)代嵌入式應(yīng)用中,很多設(shè)備需要同時運行多個任務(wù),如無人機既要實時處理飛行姿態(tài)數(shù)據(jù),又要傳輸高清圖像、接收遠程控制指令等。如果內(nèi)存被某個或某些任務(wù)大量占用,其他任務(wù)就無法獲得足夠的內(nèi)存資源來正常運行,就像多個工人在一個狹小的工作空間里工作,互相干擾,效率低下。這可能導致一些任務(wù)被迫暫停或中斷,影響整個系統(tǒng)的功能完整性和穩(wěn)定性,甚至可能引發(fā)任務(wù)之間的沖突,導致系統(tǒng)出現(xiàn)錯誤或異常行為。
高內(nèi)存占用與內(nèi)存泄漏之間也存在著緊密的關(guān)聯(lián)。內(nèi)存泄漏是導致高內(nèi)存占用的一個重要原因,隨著內(nèi)存泄漏的不斷發(fā)生,系統(tǒng)中被占用卻無法釋放的內(nèi)存越來越多,直接導致了系統(tǒng)內(nèi)存占用率的持續(xù)上升 。就像一個不斷漏水的水桶,水不斷地流進桶里卻無法流出,桶里的水就會越來越滿。而高內(nèi)存占用又會加劇內(nèi)存泄漏的影響,因為當系統(tǒng)內(nèi)存緊張時,程序在分配和釋放內(nèi)存時更容易出現(xiàn)錯誤,進一步增加了內(nèi)存泄漏的風險,形成一個惡性循環(huán)。例如,在內(nèi)存緊張的情況下,程序可能會為了獲取內(nèi)存而采取一些不安全的內(nèi)存分配方式,或者在釋放內(nèi)存時出現(xiàn)誤判,導致本應(yīng)釋放的內(nèi)存沒有被釋放,從而加重內(nèi)存泄漏問題。
★排查手段一:top 與 htop 實時監(jiān)控
在排查 Linux 內(nèi)存泄漏與高占用問題時,top 和 htop 命令是我們最先會用到的 “偵察兵”,它們就像系統(tǒng)的實時監(jiān)控儀表盤,能夠讓我們快速了解系統(tǒng)內(nèi)存的使用狀況,直觀地發(fā)現(xiàn)那些占用內(nèi)存過高的 “嫌疑進程”。
①top 命令:系統(tǒng)資源的實時監(jiān)視器
top 命令是 Linux 系統(tǒng)中常用的性能分析工具,它能夠?qū)崟r顯示系統(tǒng)中各個進程的資源占用狀況 ,如同一個忙碌的指揮中心,時刻匯報著系統(tǒng)的 “健康狀態(tài)”。在終端中輸入 “top” 命令,即可打開這個實時監(jiān)控界面,系統(tǒng)默認每 3 秒刷新一次信息 ,讓你能夠及時捕捉到系統(tǒng)狀態(tài)的變化。
在 top 命令的輸出界面中,頭部區(qū)域是系統(tǒng)整體概覽的展示區(qū),這里包含了當前時間、系統(tǒng)已經(jīng)運行的時長、登錄用戶數(shù),以及系統(tǒng)負載等關(guān)鍵信息 ,就像汽車儀表盤上的時間、里程和負載指示燈,讓你對系統(tǒng)的整體運行環(huán)境有一個初步了解。同時,這里還會顯示 CPU 和內(nèi)存的使用情況,包括總內(nèi)存、已用內(nèi)存、空閑內(nèi)存等,讓你一眼就能看出系統(tǒng)內(nèi)存的 “庫存” 狀態(tài)。
主體部分則是以列表形式展示系統(tǒng)中的各個進程,每一行都代表著一個進程,詳細列出了進程的各種信息,如進程 ID(PID),就像是進程的身份證號碼,用于唯一標識每個進程;用戶(USER),表明該進程是由哪個用戶啟動的;優(yōu)先級(PR),反映了進程在系統(tǒng)資源分配中的優(yōu)先程度;虛擬內(nèi)存使用量(VIRT),表示進程占用的虛擬內(nèi)存大小,包括進程使用的代碼、數(shù)據(jù)和共享庫等;
物理內(nèi)存使用量(RES),即進程實際占用的物理內(nèi)存大小;共享內(nèi)存(SHR),是進程與其他進程共享的內(nèi)存部分;狀態(tài)(S),顯示進程當前是正在運行(R)、休眠(S)、停止(T)還是處于僵尸狀態(tài)(Z);CPU 使用率(% CPU),直觀地展示了該進程占用 CPU 資源的百分比;內(nèi)存使用率(% MEM),表示進程占用物理內(nèi)存和總內(nèi)存的比例;累計 CPU 時間(TIME+),記錄了該進程從啟動到當前累計使用的 CPU 時間;以及進程名稱(COMMAND),讓你清楚知道這個進程對應(yīng)的是哪個程序或服務(wù)。
在實際使用 top 命令時,我們還可以通過一些選項和交互命令來更靈活地獲取所需信息。比如,使用 “-d” 選項可以設(shè)置更新間隔時間,例如 “top -d 2” 表示每 2 秒刷新一次信息,就像你可以調(diào)整汽車儀表盤上某些數(shù)據(jù)的更新頻率,以適應(yīng)不同的觀察需求。如果只想監(jiān)控特定的進程 ID,可以使用 “-p” 選項,如 “top -p 1234”,只關(guān)注 PID 為 1234 的進程,這對于排查某個關(guān)鍵服務(wù)的內(nèi)存使用情況非常有用。“-u” 選項則用于指定要監(jiān)控的用戶所屬的進程,如 “top -u username”,方便查看特定用戶相關(guān)進程的內(nèi)存占用。
而在 top 命令的交互界面中,按下 “M” 鍵,就可以按內(nèi)存使用率對進程進行排序,讓占用內(nèi)存較多的進程排在前面,快速定位到內(nèi)存占用大戶,就像在一個貨物清單中,按照貨物重量進行排序,找出最重的那些貨物。按下 “P” 鍵則可以按 CPU 使用率排序,幫助我們發(fā)現(xiàn)占用 CPU 資源較多的進程。如果想要殺死某個進程,可以按下 “k” 鍵,然后輸入要殺死的進程 ID;若要重新調(diào)整進程的優(yōu)先級,按下 “r” 鍵,再輸入進程 ID 和新的優(yōu)先級值即可。
②htop 命令:更直觀強大的監(jiān)控利器
htop 命令是 top 命令的增強版本,它在功能和用戶體驗上都有了顯著的提升,就像是在普通相機的基礎(chǔ)上,增加了更多高級功能和更清晰的顯示效果,讓我們對系統(tǒng)內(nèi)存狀況的監(jiān)控更加得心應(yīng)手。
與 top 命令類似,在終端輸入 “htop” 即可啟動這個強大的監(jiān)控工具。htop 的界面布局與 top 有相似之處,但更加美觀和直觀 。頭部同樣展示了系統(tǒng)整體概覽信息,包括 CPU 使用率、內(nèi)存使用率、交換空間使用率等,不過它是以彩色條形圖的形式呈現(xiàn),讓你能更直觀地感受到系統(tǒng)資源的使用比例,就像一個更生動的儀表盤,不同顏色的進度條代表著不同資源的使用情況。同時,這里也顯示了系統(tǒng)時間、運行時間、登錄用戶數(shù)和系統(tǒng)負載等信息。
主體部分的進程列表也列出了與 top 命令類似的進程信息,但 htop 的顯示更加清晰,并且支持鼠標操作,操作起來更加便捷 。例如,你可以直接用鼠標點擊某一列的標題,對進程按照該列信息進行排序,而無需像 top 命令那樣記住特定的按鍵操作。
htop 命令還提供了許多豐富的功能和交互命令。按下 “F2” 鍵可以進入設(shè)置菜單,在這里你可以根據(jù)自己的需求和喜好,自定義顯示的列、顏色主題、排序方式等,就像給你的監(jiān)控工具進行個性化定制,讓它更符合你的使用習慣。按下 “F3” 鍵可以搜索進程,當系統(tǒng)中進程眾多時,通過輸入進程名稱或關(guān)鍵字,就能快速定位到相關(guān)進程,節(jié)省查找時間。如果想要殺死某個進程,在 htop 中操作更加簡單,直接選擇要殺死的進程,然后按下 “F9” 鍵即可,無需手動輸入進程 ID,這對于需要快速處理問題的場景非常實用。
使用 “+” 和 “-” 鍵可以輕松調(diào)整進程的優(yōu)先級,選擇要調(diào)整優(yōu)先級的進程后,按 “+” 鍵提高優(yōu)先級,按 “-” 鍵降低優(yōu)先級,方便你根據(jù)實際情況優(yōu)化系統(tǒng)資源分配。按下 “F5” 鍵可以切換到樹狀圖模式,顯示進程之間的父子關(guān)系,這對于分析復雜的進程結(jié)構(gòu)和依賴關(guān)系非常有幫助,就像查看一個家族樹,清晰地了解各個進程之間的傳承和關(guān)聯(lián)。按下 “F6” 鍵可以選擇不同的排序方式,除了按 CPU 使用率和內(nèi)存使用率排序外,還可以根據(jù)其他指標進行排序,滿足不同的監(jiān)控需求。
通過 top 和 htop 命令的實時監(jiān)控,我們能夠快速定位到高內(nèi)存占用的進程,為進一步深入排查內(nèi)存問題提供了重要線索。它們就像我們在排查內(nèi)存泄漏與高占用問題道路上的得力助手,讓我們在面對復雜的系統(tǒng)內(nèi)存狀況時,能夠做到心中有數(shù),有的放矢地進行后續(xù)的分析和處理。
★排查手段二:/proc 文件系統(tǒng)深度剖析
在 Linux 系統(tǒng)中,/proc 文件系統(tǒng)是一個非常強大且特殊的工具,它就像一個系統(tǒng)信息的寶藏庫,為我們提供了豐富的系統(tǒng)運行時信息,其中與內(nèi)存相關(guān)的文件,更是排查內(nèi)存泄漏與高占用問題的關(guān)鍵線索來源。
①/proc/[PID]/status:進程狀態(tài)與內(nèi)存概況
在 /proc 文件系統(tǒng)中,每個正在運行的進程都有一個以其進程 ID(PID)命名的目錄 ,如 /proc/1234,其中的 status 文件就像是這個進程的 “健康報告”,詳細記錄了進程的各種狀態(tài)信息以及內(nèi)存使用的概況。
打開 /proc/[PID]/status 文件,我們可以看到一系列關(guān)鍵信息。其中,“VmPeak” 表示進程虛擬內(nèi)存使用量的峰值 ,它記錄了進程在運行過程中曾經(jīng)達到的最高虛擬內(nèi)存占用情況,就像一個運動員在比賽中跳出的最高紀錄,反映了進程在內(nèi)存需求上的最大值。“VmSize” 代表當前進程虛擬內(nèi)存的實際使用量 ,這是進程當前占用的虛擬內(nèi)存大小,包括代碼段、數(shù)據(jù)段、堆、棧以及共享庫等所占用的虛擬內(nèi)存空間,是我們了解進程當前內(nèi)存 “胃口” 大小的重要指標。“VmLck” 表示被鎖定的內(nèi)存大小 ,當進程使用了 mlock () 函數(shù)等機制將部分內(nèi)存鎖定,使其不能被交換到磁盤時,這部分內(nèi)存的大小就會記錄在這里,被鎖定的內(nèi)存就像是被特殊標記的 “保險箱”,始終保留在物理內(nèi)存中。
“VmHWM” 即 High Water Mark,是進程實際使用物理內(nèi)存的峰值 ,它記錄了進程在運行過程中曾經(jīng)占用物理內(nèi)存的最大值,反映了進程在物理內(nèi)存使用上的最高水平。“VmRSS” 則是進程當前實際占用的物理內(nèi)存大小 ,這是我們最關(guān)注的指標之一,它直接反映了進程當前實實在在占用的物理內(nèi)存資源,就像一個房間里實際擺放的物品所占據(jù)的空間大小。
“VmData” 表示進程數(shù)據(jù)段的大小 ,包括已初始化的數(shù)據(jù)和未初始化的數(shù)據(jù),這部分內(nèi)存主要用于存儲進程運行時的數(shù)據(jù)變量等信息。“VmStk” 代表進程用戶態(tài)棧的大小 ,棧是一種后進先出的數(shù)據(jù)結(jié)構(gòu),用于函數(shù)調(diào)用、局部變量存儲等,VmStk 記錄了進程棧所占用的內(nèi)存空間。“VmExe” 是進程代碼段的大小 ,也就是進程可執(zhí)行文件所占用的內(nèi)存空間,這里存放著進程運行的指令代碼。“VmLib” 表示進程使用的庫映射到虛擬內(nèi)存空間的大小 ,當進程依賴各種動態(tài)鏈接庫時,這些庫會被映射到進程的虛擬內(nèi)存空間中,VmLib 記錄了這部分庫所占用的內(nèi)存大小。
通過分析這些內(nèi)存相關(guān)的字段,我們可以對進程的內(nèi)存使用情況有一個全面的了解。例如,當我們發(fā)現(xiàn) “VmRSS” 不斷增長,而 “VmSize” 也同步增加,且沒有明顯的釋放跡象時,就可能存在內(nèi)存泄漏的隱患。這就好比一個人不斷地往房間里搬東西,卻從不清理,房間里的物品(內(nèi)存占用)就會越來越多。而如果 “VmPeak” 與當前的 “VmSize” 相差很大,說明進程在運行過程中曾經(jīng)有過較大的內(nèi)存需求波動,這也可能是我們需要進一步關(guān)注的點,也許在某個特定的操作或時間段內(nèi),進程的內(nèi)存使用出現(xiàn)了異常增長。
②/proc/[PID]/smaps:內(nèi)存映射區(qū)域的詳細剖析
/proc/[PID]/smaps 文件則像是一個放大鏡,為我們提供了比 /proc/[PID]/status 文件更加詳細的內(nèi)存映射區(qū)域信息 ,讓我們能夠深入了解進程內(nèi)存使用的每一個細節(jié)。
smaps 文件中,每一個內(nèi)存映射區(qū)域都有詳細的記錄,包括該區(qū)域的起始地址和結(jié)束地址,以及一系列描述內(nèi)存使用情況的字段。“Size” 表示該虛擬內(nèi)存區(qū)域的大小 ,它明確了這個內(nèi)存塊在虛擬地址空間中的范圍,就像劃定了一塊土地的邊界。“Rss” 是實際分配給該段落物理頁框的數(shù)量(單位 KB) ,也就是該內(nèi)存區(qū)域當前實際占用的物理內(nèi)存大小,它反映了進程在物理內(nèi)存中實實在在占據(jù)的 “地盤”。“Pss(Proportional Set Size)” 表示共享頁面按比例分攤后的大小 ,對于共享內(nèi)存區(qū)域,Pss 能夠更準確地反映該進程實際使用的內(nèi)存份額。
當多個進程共享同一個動態(tài)鏈接庫時,庫所占用的內(nèi)存會根據(jù)每個進程的使用情況按比例分攤,Pss 就是這個分攤后的大小,避免了在共享內(nèi)存情況下單純以 Rss 計算可能導致的內(nèi)存使用統(tǒng)計偏差。“Shared_Clean” 和 “Shared_Dirty” 分別表示和其它進程共享的未修改的 page 的大小以及共享的被改寫的 page 的大小 ,這兩個字段幫助我們了解共享內(nèi)存中數(shù)據(jù)的狀態(tài)。如果 “Shared_Dirty” 不斷增加,可能意味著多個進程對共享內(nèi)存的頻繁修改,這可能會帶來數(shù)據(jù)一致性等問題,也可能影響內(nèi)存的使用效率。
“Private_Clean” 和 “Private_Dirty” 則是未被改寫的私有頁面的大小和已被改寫的私有頁面的大小 ,用于描述進程私有的內(nèi)存頁面的狀態(tài)。“Referenced” 表示已經(jīng)被引用過的頁面數(shù)目 ,它反映了內(nèi)存頁面的使用活躍度,被引用次數(shù)多的頁面說明是進程運行過程中經(jīng)常訪問的數(shù)據(jù)或代碼所在的頁面。“Anonymous” 是不關(guān)聯(lián)任何磁盤文件的匿名映射空間大小 ,例如通過 malloc () 函數(shù)分配的堆內(nèi)存,通常就是匿名映射的,這個字段可以幫助我們了解進程中匿名內(nèi)存的使用情況。“Swap” 表示當前已交換出去至 swap 分區(qū)中的數(shù)據(jù)量 ,當物理內(nèi)存不足時,系統(tǒng)會將一些不常用的內(nèi)存頁面交換到磁盤的 swap 分區(qū)中,Swap 字段記錄了這部分被交換出去的數(shù)據(jù)量。如果 Swap 的值持續(xù)增加,說明系統(tǒng)內(nèi)存緊張,頻繁進行內(nèi)存交換,這會嚴重影響系統(tǒng)性能,因為磁盤的讀寫速度遠遠慢于內(nèi)存。
在實際排查內(nèi)存問題時,我們可以通過監(jiān)控 smaps 文件中相關(guān)字段的變化來發(fā)現(xiàn)異常。比如,當某個進程的 “Rss” 持續(xù)上升,而 “Pss” 也同步增加,且 “Anonymous” 區(qū)域不斷擴大,同時 “Swap” 也開始出現(xiàn)增長趨勢,這很可能是該進程存在內(nèi)存泄漏或者內(nèi)存使用不合理的情況。我們可以進一步分析具體是哪些內(nèi)存映射區(qū)域出現(xiàn)了異常,結(jié)合進程的功能和代碼邏輯,定位到問題的根源。例如,如果發(fā)現(xiàn)某個動態(tài)庫的內(nèi)存映射區(qū)域中 “Shared_Dirty” 異常高,可能是該庫在使用共享內(nèi)存時存在頻繁的寫入操作,導致內(nèi)存使用效率低下,甚至可能存在數(shù)據(jù)競爭等問題,需要進一步檢查庫的使用方式和相關(guān)代碼。
通過深入分析 /proc/[PID]/status 和 /proc/[PID]/smaps 文件,我們能夠獲取到進程內(nèi)存使用的詳細信息,為排查 Linux 內(nèi)存泄漏與高占用問題提供有力的支持,就像一位經(jīng)驗豐富的醫(yī)生通過詳細的檢查報告,準確地診斷出病人身體的問題所在。
Part8.面試真題實戰(zhàn)演練
為了讓大家更清楚地了解在實際面試中如何運用這些排查方法,我們來模擬一個大疆面試中可能出現(xiàn)的場景。假設(shè)你遇到了一個多線程程序,在運行一段時間后,系統(tǒng)出現(xiàn)了內(nèi)存泄漏和高內(nèi)存占用的問題,導致系統(tǒng)性能嚴重下降。現(xiàn)在,面試官要求你找出問題所在并提出解決方案 。
(1)場景描述
這是一個基于 Linux 系統(tǒng)的多線程數(shù)據(jù)處理程序,用于實時處理傳感器采集的數(shù)據(jù)。程序中有多個線程,分別負責數(shù)據(jù)采集、數(shù)據(jù)處理和結(jié)果存儲。在程序運行初期,一切正常,但隨著時間的推移,系統(tǒng)內(nèi)存占用不斷上升,最終導致系統(tǒng)響應(yīng)遲緩,甚至出現(xiàn)死機現(xiàn)象。
(2)排查步驟
- 初步觀察與進程定位:首先,使用 top 或 htop 命令,查看系統(tǒng)中各個進程的內(nèi)存占用情況。通過這一步,我們發(fā)現(xiàn)數(shù)據(jù)處理線程所在的進程內(nèi)存占用增長異常明顯,確定了問題所在的進程,為后續(xù)的排查工作明確了方向。這就像是在一片森林中找到了冒煙的那片區(qū)域,知道了問題的大致范圍。
- 深入分析進程內(nèi)存使用:進入 /proc/[PID]/status 文件,查看該進程的內(nèi)存使用概況,發(fā)現(xiàn) VmRSS 和 VmSize 持續(xù)增長,這進一步驗證了內(nèi)存泄漏的可能性。接著,查看 /proc/[PID]/smaps 文件,詳細分析內(nèi)存映射區(qū)域,發(fā)現(xiàn)某個動態(tài)分配的內(nèi)存區(qū)域 Rss 不斷增加,且沒有釋放的跡象,初步確定了內(nèi)存泄漏的位置就在這個動態(tài)內(nèi)存分配的部分。這就像在鎖定的問題區(qū)域內(nèi),通過更細致的搜索,找到了具體的 “問題點”。
- 利用工具精確檢測:為了更準確地定位內(nèi)存泄漏的具體代碼行,使用 Valgrind 工具。在編譯程序時,加上 “-g” 選項生成調(diào)試信息,然后執(zhí)行 “valgrind --tool=memcheck --leak-check=full --show-reachable=yes./your-program” 命令。Valgrind 運行后,輸出了詳細的錯誤報告,指出了在數(shù)據(jù)處理線程中的某個函數(shù)里,存在內(nèi)存分配后未釋放的問題,就像用高精度的探測器,精準地找到了內(nèi)存泄漏的 “漏洞”。
- 分析多線程環(huán)境下的內(nèi)存問題:由于這是一個多線程程序,線程之間的同步和資源競爭也可能導致內(nèi)存問題。檢查線程同步機制,發(fā)現(xiàn)線程之間在訪問共享內(nèi)存時,沒有正確地使用互斥鎖,導致內(nèi)存訪問混亂,進一步加劇了內(nèi)存問題。這就像是在一個多人合作的項目中,發(fā)現(xiàn)了人員之間的協(xié)作流程出現(xiàn)了混亂,影響了整體的工作效果。
- 解決問題與驗證:根據(jù)排查結(jié)果,在數(shù)據(jù)處理線程的對應(yīng)函數(shù)中,添加正確的內(nèi)存釋放代碼,并確保線程同步機制的正確性,使用互斥鎖來保護共享內(nèi)存的訪問。修改代碼后,重新編譯并運行程序,再次使用 top、htop 以及 Valgrind 等工具進行檢查,發(fā)現(xiàn)內(nèi)存占用穩(wěn)定,沒有再出現(xiàn)泄漏的情況,系統(tǒng)性能也恢復正常,這表明問題已得到有效解決 。這就像是給出現(xiàn)故障的機器換上了新的零件,并進行了全面的測試,確保機器能夠正常運轉(zhuǎn)。
通過這個實戰(zhàn)演練,我們可以看到,在面對 Linux 內(nèi)存泄漏與高占用問題時,只要掌握了正確的排查方法和工具,按照合理的步驟進行分析,就能夠準確地找到問題的根源,并提出有效的解決方案,這也是在大疆嵌入式面試中,面試官希望看到的解決問題的能力和思路。
























