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

下面用文字來描述一下:
用戶態(tài)
用戶編寫 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é)編譯成本地機器碼(Native Code)。然后根據(jù) eBPF 程序的功能,將 eBPF 機器碼掛載到內(nèi)核的不同運行路徑上(如用于跟蹤內(nèi)核運行狀態(tài)的 eBPF 程序?qū)燧d在? kprobes?的運行路徑上)。當(dāng)內(nèi)核運行到這些路徑時,就會觸發(fā)執(zhí)行相應(yīng)路徑上的 eBPF 機器碼。
如果大家使用過 Java 編寫程序的話,會發(fā)現(xiàn) eBPF 與 Java 的AOP(Aspect Oriented Programming 面向切面編程)概念很像。
為了讓有 Java 經(jīng)驗的同學(xué)更容易接受 eBPF 技術(shù)。我們先介紹一下 Java 中的 AOP 概念。
在 AOP 概念中,有兩個很重要的角色:切點?和?攔截器。
切點:程序中某個具體的業(yè)務(wù)點(方法)。攔截器:攔截器其實是一段 Java 代碼,用于攔截切點在執(zhí)行前(或執(zhí)行后),先運行這段 Java 代碼。
eBPF 程序就像 AOP 中的攔截器,而內(nèi)核的某個運行路徑就像 AOP 中的切點。
根據(jù)掛載點功能的不同,大概可以分為以下幾個模塊:
性能跟蹤 網(wǎng)絡(luò) 容器 安全
eBPF 使用
在介紹 eBPF 的實現(xiàn)前,我們先來介紹一下如何使用 eBPF 來跟蹤?fork()?系統(tǒng)調(diào)用的運行情況。
編寫 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)功能(也就是運行在內(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)容:
int?hello_world(void?*ctx)
{
????bpf_trace_printk("Hello,?World!");
????return?0;
}
使用 Python 和 BCC 工具開發(fā)一個用戶態(tài)程序
新建一個 hello.py 文件,并輸入下面的內(nèi)容:
#!/usr/bin/env?python3
#?1)?加載?BCC?庫
from?bcc?import?BPF
#?2)?加載?eBPF?內(nèi)核態(tài)程序
b?=?BPF(src_file="hello.c")
#?3)?將?eBPF?程序掛載到?kprobe
b.attach_kprobe(event="do_sys_openat2",?fn_name="hello_world")
#?4)?讀取并且打印?eBPF?內(nèi)核態(tài)程序輸出的數(shù)據(jù)
b.trace_print()
下面我們來看看每一行代碼的具體含義:
導(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)輸出中。
運行 eBPF 程序
用戶態(tài)程序開發(fā)完成之后,最后一步就是執(zhí)行它了。需要注意的是,eBPF 程序需要以?root?用戶來運行:
$?sudo?python3?hello.py
運行后,可以看到如下輸出:
$?sudo?python3?hello.py
b'?????????python3-31683???[001]?....?614653.225903:?0:?Hello,?World!'
b'?????????python3-31683???[001]?....?614653.226093:?0:?Hello,?World!'
b'?????????python3-31683???[001]?....?614653.226606:?0:?Hello,?World!'
b'???????????<...>-31684???[000]?....?614654.387288:?0:?Hello,?World!'
b'??????irqbalance-669?????[000]?....?614658.232433:?0:?Hello,?World!'
...
到了這里,我們已經(jīng)成功開發(fā)并運行了第一個 eBPF 程序。當(dāng)然,這個程序很簡單,并且也沒有實際的用途。
但通過這個程序,我們大概可以知道使用 BCC 開發(fā)一個 eBPF 程序的步驟。
因為本系列文章并不是介紹如何開發(fā) eBPF 程序,而是介紹 eBPF 的原理和實現(xiàn)。如果大家有興趣學(xué)習(xí)如何開發(fā) eBPF 程序,那么建議大家看看《BPF性能之巔》這本書,這本書詳細(xì)地介紹了如何開發(fā) eBPF 程序。
在下篇文章中,我們將介紹 eBPF 的實現(xiàn)原理,敬請期待。
