我是 LEE李破,老李,一個(gè)在 IT 行業(yè)摸爬滾打 16 年的技術(shù)老兵壹将。
事件背景
最近一直想寫(xiě)一個(gè)關(guān)于 ebpf 的文章嗤攻,但是不知道從哪里開(kāi)始寫(xiě)起。思考良久之后诽俯,決定站在一個(gè)研發(fā)的角度妇菱,求真務(wù)實(shí)的從入門(mén)的角度來(lái)介紹下 ebpf。 實(shí)際我真正的目的是能夠用好的 cilium 的整個(gè)系統(tǒng)暴区,但是站在 cilium 角度上看 ebpf闯团,它確實(shí)一個(gè)黑盒子。emmm.... 這個(gè)不符合我的人設(shè)仙粱,決定今天整理一個(gè)文章往里嘗試看看究竟房交。
廢話不多說(shuō),我們從一個(gè)非常簡(jiǎn)單的 helloworld 的編寫(xiě)出發(fā)伐割。
前置知識(shí)
這個(gè)作為 epbf 學(xué)習(xí)的第一章知識(shí)候味,我相信很多小伙伴跟我一樣都是“白手起家淹遵,一窮二白”,害怕 ebpf 整個(gè)環(huán)境的復(fù)雜性负溪。既然是前置知識(shí)透揣,那一定是最簡(jiǎn)單的,能夠讓我們一下就懂的川抡。
ebpf 是一個(gè)轉(zhuǎn)發(fā)層的驅(qū)動(dòng)模型辐真,既然是模型,就一定有定義和抽象等概念崖堤,我們通過(guò)下圖就可以有了第一印象侍咱。
eBPF 分為用戶空間程序和內(nèi)核程序兩部分:
- 用戶空間程序負(fù)責(zé)加載 BPF 字節(jié)碼至內(nèi)核,如需要也會(huì)負(fù)責(zé)讀取內(nèi)核回傳的統(tǒng)計(jì)信息或者事件詳情
- 內(nèi)核中的 BPF 字節(jié)碼負(fù)責(zé)在內(nèi)核中執(zhí)行特定事件密幔,如需要也會(huì)將執(zhí)行的結(jié)果通過(guò) maps 或者 perf-event 事件發(fā)送至用戶空間
- 其中用戶空間程序與內(nèi)核 BPF 字節(jié)碼程序可以使用 map 結(jié)構(gòu)實(shí)現(xiàn)雙向通信楔脯,這為內(nèi)核中運(yùn)行的 BPF 字節(jié)碼程序提供了更加靈活的控制
上面的內(nèi)容用大白話說(shuō):ebpf 包含用戶層和內(nèi)核層兩層組成。用戶層主要是負(fù)責(zé)業(yè)務(wù)邏輯處理和響應(yīng)胯甩,同時(shí)也兼顧著內(nèi)核中的 epbf 的邏輯 bytescode 生成(當(dāng)然這里可以使用第三方生成)昧廷,并將 bytescode 注入到內(nèi)核中。內(nèi)核層主要是接受 bytescode偎箫,然后在內(nèi)核層內(nèi)完成對(duì) bytescode 執(zhí)行木柬。果真是妥妥的控制與轉(zhuǎn)發(fā)分離的模型,要不然為什么 ebpf 這么高效呢淹办?
上手開(kāi)發(fā)
環(huán)境準(zhǔn)備
我們使用 Ubuntu 20.04 這個(gè)系統(tǒng)作為整體開(kāi)發(fā)環(huán)境眉枕,內(nèi)核使用的是 5.4.0。
檢查內(nèi)核
root@ubuntu:/tmp# uname -a
Linux ubuntu 5.4.0-126-generic #142-Ubuntu SMP Fri Aug 26 12:12:57 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
安裝編譯工具和包
# apt install -y bison build-essential cmake flex git libedit-dev pkg-config libmnl-dev \
python zlib1g-dev libssl-dev libelf-dev libcap-dev libfl-dev llvm clang pkg-config \
gcc-multilib luajit libluajit-5.1-dev libncurses5-dev libclang-dev clang-tools
安裝內(nèi)核源碼
# apt install linux-source-5.4.0
源碼安裝至 /usr/src/ 目錄下, 使用下面的命里初始化編譯環(huán)境怜森。
# cd /usr/src/linux-source-5.4.0/
#
# tar xvf linux-source-5.4.0.tar.bz2
# cd linux-source-5.4.0
#
# cp -v /boot/config-$(uname -r) .config
# make headers_install && make modules_prepare
開(kāi)發(fā)代碼
ebpf 是使用內(nèi)核層和用戶兩層速挑,那么我們寫(xiě)代碼也要實(shí)現(xiàn)兩層代碼。
內(nèi)核層代碼:hello_kern.c
#include <linux/bpf.h>
#include "bpf/bpf_helpers.h"
#define SEC(NAME) __attribute__((section(NAME), used))
SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(void *ctx)
{
char msg[] = "Hello Shengyan.li, I'm in eBPF World\n";
bpf_trace_printk(msg, sizeof(msg)); /* 把信息打印到 trace 隊(duì)列中 */
return 0;
}
char _license[] SEC("license") = "GPL"; /* 這里很重要副硅,告訴 ebpf 當(dāng)前的模塊協(xié)議是 GPL姥宝,如果不是GPL,libpbf 報(bào)錯(cuò) err = 22, 退出想许。*/
用戶層代碼:hello_user.c
#include <stdio.h>
#include "bpf_load.h"
int main(int argc, char **argv)
{
if(load_bpf_file("hello_kern.o") != 0) /* 加載生成的 bytescode 到內(nèi)核*/
{
printf("The kernel didn't load BPF program\n");
return -1;
}
read_trace_pipe(); /* 從 trace 隊(duì)列中讀取信息伶授,輸出到 stdout */
return 0;
}
編譯測(cè)試
修改 Makefile
在 /usr/src/linux-source-5.4.0/linux-source-5.4.0/samples/bpf 目錄下,修改 Makefile流纹,然后在對(duì)應(yīng)的位置添加對(duì)應(yīng)內(nèi)容糜烹。
hostprogs-y += helloworld
hello-objs := bpf_load.o hello_user.o
always += hello_kern.o
編譯
# cd /usr/src/linux-source-5.4.0/linux-source-5.4.0
# make M=samples/bpf
測(cè)試
# cd /usr/src/linux-source-5.4.0/linux-source-5.4.0/samples/bpf
# ./helloworld
當(dāng)你執(zhí)行上面的 helloworld 命令后,發(fā)現(xiàn)沒(méi)有反應(yīng)漱凝,不要慌疮蹦,這個(gè)是正常的。這個(gè)時(shí)候你只需要在打開(kāi)一個(gè)新會(huì)話連接到編譯環(huán)境的機(jī)器上茸炒,然后在這個(gè)新窗口執(zhí)行如下命令:
# watch "ls -l"
之前執(zhí)行 helloworld 的命令的窗口會(huì)有如下的輸出:
總結(jié)
我們通過(guò)上面的操作和使用愕乎,基本確認(rèn)了 epbf 整體開(kāi)發(fā)流程阵苇。 通過(guò)簡(jiǎn)單的開(kāi)發(fā),我們基本可以得出下面的幾個(gè)結(jié)論:
- ebpf 開(kāi)發(fā)不需要重新編譯內(nèi)核和重啟服務(wù)器感论,能夠非成鹣睿快速的迭代開(kāi)發(fā)。
- 和 DPDK 的邏輯很像比肄,之前很多積累的知識(shí)這里可以無(wú)腦復(fù)用快耿。
- 有很多開(kāi)發(fā)框架和庫(kù)都在使用 ebpf,代碼風(fēng)險(xiǎn)可控芳绩。
- ebpf 開(kāi)發(fā)模型沒(méi)有那么復(fù)雜掀亥,還是很簡(jiǎn)單,且很有意思的妥色。