一文看懂eBPF、eBPF的使用(超詳細(xì))
eBPF(extended Berkeley Packet Filter) 可謂 Linux 社區(qū)的新寵,很多大公司都開始投身于 eBPF 技術(shù),如 Goole、Facebook、Twitter 等。
eBPF 究竟有什么魅力讓大家都關(guān)注它呢?
這是因為 eBPF 增加了內(nèi)核的可擴(kuò)展性,讓內(nèi)核變得更加靈活和強(qiáng)大。
如果大家玩過 樂高積木 的話就會深有體會,樂高積木就是通過不斷向主體添加積木來組合出更龐大的模型。
而 eBPF 就像樂高積木一樣,可以不斷向內(nèi)核添加 eBPF 模塊來增強(qiáng)內(nèi)核的功能。
什么是 eBPF
eBPF 全稱 extended Berkeley Packet Filter,中文意思是 擴(kuò)展的伯克利包過濾器。一般來說,要向內(nèi)核添加新功能,需要修改內(nèi)核源代碼或者編寫 內(nèi)核模塊 來實現(xiàn)。而 eBPF 允許程序在不修改內(nèi)核源代碼,或添加額外的內(nèi)核模塊情況下運(yùn)行。
從 eBPF 的名字看,好像是專門為過濾網(wǎng)絡(luò)包而創(chuàng)造的。其實,eBPF 是從 BPF(也稱為 cBPF:classic Berkeley Packet Filter)發(fā)展而來的,BPF 是專門為過濾網(wǎng)絡(luò)數(shù)據(jù)包而創(chuàng)造的。
但隨著 eBPF 不斷完善和加強(qiáng),現(xiàn)在的 eBPF 已經(jīng)不再限于過濾網(wǎng)絡(luò)數(shù)據(jù)包了。
eBPF 架構(gòu)
我們先來看看 eBPF 的架構(gòu),如下圖所示:

下面用文字來描述一下:
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【891587639】整理了一些個人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。?!前100名進(jìn)群領(lǐng)取,額外贈送一份價值699的內(nèi)核資料包(含視頻教程、電子書、實戰(zhàn)項目及代碼)?


