eBPF+內核調優:動態追蹤內存碎片的終極武器
在計算機系統中,內存管理至關重要,而內存碎片是影響其效率的關鍵問題。內存碎片指內存分配和釋放過程中,產生的無法有效利用的小塊內存。它分為內部和外部碎片。內部碎片是進程已分配卻未用完的內存,比如租大倉庫只使用部分空間。外部碎片則是內存中有零散空閑塊,但因不連續無法滿足大內存請求,像零散拼圖拼不成畫。
內存碎片危害大,會降低內存利用率,增加程序響應時間,嚴重時導致內存分配失敗,引發系統崩潰。傳統內存管理技術難以徹底解決該問題,隨著應用程序復雜,內存碎片問題愈發嚴重。這時,eBPF 技術出現了。它能深入系統底層,精準追蹤內存分配和釋放過程,幫助剖析內存碎片成因,為解決這一難題提供新方向。
一、eBPF技術簡介最后
了解內存碎片問題后,再來認識 eBPF 技術。它最初用于網絡數據包過濾,如今已演變成通用執行引擎。能在內核構建安全虛擬機環境,在不改動內核源碼、不重啟系統的情況下,動態加載自定義程序,精準追蹤內存分配與釋放,進而解決內存碎片問題 。
1.1 eBPF的起源與發展
eBPF,全稱是擴展伯克利數據包過濾器(Extended Berkeley Packet Filter) ,它的故事始于 1992 年。當時,Steven McCanne 和 Van Jacobson 為了解決網絡數據包過濾的效率問題,開發了 BPF。最初的 BPF 就像是一個專注于網絡過濾的工匠,它允許用戶在內核空間運行過濾代碼,大大提高了網絡數據包處理的效率,相比當時的其他技術快了 20 倍之多。tcpdump 就是基于 BPF 技術實現的,它讓網絡管理員能夠方便地捕獲和分析網絡數據包,就像用一把精準的手術刀,對網絡流量進行精細的剖析。
隨著時間的推移,計算機系統變得越來越復雜,應用場景也越來越多樣化。傳統的 BPF 逐漸顯露出它的局限性,它就像一個只能在特定領域發揮作用的專家,難以滿足日益增長的內核編程需求。于是,在 2014 年,eBPF 應運而生。它就像是 BPF 的一次華麗升級,對傳統的 BPF 進行了全面擴展,使其從單純的網絡過濾工具,轉變為一種通用的內核編程技術,擁有了更強大的功能和更廣泛的應用領域。
1.2 eBPF的工作原理
eBPF 的工作原理可以用一個形象的比喻來理解。假設內核是一個龐大而復雜的工廠,里面有許多不同的生產線(內核子系統)在運作。eBPF 就像是一個靈活的機器人,它可以根據你的需求,被派往不同的生產線(掛載到內核鉤子上),執行特定的任務(運行用戶定義的代碼)。
具體來說,開發人員首先使用高級語言(如 C 語言)編寫 eBPF 程序,這個程序就像是給機器人下達的任務指令。然后,通過 LLVM 等編譯器,將 eBPF 程序編譯成字節碼,這就好比將任務指令翻譯成機器人能夠理解的機器語言。接著,這些字節碼被加載到內核中,在 eBPF 虛擬機中運行。在加載之前,字節碼會經過嚴格的驗證,確保它不會對內核的安全性和穩定性造成威脅,就像在機器人進入工廠之前,要對它進行全面的安全檢查,防止它攜帶危險物品進入工廠。
當內核中發生特定的事件(如網絡數據包的接收、系統調用的發生等)時,掛載在相應鉤子上的 eBPF 程序就會被觸發執行,它可以訪問內核中的數據結構,根據預設的邏輯進行處理,然后將結果返回或者存儲在特定的映射(Map)中,以便用戶空間的程序可以獲取這些信息。
1.3 eBPF的獨特優勢
與傳統的追蹤技術相比,eBPF 在性能、安全性、靈活性等方面都展現出了巨大的優勢。
在性能方面,eBPF 程序直接在內核空間運行,避免了頻繁的用戶態和內核態之間的上下文切換,大大提高了執行效率。這就好比在一個工廠中,傳統的方式是工人要頻繁地在不同的車間(用戶態和內核態)之間來回奔波傳遞信息,效率低下;而 eBPF 則像是在車間內部直接設置了一個快速通道,信息可以直接在內部傳遞,大大提高了工作效率。
安全性是 eBPF 的另一大亮點。在加載到內核之前,eBPF 程序會經過嚴格的驗證器檢查,確保程序不會訪問非法內存、陷入無限循環或者執行其他危險操作,從而避免了內核崩潰或安全漏洞的出現。這就像一個嚴格的安檢系統,對進入工廠的每一個物品(eBPF 程序)都進行仔細檢查,只有通過安檢的物品才能進入工廠,確保了工廠的安全運行。
eBPF 還具有極高的靈活性。它支持多種類型的映射,用于在用戶空間和內核空間之間高效地共享數據,并且可以通過事件驅動的方式,在不同的內核事件發生時觸發執行。這使得開發人員可以根據具體的需求,靈活地編寫 eBPF 程序,實現各種復雜的功能。例如,在網絡監控中,可以根據不同的網絡事件,實時地調整監控策略;在性能分析中,可以針對不同的性能指標,進行定制化的分析。
正是這些獨特的優勢,使得 eBPF 在眾多的內核編程技術中脫穎而出,成為了解決內存碎片問題的理想選擇。
1.4 ebpf環境搭建
①編譯運行源碼samples/bpf中的代碼
- 下載內核源碼并解壓
- /bin/sh: scripts/mod/modpost: No such file or directory 遇到這種錯誤,需要make scripts
- make M=samples/bpf 需要.config文件,需要保證這些項存在
- 遇到錯誤libcrypt1.so.1 not found,執行如下代碼:
$ cd /tmp$ apt -y download libcrypt1$ dpkg-deb -x libcrypt1_1%3a4.4.25-2_amd64.deb .$ cp -av lib/x86_64-linux-gnu/* /lib/x86_64-linux-gnu/$ apt -y --fix-broken install
5.編譯成功,可以執行samples/bpf中的可執行文件。
②編譯運行自己開發的代碼
1. 下載linux source code,編譯內核并升級
git clone https://github.com/torvalds/linux.gitcd linux/git checkout -b v5.0 v5.0
配置文件
cp -a /boot/config-4.14.81.bm.15-amd64 ./.configecho 'CONFIG_BPF=yCONFIG_BPF_SYSCALL=yCONFIG_BPF_JIT=yCONFIG_HAVE_EBPF_JIT=yCONFIG_BPF_EVENTS=yCONFIG_FTRACE_SYSCALLS=yCONFIG_FUNCTION_TRACER=yCONFIG_HAVE_DYNAMIC_FTRACE=yCONFIG_DYNAMIC_FTRACE=yCONFIG_HAVE_KPROBES=yCONFIG_KPROBES=yCONFIG_KPROBE_EVENTS=yCONFIG_ARCH_SUPPORTS_UPROBES=yCONFIG_UPROBES=yCONFIG_UPROBE_EVENTS=yCONFIG_DEBUG_FS=yCONFIG_DEBUG_INFO_BTF=y' >> ./.config
需要添加sid源安裝dwarves
apt install dwarvesmake oldconfigapt install libssl-devmakemake modules_installmake installreboot
此時:
uname -aLinux n231-238-061 5.0.0 #1 SMP Mon Dec 13 05:38:52 UTC 2021 x86_64 GNU/Linux
③編譯bpf helloworld
切換到helloworld目錄
sed -i 's;/kernel-src;/root/linux;' Makefilemake
cp /root/linux/include/uapi/linux/bpf.h /usr/include/linux/bpf.h
執行./monitor-exec,有報錯
./monitor-exec: error while loading shared libraries: libbpf.so: cannot open shared object file: No such file or directory
④解決方法
cd /root/linux/tools/lib/bpf/makemake install
在 /etc/ld.so.conf 中添加 /usr/local/lib64這一行,運行 sudo ldconfig 重新生成動態庫配置信息。
~/linux/tools/lib/bpf# ldconfig -v 2>/dev/null | grep libbpf libbpf.so.0 -> libbpf.so.0.5.0 libbpf.so -> libbpf.so
⑤最終執行情況:
可能需要安裝apt-get install gcc-multilib g++-multilib。
二、eBPF框架
在開始說明之前先解釋下eBPF上的名詞,來幫忙更好地理解:
- eBPF bytecode:將C語言寫的鉤子代碼,通過clang編譯成二進制字節碼,通過程序加載到內核中,鉤子觸發后在kernel "虛擬機"中運行。
- JIT: Just-in-time compilation,將字節碼編譯成本地機器碼來提升運行速度,和Java中的概念類似。
- Maps:鉤子代碼可以將一些統計類信息保存在鍵值對的map中,來與用戶空間程序進行通信,傳遞數據。
- 關于eBPF機制詳細的講解網上有很多,這里就不展開了,這里先上一張圖,這里包括了使用或者編寫ebpf涉及到的所有東西,下面會對這個圖進行詳細的講解。
oo_kern.c 鉤子實現代碼,主要負責:
- 聲明使用的Map節點
- 聲明鉤子掛載點及處理函數
通過LLVM/clang編譯成字節碼
- 編譯命令:clang --target=bpf
- android平臺有集成eBPF的編譯,后文會提到
foo_user.c 用戶空間處理函數,主要負責:
- 將foo_kern.c 編譯成的字節碼加載到kenel中
- 讀取Map中的信息并處理輸出給用戶
kernel當收到eBPF的加載請求時,會先對字節碼進行驗證,并通過JIT編譯為機器碼,當鉤子事件來臨后,調用鉤子函數,kernel會對加載的字節碼進行驗證,來保證系統的安全性,主要驗證規則如下:
a. 檢查是否聲明了GNU GPL,檢查kernel的版本是否支持
b. 函數調用規則:
允許bpf函數之間的相互調用,只允許調用kernel允許的BPF helper函數,具體可以參考linux/bpf.h文件,上述以外的函數及動態鏈接都是不允許的。
c. 流程處理規則:
不允許使用loop循環以防止進入死循環卡死kernel
不允許有不可到達的分支代碼
d. 堆棧大小被限制在MAX_BPF_STACK范圍內。
e. 編譯的字節碼大小被限制在BPF_COMPLEXITY_LIMIT_INSNS范圍內。
鉤子掛載點,主要包括:
另外在kernel的源代碼中samples/bpf目錄下有大量的示例,感興趣的可以閱讀下。
三、eBPF動態追蹤內存碎片實戰
理論鋪墊之后,現在就來進入實戰環節,看看如何使用 eBPF 來動態追蹤內存碎片。這就像是一位醫生拿著先進的醫療設備,深入到系統的 “身體” 內部,精確地找出內存碎片這個 “病癥” 的根源。
3.1 準備工作
在開始使用 eBPF 追蹤內存碎片之前,首先要確保你的系統滿足一些基本的條件。
需要有一個支持 eBPF 的 Linux 內核版本,一般來說,4.1 及以上版本的內核都已經支持 eBPF,但為了獲得更好的性能和更多的功能,建議使用 4.9 及以上版本的內核 。你可以通過以下命令查看當前系統的內核版本:
uname -r
還需要安裝 BPF 編譯器集合(BCC,BPF Compiler Collection)工具集。BCC 是一個基于 Python 和 C 的開發工具,它提供了一系列的庫和工具,使得編寫和運行 eBPF 程序變得更加簡單和高效。在大多數 Linux 發行版中,可以通過包管理器來安裝 BCC。例如,在 Ubuntu 系統上,可以使用以下命令進行安裝:
sudo apt-get install bcc-tools libbcc-examples linux-headers-$(uname -r)
在 CentOS 系統上,可以使用以下命令:
sudo yum install bcc-tools bpftool python3-bcc
3.2 編寫eBPF程序
準備工作完成后,接下來就可以開始編寫 eBPF 程序了。我們的目標是編寫一個能夠追蹤內存分配和釋放函數的 eBPF 程序,通過這個程序,我們可以獲取內存分配和釋放的相關信息,進而分析內存碎片的情況。
以下是一個簡單的用 C 語言編寫的 eBPF 程序示例,它可以追蹤kmalloc(內核空間內存分配函數)和kfree(內核空間內存釋放函數):
#include <uapi/linux/ptrace.h>
#include <linux/sched.h>
// 定義一個用于存儲內存分配信息的結構體
struct alloc_info {
u32 pid; // 進程ID
u64 size; // 分配的內存大小
u64 timestamp; // 分配時間
char comm[TASK_COMM_LEN]; // 進程名稱
};
// 定義一個用于存儲內存釋放信息的結構體
struct free_info {
u32 pid;
u64 timestamp;
char comm[TASK_COMM_LEN];
};
// 創建一個BPF哈希表,用于存儲內存分配信息
BPF_HASH(alloc_map, u64, struct alloc_info);
// 創建一個BPF環形緩沖區,用于輸出內存釋放信息
BPF_PERF_OUTPUT(free_events);
// 定義一個kprobe,在kmalloc函數被調用時觸發
int kprobe__kmalloc(struct pt_regs *ctx, size_t size) {
u64 ip = bpf_get_current_retval(ctx); // 獲取函數返回值,即分配的內存地址
if (!ip) return 0;
struct alloc_info info = {};
info.pid = bpf_get_current_pid_tgid() >> 32;
info.size = size;
info.timestamp = bpf_ktime_get_ns();
bpf_get_current_comm(&info.comm, sizeof(info.comm));
alloc_map.update(&ip, &info); // 將分配信息存儲到哈希表中
return 0;
}
// 定義一個kprobe,在kfree函數被調用時觸發
int kprobe__kfree(struct pt_regs *ctx) {
u64 ip = PT_REGS_PARM1(ctx); // 獲取kfree函數的第一個參數,即要釋放的內存地址
if (!ip) return 0;
struct alloc_info *info = alloc_map.lookup(&ip);
if (!info) return 0;
struct free_info free_info = {};
free_info.pid = info->pid;
free_info.timestamp = bpf_ktime_get_ns();
__builtin_memcpy(&free_info.comm, &info->comm, sizeof(free_info.comm));
free_events.perf_submit(ctx, &free_info, sizeof(free_info)); // 將釋放信息輸出到環形緩沖區
alloc_map.delete(&ip); // 從哈希表中刪除已釋放的內存分配信息
return 0;
}
在這段代碼中,我們定義了兩個結構體alloc_info和free_info,分別用于存儲內存分配和釋放的相關信息。然后,我們創建了一個 BPF 哈希表alloc_map,用于存儲內存分配信息,以及一個 BPF 環形緩沖區free_events,用于輸出內存釋放信息。
通過kprobe__kmalloc和kprobe__kfree這兩個函數,我們分別在kmalloc和kfree函數被調用時,捕獲相關信息,并進行相應的處理。在kmalloc函數中,我們獲取分配的內存地址、大小、進程 ID、進程名稱和分配時間,并將這些信息存儲到哈希表中;在kfree函數中,我們獲取要釋放的內存地址,從哈希表中查找對應的分配信息,然后將釋放信息輸出到環形緩沖區,并從哈希表中刪除該分配信息。
3.3 加載與運行
編寫好 eBPF 程序后,接下來就需要將其加載到內核中,并運行起來。這一步可以通過 BCC 工具集中的 Python 庫來實現。以下是一個簡單的 Python 腳本,用于加載和運行上述 eBPF 程序:
from bcc import BPF
import time
# 加載eBPF程序
b = BPF(src_file="memory_trace.c")
# 定義一個回調函數,用于處理從環形緩沖區中讀取的內存釋放信息
def print_free_event(cpu, data, size):
event = b["free_events"].event(data)
print(f"PID: {event.pid}, COMM: {event.comm.decode('utf-8')}, FREE_TIME: {event.timestamp / 1e9:.6f} s")
# 打開環形緩沖區,并注冊回調函數
b["free_events"].open_perf_buffer(print_free_event)
print("Tracing memory allocations and frees... Ctrl+C to end.")
try:
while True:
b.perf_buffer_poll()
time.sleep(1)
except KeyboardInterrupt:
pass
在這個腳本中,我們首先使用BPF類加載了 eBPF 程序文件memory_trace.c。然后,我們定義了一個回調函數print_free_event,用于處理從環形緩沖區中讀取的內存釋放信息。這個函數會打印出進程 ID、進程名稱和釋放時間。
接著,我們使用open_perf_buffer方法打開環形緩沖區,并將回調函數注冊到緩沖區上。這樣,當有新的內存釋放信息寫入環形緩沖區時,回調函數就會被自動調用。
最后,我們通過一個無限循環,不斷調用perf_buffer_poll方法來輪詢環形緩沖區,檢查是否有新的事件到來。當用戶按下Ctrl+C時,程序會捕獲到KeyboardInterrupt異常,從而退出循環,結束程序的運行。
運行這個 Python 腳本后,eBPF 程序就會開始追蹤內存分配和釋放的操作。每當有內存被釋放時,回調函數就會被觸發,打印出相應的信息。通過分析這些信息,我們就可以了解內存的使用情況,進而找出內存碎片產生的原因和規律。
四、eBPF在Android平臺的使用
經過上面枯燥的講解,大家應該對eBPF有了基礎的認識,下面我們就來通過android平臺上的一個監控性能的小例子來實操下。這個小例子的需求是統計系統中每個應用在一段時間內系統調用的次數。
4.1 android系統對eBPF的編譯支持
目前android編譯系統已經對eBPF進行了集成,通過android.bp就能很方便的在android源代碼中編譯eBPF的字節碼。
android.bp示例:
圖片
相關的編譯代碼在soong的bpf.go,雖然google關于soong的文檔很少,但是至少代碼是比較清晰的。
圖片
這里的$ccCmd一般是clang, 所以它的編譯命令主要是clang --target=bpf。和普通的bpf編譯沒有區別。
4.2 eBPF鉤子代碼實現
解決了編譯問題,下一步我們開始實現鉤子代碼,我們準備使用tracepoint鉤子,首先要找到我們需要的tracepoint函數sys_enter和sys_exit。
函數定義在include/trace/events/syscalls.h文件sys_enter的trace參數是id 和長度為6的數組,sys_exit的trace參數是兩個長整形數 id 和ret。
找到了鉤子后,下一步就可以編寫鉤子處理代碼了:
定義map保存系統調用統計信息,在DEFINE_BPF_MAP聲明map的同時,也會生成刪,改,查的宏函數,例如本例中會生成如下函數:
bpf_pid_syscall_map_lookup_elem
bpf_pid_syscall_map_update_elem
bpf_pid_syscall_map_delete_elem
- 定義回調函數參數類型,需要參考前面的tracepoint的定義。
- 指定監聽的tracepoint事件。
- 使用bpf_trace_printk函數打印debug信息,會直接打印信息到ftrace中。
- 在map中查找指定key。
- 更新指定的key的值。
4.3 加載鉤子代碼
我們只需要把我們編譯出來的*.o文件push到手機的system/etc/bpf目錄下,重啟手機,系統會自動加載我們的鉤子文件,加載成功后會在 /sys/fs/bpf目錄下顯示我們定義的map及prog文件。
系統加載代碼在system/bpf/bpfloader中,代碼很簡單。
主要有如下操作:
1)在early-init階段向下面兩個節點寫1
– /proc/sys/net/core/bpf_jit_enable
使能eBPF JIT,當內核設定BPF_JIT_ALWAYS_ON的時候,默認為1
– /proc/sys/net/core/bpf_jit_kallsyms
使特權用戶可以通過kallsyms節點讀取kernel的symbols
2)啟動bpfloader service
- 讀取system/etc/bpf目錄下的*.o文件,調用libbpf_android.so中的loadProg函數加載進內核。
- 生成相應的/sys/fs/bpf/節點。
- 設置屬性bpf.progs_loaded為1
sys節點分為map節點和prog節點兩種, 分別為map_<filename>_<mapname>, prog_<filename>_<mapname>
下面是Android Q版本上的節點信息。
可以使用下面的命令調試動態加載
4.4 用戶空間程序實現
下面我們需要編寫用戶空間的顯示程序,本質上就是在用戶態通過系統調用把BPF map給讀出來
1)eBPF統計只有在調用bpf_attach_tracepoint只有才會起作用。bpf_attach_tracepoint是bcc里面的函數,android將bcc的一部分內容打包成了libbpf,放到了系統庫里面。
2)取得map的fd, bpf_obj_get會直接調用bpf的系統調用。
3)將fd包裝成BpfMap,android在BpfMap.h中定義了很多方便的函數。
4)遍歷map回調函數。返回值必須是android::netdutils::status::ok(在android的新版本中已經進行修改)。
4.5 運行結果查看
直接在目錄下執行mm,將編譯出來的bpf.o push到/system/etc/bpf目錄下,將統計程序push到/system/bin目錄下,重啟,看下結果。
前面的是pid, 后面的是系統調用次數。至此,如何在android平臺使用eBPF實現統計系統中每個pid在一段時間內系統調用的次數的功能就介紹完了。
此外還有很多技術細節沒有深入研究,不過畢竟只是初探,就先講到這里了,后續有時間再進一步深入研究。研究的時間還是比較短,如果有任何錯誤的地方歡迎指正。
五、內核調優:與eBPF協同作戰
5.1 內存相關內核參數調整
在解決內存碎片問題的過程中,eBPF 就像是我們手中的精密探測器,能夠深入系統底層獲取關鍵數據,而內核參數調整則是對系統內存管理機制的宏觀調控,兩者相互配合,才能達到最佳的優化效果。下面來介紹一些與內存管理密切相關的內核參數,以及它們如何與 eBPF 協同工作。
swappiness是一個重要的內核參數,它的值介于 0 - 100 之間,用于控制系統將內存數據交換到磁盤交換空間(swap)的傾向程度 。默認情況下,許多 Linux 發行版的swappiness值設置為 60。當swappiness的值較高時,系統會更積極地將內存中不常使用的數據移動到交換空間,以釋放物理內存供其他進程使用;相反,當swappiness的值較低時,系統會盡量保留數據在物理內存中,減少磁盤 I/O 操作。
例如,對于一些對磁盤 I/O 性能敏感的應用場景,如數據庫服務器,將swappiness值降低到 10 或 20,可以有效減少因頻繁交換數據而導致的性能下降。通過 eBPF 獲取的內存使用和交換空間使用情況數據,可以幫助我們更準確地判斷當前swappiness值是否合適,從而進行相應的調整。
overcommit_memory則是另一個關鍵的內核參數,它決定了內核對于內存分配請求的處理策略,有三個可選值:0、1 和 2 。當overcommit_memory設置為 0 時,內核會采用一種啟發式的策略來判斷內存分配請求是否合理。它會檢查系統當前的可用內存、交換空間以及內存使用趨勢等因素,如果內核認為分配請求可能導致系統內存不足,就會拒絕該請求。這種策略相對保守,適用于對內存穩定性要求較高的系統。當設置為 1 時,內核會允許分配所有的物理內存,而不管當前的內存狀態如何,即對內存申請來者不拒。
這種策略在某些情況下可以避免因內存分配失敗而導致的程序異常,但也增加了系統發生內存耗盡(OOM,Out - Of - Memory)的風險。若設置為 2,內核會嚴格限制內存分配,不允許分配超過所有物理內存和交換空間總和的內存,即禁止 overcommit。通過 eBPF 對內存分配和使用情況的實時追蹤,我們可以了解系統中各個進程的內存需求模式,從而根據實際情況選擇合適的overcommit_memory值,在保證系統穩定性的同時,充分利用內存資源。
5.2 eBPF 與內核調優的配合策略
eBPF 與內核調優之間的配合是一個動態的、相互反饋的過程。通過 eBPF 程序對內存分配、釋放以及內存碎片情況的實時追蹤,我們可以獲取大量的詳細數據,這些數據就像是系統內存狀態的 “實時監控畫面”,為內核調優提供了堅實的數據基礎。
當 eBPF 監測到系統中內存碎片的比例逐漸增加,導致內存分配效率下降時,我們可以根據這些數據,針對性地調整相關內核參數。如果發現由于頻繁的內存交換導致內存碎片問題加劇,就可以適當降低swappiness的值,減少內存與交換空間之間的數據交換,從而減少碎片的產生。相反,如果 eBPF 數據顯示系統內存利用率較低,而有一些進程因為內存分配限制而無法正常運行,這時可以考慮適當調整overcommit_memory的值,放寬內存分配策略,提高內存的利用率。
還可以結合 eBPF 獲取的進程內存使用信息,對不同的進程進行差異化的內存管理。對于一些內存使用量大且穩定的進程,可以通過內核參數為其分配更多的固定內存塊,避免頻繁的內存分配和釋放操作,從而減少碎片的產生;對于內存使用量較小且波動頻繁的進程,可以采用更靈活的內存分配算法,提高內存的使用效率。
在實際應用中,eBPF 與內核調優的配合需要不斷地進行測試和優化。通過在不同的負載條件下運行 eBPF 程序,收集系統性能指標數據,然后根據這些數據調整內核參數,再進行新一輪的測試,直到系統性能達到最佳狀態。這種循環優化的過程就像是一場精密的調試,需要耐心和細心,才能找到系統內存管理的最優配置。