Linux在日常的使用中已經無處不在了,從人們的手機,到電視機,再到路由器,都運行在linux內核之上,而eBPF就是Linux內核的一把瑞士軍刀,通過eBPF可以在linux內核中運行程序,運維人員可以使用它對linux內核進行監控,開發者可以使用它對linux內核功能進行定制修改,做到很多以前無法實現的功能。
官方的說法: eBPF is a revolutionary technology with origins in the Linux kernel that can run sandboxed programs in an operating system kernel.
個人認為,簡單的說,eBPF可以理解為一個框架,通過這個框架,我們可以在內核中執行自己編寫的程序代碼。
eBPF基本概念
eBPF的前身是BPF(extended Berkeley Packet Filter),BPF是內核中用來做高效過濾網絡報文的,tcpdump里面就是用的BPF技術過濾報文。2014年內核3.18中eBPF第一次出現,此時的eBPF已經成為內核的頂級子系統,演進為一個通用執行引擎,允許用戶在內核中運行自己的程序。
掛載點(Hook)eBPF程序基于事件觸發,當內核代碼走到對應的掛載點就會執行掛載在此處的eBPF程序。常見的掛載點有:系統調用,內核函數進入/退出,內核跟蹤點,網絡數據包等等。
映射(Maps)eBPF程序中用來存儲數據,共享數據的結構體就是Maps,內核內置了多種類型的Maps給開發者使用,常見的有哈希表,數組,LRU,Ring buffer等等。
幫助函數(Helper Calls)內核為了保證安全,運行在內核中的eBPF程序只能調用當前內核版本預定義好的函數,不能隨意調用其他內核函數,函數名稱都是以bpf_開頭命名。例如u32 bpf_get_smp_processor_id(void),可以獲取當前eBPF程序運行在哪個cpu上。
加載與驗證(Loader & Verification)編寫好的eBPF程序需要先通過編譯,生成字節碼,之后通過調用bpf系統調用將字節碼加載到內核,此時內核會運行自己的驗證器來檢驗eBPF程序,確保程序是安全的,有限循環的,不會把linux系統搞壞。
eBPF特點
強大的內核可編程性。隨著內核版本的升級,內核中可以運行eBPF程序的地方越來越多,eBPF可以做的事情也越來越多。不同內核功能加入eBPF版本列表:
XDP(快速數據路徑)
Kernel 4.8
LIRC(紅外)
Kernel 4.18
限制100萬條指令
Kernel 5.2
Socket lookup(控制數據包入socket)
Kernel 5.9
開發方便開發者不需要自己定義數據結構,直接使用現成的Maps進行存儲共享數據,只需要關注具體的業務實現代碼。由于eBPF程序先編譯成字節碼,之后內核自己校驗通過之后再生成可用的內核代碼,所以可以一次編譯處處運行,不需要像內核模塊一樣,每次更新內核之后都要重新編譯。
可以在x86上編譯mips架構上運行的eBPF字節碼。免去交叉編譯的痛苦。
安全數據操作都是通過Maps,操作Maps的函數也是預先定義好的,不存在訪問空指針。
eBPF使用
介紹了這么多eBPF的概念,接下來實際操作一下,看看eBPF程序如何編譯和使用,這里采用原汁原味的linux源代碼編譯演示。
使用最新的archlinux系統,其他系統也差不多,稍微按照實際情況改一下。
# asp checkout linux# 準備linux內核源碼
# makepkg -o -d –skippgpcheck# 下載linux源代碼
# make mrproper
# zcat /proc/config.gz >.config
# make headers_install
# make modules_prepare
# make VMLINUX_BTF=/sys/kernel/btf/vmlinux M=samples/bpf
# asp checkout linux# 準備linux內核源碼
# makepkg -o -d –skippgpcheck# 下載linux源代碼
# make mrproper
# zcat /proc/config.gz >.config
# make headers_install
# make modules_prepare
# make VMLINUX_BTF=/sys/kernel/btf/vmlinux M=samples/bpf
這里把內核自帶的bpf示例程序都編譯出來了,在目錄samples/bpf下面。
這里我們具體看一個sampleip的eBPF程序,看看eBPF程序是如何編寫的
直接上代碼:
首先17到23行定義了一個maps,叫ip_map,類型是哈希,鍵是u64,值是u32,最大長度8192。
之后定義了一個do_sample函數,函數參數類型是bpf_perf_event_data,里面有當前內核IP指令指針寄存器的內容。通過調用bpf_map_lookup_elem函數來更新ip_map。
運行內核編譯好的sampleip程序,默認是采樣5秒,每秒采樣99次,程序結束后會把ip_map采集到的信息打印出來。
從這個實例中可以看出,開發的eBPF程序比傳統的內核開發方便了很多,數據結構不用操心,可以調用的函數也不用操心,都是預先定義好的,只需要實現自己的業務邏輯即可。
eBPF使用場景
linux性能分析,性能調優上面的sampleip就是簡單的內核性能分析,可以看出當前內核經常調用的函數。eBPF對于內核開銷很小,可以在生產環境排查問題的時候進行精確定位,同時由于eBPF的安全性,不用擔心會把內核搞掛。有興趣的可以去看一下bcc,里面對于內核每個子系統都有對應的eBPF監控程序,非常方便。
linux網絡加速eBPF在這個領域中也是牛的很,底層有XDP快速數據路徑,可以直接在網卡收到數據包的同時進行處理,避免內核分配skb開銷,可以用來實現DDos,負載均衡,性能媲美DPDK。再往上一點內核的tc也可以hook eBPF程序實現自定義流量分類,再向上的socket層還可以調用eBPF實現動態修改socket選項,甚至tcp的擁塞算法內核也提供了eBPF掛載的地方,可以自己實現一套新的擁塞算法。
安全管理systemd中使用eBPF控制服務可以監聽的端口,libvirtd也使用eBPF進行設備的訪問控制,社區還有eBPF控制進程允許訪問的文件,允許讀寫哪些/sys文件。
eBPF的出現讓Linux內核開發變得簡單,降低了內核開發門檻,為普通人了解深入linux內核提供了途徑,真的是一個革命性的發明,有linux的地方就有ebpf ^^。