用戶編寫 eBPF 程序,可以使用 eBPF 匯編或者 eBPF 特有的 C 語言來編寫。
使用 LLVM/CLang 編譯器,將 eBPF 程序編譯成 eBPF 字節(jié)碼。
調(diào)用 bpf() 系統(tǒng)調(diào)用把 eBPF 字節(jié)碼加載到內(nèi)核。
內(nèi)核態(tài)
當(dāng)用戶調(diào)用 bpf() 系統(tǒng)調(diào)用把 eBPF 字節(jié)碼加載到內(nèi)核時,內(nèi)核先會對 eBPF 字節(jié)碼進(jìn)行安全驗證。
使用 JIT(Just In Time)技術(shù)將 eBPF 字節(jié)編譯成本地機(jī)器碼(Native Code)。
然后根據(jù) eBPF 程序的功能,將 eBPF 機(jī)器碼掛載到內(nèi)核的不同運(yùn)行路徑上(如用于跟蹤內(nèi)核運(yùn)行狀態(tài)的 eBPF 程序?qū)燧d在 kprobes 的運(yùn)行路徑上)。當(dāng)內(nèi)核運(yùn)行到這些路徑時,就會觸發(fā)執(zhí)行相應(yīng)路徑上的 eBPF 機(jī)器碼。
如果大家使用過 Java 編寫程序的話,會發(fā)現(xiàn) eBPF 與 Java 的AOP(Aspect Oriented Programming 面向切面編程)概念很像。
為了讓有 Java 經(jīng)驗的同學(xué)更容易接受 eBPF 技術(shù)。我們先介紹一下 Java 中的 AOP 概念。
在 AOP 概念中,有兩個很重要的角色:切點(diǎn) 和 攔截器。
切點(diǎn):程序中某個具體的業(yè)務(wù)點(diǎn)(方法)。
攔截器:攔截器其實是一段 Java 代碼,用于攔截切點(diǎn)在執(zhí)行前(或執(zhí)行后),先運(yùn)行這段 Java 代碼。
eBPF 程序就像 AOP 中的攔截器,而內(nèi)核的某個運(yùn)行路徑就像 AOP 中的切點(diǎn)。
根據(jù)掛載點(diǎn)功能的不同,大概可以分為以下幾個模塊:
性能跟蹤
網(wǎng)絡(luò)
容器
安全
eBPF 使用
在介紹 eBPF 的實現(xiàn)前,我們先來介紹一下如何使用 eBPF 來跟蹤 fork() 系統(tǒng)調(diào)用的運(yùn)行情況。
編寫 eBPF 程序有多種方式,比如使用原生 eBPF 匯編來編寫,但使用原生 eBPF 匯編編寫程序的難度較大,所以一般不建議。
也可以使用 eBPF 受限的 C 語言來編寫,難度比使用原生 eBPF 匯編簡單些,但對初學(xué)者來說也不是十分友好。
最簡單是使用 BCC 工具來編寫,BCC 工具幫我們簡化了很多繁瑣的工作,比如不用編寫加載器。
下面我們將使用 BCC 工具來介紹怎么編寫一個 eBPF 程序。
注意:由于 eBPF 對內(nèi)核的版本有較高的要求,不同版本的內(nèi)核對 eBPF 的支持可能有所不相同。所以使用 eBPF 時,最好使用最新版本的內(nèi)核。 本文使用 Ubuntu 20.20(內(nèi)核版本為5.8.1)作為解說。
1. BCC 工具安裝
在 Ubuntu 系統(tǒng)中安裝 BCC 工具是比較簡單的,可以使用以下命令:
$ sudo apt-get install bpfcc-tools linux-headers-$(uname -r)
BCC 工具可以讓你使用 Python 和 C 語言組合來編寫 eBPF 程序。
安裝完成后,可以使用命令 bcc -v 來測試是否安裝成功。如果安裝失敗,可以參考官網(wǎng)安裝文檔,如下:
https://github.com/iovisor/bcc/blob/master/INSTALL.md
2. 編寫 eBPF 版的 hello world
一般編程課的第一步都是編寫著名的 hello world 程序,所以我們也以編寫 hello world 程序作為第一步吧。
使用 BCC 編寫 eBPF 程序的步驟如下:
使用 C 語言編寫 eBPF 程序的內(nèi)核態(tài)功能(也就是運(yùn)行在內(nèi)核態(tài)的 eBPF 程序)。
使用 Python 編寫加載代碼和用戶態(tài)功能。
為什么不能全部使用 Python 編寫呢?這是因為 LLVM/Clang 只支持將 C 語言編譯成 eBPF 字節(jié)碼,而不支持將 Python 代碼編譯成 eBPF 字節(jié)碼。
所以,eBPF 內(nèi)核態(tài)程序只能使用 C 語言編寫。而 eBPF 的用戶態(tài)程序可以使用 Python 進(jìn)行編寫,這樣就能簡化編寫難度。
所以,第一步就是編寫 eBPF 內(nèi)核態(tài)程序。
使用 C 編寫 eBPF 程序
新建一個 hello.c 文件,并輸入下面的內(nèi)容:
使用 Python 和 BCC 工具開發(fā)一個用戶態(tài)程序
新建一個 hello.py 文件,并輸入下面的內(nèi)容:
下面我們來看看每一行代碼的具體含義:
導(dǎo)入了 BCC 庫的 BPF 模塊,以便接下來調(diào)用。
調(diào)用 BPF() 函數(shù)加載 eBPF 內(nèi)核態(tài)程序(也就是我們編寫的hello.c)。
將 eBPF 程序掛載到內(nèi)核探針(簡稱 kprobe),其中 do_sys_openat2() 是系統(tǒng)調(diào)用 openat() 在內(nèi)核中的實現(xiàn)。
讀取內(nèi)核調(diào)試文件 /sys/kernel/debug/tracing/trace_pipe 的內(nèi)容(bpf_trace_printk() 函數(shù)會將信息寫入到此文件),并打印到標(biāo)準(zhǔn)輸出中。
運(yùn)行 eBPF 程序
用戶態(tài)程序開發(fā)完成之后,最后一步就是執(zhí)行它了。需要注意的是,eBPF 程序需要以 root 用戶來運(yùn)行:
運(yùn)行后,可以看到如下輸出:
到了這里,我們已經(jīng)成功開發(fā)并運(yùn)行了第一個 eBPF 程序。當(dāng)然,這個程序很簡單,并且也沒有實際的用途。
但通過這個程序,我們大概可以知道使用 BCC 開發(fā)一個 eBPF 程序的步驟。
因為本系列文章并不是介紹如何開發(fā) eBPF 程序,而是介紹 eBPF 的原理和實現(xiàn)。如果大家有興趣學(xué)習(xí)如何開發(fā) eBPF 程序,那么建議大家看看《BPF性能之巔》這本書,這本書詳細(xì)地介紹了如何開發(fā) eBPF 程序。
