libbpf 概述¶
libbpf 是一個基於 C 語言的庫,包含一個 BPF 載入器,用於接收編譯後的 BPF 目標檔案並準備將其載入到 Linux 核心中。libbpf 承擔了載入、驗證和將 BPF 程式附加到各種核心鉤子的繁重工作,使 BPF 應用程式開發人員能夠專注於 BPF 程式的正確性和效能。
以下是 libbpf 支援的高階功能
為使用者空間程式提供高階和低階 API,以便與 BPF 程式互動。低階 API 封裝了所有的 bpf 系統呼叫功能,這對於使用者需要對使用者空間和 BPF 程式之間的互動進行更細粒度控制時非常有用。
為 bpftool 生成的 BPF 物件骨架提供全面支援。骨架檔案簡化了使用者空間程式訪問全域性變數和使用 BPF 程式的過程。
提供 BPF 端 API,包括 BPF 助手定義、BPF 對映支援和追蹤助手,使開發人員能夠簡化 BPF 程式碼編寫。
支援 BPF CO-RE 機制,使 BPF 開發人員能夠編寫可移植的 BPF 程式,這些程式可以編譯一次並在不同的核心版本上執行。
本文件將詳細探討上述概念,提供對 libbpf 功能和優勢的更深入理解,以及它如何幫助您高效開發 BPF 應用程式。
BPF 應用程式生命週期和 libbpf API¶
一個 BPF 應用程式包含一個或多個 BPF 程式(協作或完全獨立)、BPF 對映和全域性變數。全域性變數在所有 BPF 程式之間共享,這使它們能夠在一組共同的資料上進行協作。libbpf 提供了使用者空間程式可以用來透過觸發 BPF 應用程式生命週期的不同階段來操作 BPF 程式的 API。
以下部分簡要概述了 BPF 生命週期中的每個階段
開啟階段:在此階段,libbpf 解析 BPF 目標檔案並發現 BPF 對映、BPF 程式和全域性變數。開啟 BPF 應用程式後,使用者空間應用程式可以在所有實體建立和載入之前進行額外調整(如有必要,設定 BPF 程式型別;預設全域性變數的初始值等)。
載入階段:在載入階段,libbpf 建立 BPF 對映,解析各種重定位,並驗證和載入 BPF 程式到核心中。此時,libbpf 驗證 BPF 應用程式的所有部分並將 BPF 程式載入到核心中,但尚未執行任何 BPF 程式。載入階段之後,可以在不與 BPF 程式程式碼執行競爭的情況下設定初始 BPF 對映狀態。
附加階段:在此階段,libbpf 將 BPF 程式附加到各種 BPF 鉤子點(例如,tracepoints、kprobes、cgroup 鉤子、網路資料包處理管道等)。在此階段,BPF 程式執行有用的工作,例如處理資料包,或更新可以從使用者空間讀取的 BPF 對映和全域性變數。
拆卸階段:在拆卸階段,libbpf 分離 BPF 程式並將其從核心中解除安裝。BPF 對映被銷燬,並且 BPF 應用程式使用的所有資源都被釋放。
BPF 物件骨架檔案¶
BPF 骨架是 libbpf API 的另一種介面,用於處理 BPF 物件。骨架程式碼抽象了通用的 libbpf API,以顯著簡化從使用者空間操作 BPF 程式的程式碼。骨架程式碼包含 BPF 物件檔案的位元組碼錶示,簡化了分發 BPF 程式碼的過程。由於嵌入了 BPF 位元組碼,您的應用程式二進位制檔案無需部署額外檔案。
透過將 BPF 物件傳遞給 bpftool,您可以為特定物件檔案生成骨架標頭檔案 (.skel.h)。生成的 BPF 骨架提供了以下與 BPF 生命週期對應的自定義函式,每個函式都以特定的物件名稱作為字首
<name>__open()– 建立並開啟 BPF 應用程式(<name>代表特定的 bpf 物件名稱)<name>__load()– 例項化、載入和驗證 BPF 應用程式元件<name>__attach()– 附加所有可自動附加的 BPF 程式(可選,您可以透過直接使用 libbpf API 獲得更多控制)<name>__destroy()– 分離所有 BPF 程式並釋放所有已用資源
使用骨架程式碼是處理 bpf 程式的推薦方式。請記住,BPF 骨架提供了對底層 BPF 物件的訪問,因此即使使用 BPF 骨架,通用 libbpf API 所能做的一切仍然可以實現。它是一個附加的便捷功能,沒有系統呼叫,也沒有繁瑣的程式碼。
使用骨架檔案的其他優勢¶
BPF 骨架為使用者空間程式提供了使用 BPF 全域性變數的介面。骨架程式碼將全域性變數作為結構體記憶體對映到使用者空間。結構體介面允許使用者空間程式在 BPF 載入階段之前初始化 BPF 程式,並在之後從使用者空間獲取和更新資料。
skel.h檔案透過列出可用的對映、程式等來反映物件檔案結構。BPF 骨架提供了對所有 BPF 對映和 BPF 程式的直接訪問,作為結構體欄位。這消除了使用bpf_object_find_map_by_name()和bpf_object_find_program_by_name()API 進行基於字串查詢的需求,減少了由於 BPF 原始碼和使用者空間程式碼不同步而導致的錯誤。物件檔案中嵌入的位元組碼錶示確保了骨架和 BPF 物件檔案始終保持同步。
BPF 助手¶
libbpf 提供了 BPF 端 API,BPF 程式可以使用這些 API 與系統互動。BPF 助手定義允許開發人員在 BPF 程式碼中像使用其他普通 C 函式一樣使用它們。例如,有用於列印除錯訊息、獲取系統啟動以來的時間、與 BPF 對映互動、操作網路資料包等的助手函式。
有關助手功能、所接受的引數和返回值的完整描述,請參閱 bpf-helpers 手冊頁。
BPF CO-RE (Compile Once – Run Everywhere)¶
BPF 程式在核心空間中執行,可以訪問核心記憶體和資料結構。BPF 應用程式遇到的一個限制是缺乏跨不同核心版本和配置的可移植性。BCC 是 BPF 可移植性的解決方案之一。然而,它帶來了執行時開銷,並且由於將編譯器嵌入到應用程式中而導致二進位制檔案大小龐大。
libbpf 透過支援 BPF CO-RE 概念來提高 BPF 程式的可移植性。BPF CO-RE 將 BTF 型別資訊、libbpf 和編譯器結合在一起,生成一個可以在多個核心版本和配置上執行的單一可執行二進位制檔案。
為了使 BPF 程式可移植,libbpf 依賴於執行中核心的 BTF 型別資訊。核心還透過 sysfs 在 /sys/kernel/btf/vmlinux 暴露這種自描述的權威 BTF 資訊。
您可以使用以下命令為執行中的核心生成 BTF 資訊
$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
該命令生成一個 vmlinux.h 標頭檔案,其中包含執行中的核心使用的所有核心型別(BTF 型別)。在 BPF 程式中包含 vmlinux.h 消除了對系統範圍內核頭的依賴。
libbpf 透過檢視 BPF 程式的記錄的 BTF 型別和重定位資訊,並將其與執行中核心提供的 BTF 資訊 (vmlinux) 進行匹配,從而實現 BPF 程式的可移植性。libbpf 然後解析並匹配所有型別和欄位,並更新必要的偏移量和其他可重定位資料,以確保 BPF 程式的邏輯對於主機上的特定核心能夠正確執行。BPF CO-RE 概念因此消除了與 BPF 開發相關的開銷,並允許開發人員在目標機器上無需修改和執行時原始碼編譯即可編寫可移植的 BPF 應用程式。
以下程式碼片段展示瞭如何使用 BPF CO-RE 和 libbf 讀取核心 task_struct 的父欄位。以 CO-RE 可重定位方式讀取欄位的基本助手是 bpf_core_read(dst, sz, src),它將從 src 引用的欄位中讀取 sz 位元組到 dst 指向的記憶體中。
//...
struct task_struct *task = (void *)bpf_get_current_task();
struct task_struct *parent_task;
int err;
err = bpf_core_read(&parent_task, sizeof(void *), &task->parent);
if (err) {
/* handle error */
}
/* parent_task contains the value of task->parent pointer */
在程式碼片段中,我們首先使用 bpf_get_current_task() 獲取指向當前 task_struct 的指標。然後我們使用 bpf_core_read() 將任務結構體的 parent 欄位讀入 parent_task 變數。bpf_core_read() 就像 bpf_probe_read_kernel() BPF 助手一樣,不同之處在於它記錄了關於應在目標核心上重定位的欄位的資訊。即,如果 parent 欄位由於在其前面添加了一些新欄位而在 struct task_struct 內移動到不同的偏移量,libbpf 將自動將實際偏移量調整為正確的值。
libbpf 入門¶
檢視 libbpf-bootstrap 倉庫,其中包含使用 libbpf 構建各種 BPF 應用程式的簡單示例。
另請參閱 libbpf API 文件。
libbpf 和 Rust¶
如果您正在使用 Rust 構建 BPF 應用程式,建議使用 Libbpf-rs 庫,而不是直接繫結 libbpf。Libbpf-rs 將 libbpf 功能封裝在 Rust 風格的介面中,並提供 libbpf-cargo 外掛來處理 BPF 程式碼編譯和骨架生成。使用 Libbpf-rs 將使構建 BPF 應用程式的使用者空間部分變得更容易。請注意,BPF 程式本身仍必須用純 C 編寫。
libbpf 日誌記錄¶
預設情況下,libbpf 將資訊和警告訊息記錄到 stderr。可以透過將環境變數 LIBBPF_LOG_LEVEL 設定為 warn、info 或 debug 來控制這些訊息的詳細程度。可以使用 libbpf_set_print() 設定自定義日誌回撥。