新聞中心
近兩年BPF技術(shù)躍然成為了一項(xiàng)熱門(mén)技術(shù),在KubeCon 2020 Europe會(huì)議上有7個(gè)關(guān)于BPF的技術(shù)分享, 而在KubeCon 2020 China會(huì)議上也已有了3個(gè)關(guān)于BPF技術(shù)的中文分享,分別來(lái)自騰訊和PingCAP,涉足網(wǎng)絡(luò)優(yōu)化和系統(tǒng)追蹤等領(lǐng)域。在中文社區(qū)里,包括阿里巴巴、網(wǎng)易、字節(jié)跳動(dòng)等國(guó)內(nèi)第一梯隊(duì)IT公司也越來(lái)越關(guān)注BPF這項(xiàng)新技術(shù)。

一、前言
作為一個(gè)coder,時(shí)不時(shí)會(huì)遇到性能問(wèn)題,有時(shí)候明明看資源,cpu,io都占用不高,程序的性能就是上不去,真有一種想進(jìn)入到計(jì)算機(jī)里面看看到底發(fā)生什么的沖突;還有優(yōu)化性能的時(shí)候不知道整個(gè)系統(tǒng)的短板到底是哪一塊,如何去優(yōu)化它?
根本原因其實(shí)是對(duì)系統(tǒng)的內(nèi)核不夠了解,導(dǎo)致雖然有解決問(wèn)題的激情和動(dòng)力,但是總是難找到關(guān)鍵點(diǎn),彷徨而不得其門(mén)。讓我學(xué)習(xí)內(nèi)核,卻又望而退步,覺(jué)得難度還是太大,有沒(méi)有不用深入了解系統(tǒng)內(nèi)核,但是又能深入觀察內(nèi)核行為的辦法那,這時(shí)候我發(fā)現(xiàn)了BPF和eBPF,通過(guò)它有了透視內(nèi)核的能力,所以就開(kāi)始了BPF學(xué)習(xí)之旅。
二、BPF是個(gè)什么
BPF原來(lái)是Berkely Packet Filter(伯克利數(shù)據(jù)包過(guò)濾器)的縮寫(xiě),原來(lái)是提升pcap過(guò)濾性能的,比當(dāng)時(shí)最快的包過(guò)濾技術(shù)快20倍,只所以性能高,是因?yàn)樗ぷ髟趦?nèi)核中,避免包從內(nèi)核態(tài)復(fù)制到用戶態(tài)所以速度快,后來(lái)Alexei Starovoitov 大牛在2014年重新實(shí)現(xiàn)了BPF,將其擴(kuò)展成了通用的執(zhí)行引擎,稱(chēng)為eBPF,官方縮寫(xiě)仍是BPF。
簡(jiǎn)單解釋BPF作用,BPF提供了一種當(dāng)內(nèi)核或應(yīng)用特定事件發(fā)生時(shí)候,執(zhí)行一段代碼的能力。BPF 采用了虛擬機(jī)指令規(guī)范,所以也可以看一種虛擬機(jī)實(shí)現(xiàn),使我們可以在不修改內(nèi)核源碼和重新編譯的情況下,提供一種擴(kuò)展內(nèi)核的能力的方法。
三、BPF能干嘛
BPF程序不像一般程序可以獨(dú)立運(yùn)行,它是被動(dòng)運(yùn)行的,需要事件觸發(fā)才能運(yùn)行,有點(diǎn)類(lèi)似js里面的監(jiān)聽(tīng),監(jiān)聽(tīng)到按鈕點(diǎn)擊執(zhí)行一小段代碼。這些事件包括系統(tǒng)調(diào)用,內(nèi)核跟蹤,內(nèi)核函數(shù),用戶函數(shù),網(wǎng)絡(luò)事件等。
具體能干嘛那,作用還是很強(qiáng)大,可以進(jìn)行系統(tǒng)故障診斷,因?yàn)槠溆型敢晝?nèi)核的能力;網(wǎng)絡(luò)性能優(yōu)化,因?yàn)樗梢栽趦?nèi)核態(tài)接收網(wǎng)絡(luò)包,并做修改和轉(zhuǎn)發(fā);系統(tǒng)安全,因?yàn)樗梢灾袛喾欠ㄟB接等;性能監(jiān)控,因?yàn)槠渫敢暷芰?,可以查看函?shù)耗費(fèi)時(shí)間從而我們可以知道問(wèn)題到底出在哪里。 如下圖:
四、BPF如何工作
經(jīng)典的BPF的工作模式是用戶使用BPF虛擬機(jī)的指令集定義過(guò)濾表達(dá)式,傳遞給內(nèi)核,由解釋器運(yùn)行,使得包過(guò)濾器可以直接在內(nèi)核態(tài)工作,避免向用戶態(tài)復(fù)制數(shù)據(jù),從而提升性能,比如tcpdump的BPF過(guò)濾指令實(shí)例如下:
[root@localhost ~]# tcpdump -d port 80
(000) ldh [12]
(001) jeq #0x86dd jt 2 jf 10
(002) ldb [20]
(003) jeq #0x84 jt 6 jf 4
(004) jeq #0x6 jt 6 jf 5
(005) jeq #0x11 jt 6 jf 23
(006) ldh [54]
(007) jeq #0x50 jt 22 jf 8
(008) ldh [56]
(009) jeq #0x50 jt 22 jf 23
(010) jeq #0x800 jt 11 jf 23
(011) ldb [23]
(012) jeq #0x84 jt 15 jf 13
(013) jeq #0x6 jt 15 jf 14
(014) jeq #0x11 jt 15 jf 23
(015) ldh [20]
(016) jset #0x1fff jt 23 jf 17
(017) ldxb 4*([14]&0xf)
(018) ldh [x + 14]
(019) jeq #0x50 jt 22 jf 20
(020) ldh [x + 16]
(021) jeq #0x50 jt 22 jf 23
(022) ret #262144
(023) ret #0
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.
執(zhí)行過(guò)程如下:
后來(lái)又一位大牛EricDumazet在2011年7月發(fā)布的Linux 3.0中增加了JIT(即時(shí)編譯),性能比解釋執(zhí)行更快,多像java的虛擬機(jī),可以解釋執(zhí)行也可以即時(shí)編譯執(zhí)行。
現(xiàn)在BPF的執(zhí)行過(guò)程如下示意圖:
-
編寫(xiě)eBPF 代碼;
-
將eBPF代碼通過(guò)LLVM把編寫(xiě)的eBPF代碼轉(zhuǎn)成字節(jié)碼;
-
通過(guò)bpf系統(tǒng)調(diào)用提交給系統(tǒng)內(nèi)核;
-
內(nèi)核通過(guò)驗(yàn)證器對(duì)代碼做安全性驗(yàn)證(包括對(duì)無(wú)界循環(huán)的檢查);
-
只有校驗(yàn)通過(guò)的字節(jié)碼才會(huì)提交到JIT進(jìn)行編譯成可以直接執(zhí)行的機(jī)器指令;
-
當(dāng)事件發(fā)生時(shí)候,調(diào)用這些指令執(zhí)行,將結(jié)果保存到map中;
-
用戶程序通過(guò)映射來(lái)獲取執(zhí)行結(jié)果。
五、BPF 和內(nèi)核模塊對(duì)比
BPF程序會(huì)進(jìn)行安全檢查,內(nèi)核模塊可能會(huì)引入Bug。
BPF程序不能隨意調(diào)用內(nèi)核函數(shù),只能調(diào)用部分輔助函數(shù)。
BPF的棧空間最大為512個(gè)字節(jié),不能擴(kuò)大,只能借助map存儲(chǔ)。
BPF程序可以一次編譯到處運(yùn)行,因?yàn)樗蕾?lài)的輔助函數(shù),映射表,BPF指令集屬于穩(wěn)定的API。
六、編寫(xiě)B(tài)PF程序
6.1 準(zhǔn)備知識(shí)
開(kāi)發(fā)BPF指令顯然不適合直接用BPF指令開(kāi)發(fā),所以大牛們開(kāi)發(fā)了一些前端工具讓我們可以更方便的開(kāi)發(fā),比如我們可以通過(guò)C來(lái)編寫(xiě)B(tài)PF程序,然后通過(guò)LLVM編譯成BPF。
當(dāng)然還是負(fù)載,又有了BCC和bpftrace。BCC即BPF Compiler Collection,提供了開(kāi)發(fā)BPF跟蹤程序的高級(jí)框架,提供編寫(xiě)內(nèi)核BPF程序的C語(yǔ)言環(huán)境,同時(shí)提供了許多高級(jí)語(yǔ)言的接口,比如pyhton等。同時(shí)BCC中提供了很多BPF工具,讓我們可以方便使用用于性能分析和故障分析,在開(kāi)發(fā)BPF程序之前可以看看。
bpftrace編寫(xiě)單行程序或短小腳本更加適合,BCC適合編寫(xiě)復(fù)雜的腳本和作為后臺(tái)進(jìn)程使用。libbcc和libbpf為兩者提供底層支持。
BPF程序編寫(xiě)可以借助工具
BCC開(kāi)發(fā)的動(dòng)態(tài)追蹤工具集
6.2 環(huán)境準(zhǔn)備
我的測(cè)試環(huán)境是centos8.5版本,內(nèi)核版本為4.18,而B(niǎo)PF最好用5.x版本的內(nèi)核需要先升級(jí)下。
[root@localhost ~]# cat /etc/centos-release
CentOS Linux release 8.5.2111
[root@localhost ~]# uname -a
Linux localhost.localdomain 4.18.0-348.7.1.el8_5.x86_64 #1 SMP Wed Dec 22 13:25:12 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
1.2.3.4.
內(nèi)核升級(jí)步驟:
#1. 到[https://www.kernel.org/](https://www.kernel.org/)查看穩(wěn)定的內(nèi)核版本為5.16.10
#2. 下載編譯
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.16.10.tar.xz
tar xvf linux-5.16.10.tar.xz
cd linux-5.16.10/
uname -a
cp /boot/config-4.18.0-348.7.1.el8_5.x86_64 .config
#注釋掉CONFIG_SYSTEM_TRUSTED_KEYS
make menuconfig
#進(jìn)入界面按tab 選擇Load 加載.config ,在Save后即可用原來(lái)配置編譯
#編譯內(nèi)核核心
make -j 4
make modules_install
#安裝內(nèi)核核心
make install
grub2-set-default 0 #0表示 /boot/grub2/grub.cfg 文件中排在第一位的 menuentry 段
reboot
make modules_prepare
make script
make headers_install INSTALL_HDR_PATH=/usr/include
#安裝bpf 實(shí)例
make M=samples/bpf
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.
安裝BPF相關(guān)庫(kù)和工具:
yum install libbpf-devel make clang llvm elfutils-libelf-devel bpftool bcc-tools bcc-devel
1.
-
llvm : 將eBPF程序編譯成字節(jié)碼工具。
-
c代碼構(gòu)建工具make。
-
eBPF工具集BCC和它依賴(lài)的頭文件。
-
libelf庫(kù)以及ebpf管理工具ebpftool。
-
用戶程序通過(guò)BPF映射查詢到BPF字節(jié)碼的字節(jié)碼運(yùn)行結(jié)果。
6.3 依賴(lài)BCC開(kāi)發(fā)BPF的helloworld
步驟如下:
用C語(yǔ)言開(kāi)發(fā)一個(gè)eBPF程序;
用LLVM把eBPF程序編譯成BPF字節(jié)碼;
通過(guò)bpf系統(tǒng)調(diào)用,把BPF字節(jié)碼提交給內(nèi)核;
內(nèi)核驗(yàn)證并運(yùn)行BPF字節(jié)碼,并把相應(yīng)狀態(tài)保存到BPF映射中;
用戶程序通過(guò) BPF 映射查詢 BPF 字節(jié)碼,得到執(zhí)行結(jié)果;
這個(gè)流程一般比較麻煩,可以利用BCC來(lái)簡(jiǎn)化,用python腳本加載BPF程序,編譯為字節(jié)碼,并通過(guò)系統(tǒng)調(diào)用將BPF字節(jié)碼,運(yùn)行BPF字節(jié)碼;
6.3.1 用C開(kāi)發(fā)一個(gè)eBPF程序
int hello(void *ctx)
{
bpf_trace_printk("Hello, World!");
return 0;
}
1.2.3.4.5.
bpf_trace_printk 是常用的BPF輔助函數(shù),它就是簡(jiǎn)單的打印一個(gè)字符串;不過(guò)eBPF輸出是內(nèi)核調(diào)試文件:
/sys/kernel/debug/tracing/trace_pipe
1.
6.3.2 使用python和BCC開(kāi)發(fā)BPF的加載程序
#!/usr/bin/env python3
# 1) 導(dǎo)入BCC庫(kù)中的BPF模塊
from bcc import BPF
# 2) 加載C程序開(kāi)發(fā)的BPF程序
b = BPF(src_file="hello.c")
# 3) 將此BPF程序掛載到內(nèi)核探針,其中do_sys_openat2是系統(tǒng)調(diào)用openat 在內(nèi)核實(shí)現(xiàn)
b.attach_kprobe(event="do_sys_openat2", fn_name="hello_world")
# 4) 讀取和打印 /sys/kernel/debug/tracing/trace_pipe
b.trace_print()
1.2.3.4.5.6.7.8.9.10.
運(yùn)行查看:
> python3 hello.py
b' pmdalinux-1298 [007] d..31 6758.674383: bpf_trace_printk: Hello, World!'
b' pmdalinux-1298 [007] d..31 6758.674395: bpf_trace_printk: Hello, World!'
b' pmdalinux-1298 [007] d..31 6758.674410: bpf_trace_printk: Hello, World!'
b' pmdalinux-1298 [007] d..31 6758.674422: bpf_trace_printk: Hello, World!'
b' pmdalinux-1298 [007] d..31 6758.674426: bpf_trace_printk: Hello, World!'
b' python3-73326 [001] d..31 6758.674859: bpf_trace_printk: Hello, World!'
b' irqbalance-942 [006] d..31 6758.894331: bpf_trace_printk: Hello, World!'
b' irqbalance-942 [006] d..31 6758.894593: bpf_trace_printk: Hello, World!'
1.2.3.4.5.6.7.8.9.10.
問(wèn)題解決
問(wèn)題一:編譯過(guò)程磁盤(pán)空間滿了
按照[https://blog.csdn.net/xionglangs/article/details/108866146]擴(kuò)展磁盤(pán);(https://blog.csdn.net/xionglangs/article/details/108866146)
1.
問(wèn)題二:make -j4 編譯報(bào)錯(cuò)
BTF: .tmp_vmlinux.btf: pahole (pahole) is not available
Failed to generate BTF for vmlinux
Try to disable CONFIG_DEBUG_INFO_BTF
make: *** [Makefile:1106: vmlinux] Error 1
1.2.3.4.
解決辦法: 注釋掉.config中的CONFIG_DEBUG_INFO_BTF 或 yum install dwarves
問(wèn)題三:編譯需要支持bpf
編譯內(nèi)核的時(shí)候bpf的編譯選項(xiàng)打開(kāi),在.config文件中添加或修改
編譯內(nèi)核的時(shí)候bpf的編譯選項(xiàng)打開(kāi),在.config文件中添加或修改
CONFIG_CGROUP_BPF=y
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_NET_SCH_INGRESS=m
CONFIG_NET_CLS_BPF=m
CONFIG_NET_CLS_ACT=y
CONFIG_BPF_JIT=y
CONFIG_LWTUNNEL_BPF=y
CONFIG_HAVE_EBPF_JIT=y
CONFIG_BPF_EVENTS=y
CONFIG_TEST_BPF=m
1.2.3.4.5.6.7.8.9.10.11.12.
問(wèn)題四:make M=samples/bpf報(bào)錯(cuò)
/root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:322:63: error: unknown type name '__u32'
static long (*bpf_tail_call)(void *ctx, void *prog_array_map, __u32 index) = (void *) 12;
^
/root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:350:58: error: unknown type name '__u32'
static long (*bpf_clone_redirect)(struct __sk_buff *skb, __u32 ifindex, __u64 flags) = (void *) 13;
^
fatal error: too many errors emitted, stopping now [-ferror-limit=]1. make M=samples/bpf報(bào)錯(cuò)
/root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:322:63: error: unknown type name '__u32'
static long (*bpf_tail_call)(void *ctx, void *prog_array_map, __u32 index) = (void *) 12;
^
/root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h:350:58: error: unknown type name '__u32'
static long (*bpf_clone_redirect)(struct __sk_buff *skb, __u32 ifindex, __u64 flags) = (void *) 13;
^
fatal error: too many errors emitted, stopping now [-ferror-limit=]
1.2.3.4.5.6.7.8.9.10.11.12.13.14.
解決辦法:
vim /root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h
添加頭文件:
#include
#include
1.2.3.4.
問(wèn)題五:failed to load BTF from /root/core/linux-5.16.10/vmlinux: No such file or directory
Error: failed to load BTF from /root/core/linux-5.16.10/vmlinux: No such file or directory
make[2]: *** [Makefile:179:/root/core/linux-5.16.10/samples/bpf/bpftool/vmlinux.h] 錯(cuò)誤 2
make[1]: *** [samples/bpf/Makefile:296:/root/core/linux-5.16.10/samples/bpf/bpftool/bpftool] 錯(cuò)誤 2
make: *** [Makefile:1846:samples/bpf] 錯(cuò)誤 2
[root@localhost linux-5.16.10]#
1.2.3.4.5.
更改.config 配置:
CONFIG_DEBUG_INFO_BTF=y
make -j4
1.2.
問(wèn)題六:fatal error: ‘gnu/stubs-32.h’ file not found
make[2]: ***
[Makefile:179:/root/core/linux-5.16.10/samples/bpf/bpftool/vmlinux.h] 錯(cuò)誤 2
make[1]: ***
[samples/bpf/Makefile:296:/root/core/linux-5.16.10/samples/bpf/bpftool/bpftool]
錯(cuò)誤 2
make: *** [Makefile:1846:samples/bpf] 錯(cuò)誤 2
文章題目:非常神器的Linux技術(shù):BPF
鏈接地址:http://www.dlmjj.cn/article/cddgipd.html


咨詢
建站咨詢
