透視Linux內(nèi)核 神奇的BPF一

一 前言

作為一個(gè)coder叛溢,時(shí)不時(shí)會(huì)遇到性能問題塑悼,有時(shí)候明明看資源,cpu雇初,io都占用不高拢肆,程序的性能就是上不去,真有一種想進(jìn)入到計(jì)算機(jī)里面看看到底發(fā)生什么的沖突;還有優(yōu)化性能的時(shí)候不知道整個(gè)系統(tǒng)的短板到底是哪一塊郭怪,如何去優(yōu)化它支示?

根本原因其實(shí)是對(duì)系統(tǒng)的內(nèi)核不夠了解,導(dǎo)致雖然有解決問題的激情和動(dòng)力鄙才,但是總是難找到關(guān)鍵點(diǎn)颂鸿,彷徨而不得其門。讓我學(xué)習(xí)內(nèi)核攒庵,卻又望而退步嘴纺,覺得難度還是太大,有沒有不用深入了解系統(tǒng)內(nèi)核浓冒,但是又能深入觀察內(nèi)核行為的辦法那栽渴,這時(shí)候我發(fā)現(xiàn)了BPF和eBPF,通過它有了透視內(nèi)核的能力稳懒,所以就開始了BPF學(xué)習(xí)之旅闲擦。

二 BPF是個(gè)什么

BPF原來是Berkely Packet Filter(伯克利數(shù)據(jù)包過濾器)的縮寫,原來是提升pcap過濾性能的场梆,比當(dāng)時(shí)最快的包過濾技術(shù)快20倍墅冷,只所以性能高,是因?yàn)樗ぷ髟趦?nèi)核中或油,避免包從內(nèi)核態(tài)復(fù)制到用戶態(tài)所以速度快寞忿,后來Alexei Starovoitov 大牛在2014年重新實(shí)現(xiàn)了BPF,將其擴(kuò)展成了通用的執(zhí)行引擎顶岸,稱為eBPF腔彰,官方縮寫仍是BPF。

簡單解釋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)類似js里面的監(jiān)聽,監(jiān)聽到按鈕點(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í)間從而我們可以知道問題到底出在哪里去扣。
如下圖:


來自ebpf.io

四 BPF如何工作

經(jīng)典的BPF的工作模式是用戶使用BPF虛擬機(jī)的指令集定義過濾表達(dá)式氛魁,傳遞給內(nèi)核,由解釋器運(yùn)行厅篓,使得包過濾器可以直接在內(nèi)核態(tài)工作秀存,避免向用戶態(tài)復(fù)制數(shù)據(jù),從而提升性能羽氮,比如tcpdump的BPF過濾指令實(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

執(zhí)行過程如下:


經(jīng)典BPF過濾指令執(zhí)行過程

后來又一位大牛EricDumazet在2011年7月發(fā)布的Linux 3.0中增加了JIT(即時(shí)編譯)或链,性能比解釋執(zhí)行更快,多像java的虛擬機(jī)档押,可以解釋執(zhí)行也可以即時(shí)編譯執(zhí)行澳盐。

現(xiàn)在BPF的執(zhí)行過程如下示意圖:


圖片來自brendangregg.com
  1. 編寫eBPF 代碼。
  2. 將eBPF代碼通過LLVM把編寫的eBPF代碼轉(zhuǎn)成字節(jié)碼令宿;
  3. 通過bpf系統(tǒng)調(diào)用提交給系統(tǒng)內(nèi)核叼耙;
  4. 內(nèi)核通過驗(yàn)證器對(duì)代碼做安全性驗(yàn)證(包括對(duì)無界循環(huán)的檢查);
  5. 只有校驗(yàn)通過的字節(jié)碼才會(huì)提交到JIT進(jìn)行編譯成可以直接執(zhí)行的機(jī)器指令;
  6. 當(dāng)事件發(fā)生時(shí)候吊档,調(diào)用這些指令執(zhí)行却邓,將結(jié)果保存到map中。
  7. 用戶程序通過映射來獲取執(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)樗蕾嚨妮o助函數(shù)迎捺,映射表,BPF指令集屬于穩(wěn)定的API查排。

五 編寫B(tài)PF程序

5.1 準(zhǔn)備知識(shí)

開發(fā)BPF指令顯然不適合直接用BPF指令開發(fā)凳枝,所以大牛們開發(fā)了一些前端工具讓我們可以更方便的開發(fā),比如我們可以通過C來編寫B(tài)PF程序跋核,然后通過LLVM編譯成BPF岖瑰。

當(dāng)然還是負(fù)載,又有了BCC和bpftrace砂代。BCC即BPF Compiler Collection蹋订,提供了開發(fā)BPF跟蹤程序的高級(jí)框架,提供編寫內(nèi)核BPF程序的C語言環(huán)境刻伊,同時(shí)提供了許多高級(jí)語言的接口露戒,比如pyhton等。同時(shí)BCC中提供了很多BPF工具捶箱,讓我們可以方便使用用于性能分析和故障分析智什,在開發(fā)BPF程序之前可以看看。

bpftrace編寫單行程序或短小腳本更加適合丁屎,BCC適合編寫復(fù)雜的腳本和作為后臺(tái)進(jìn)程使用荠锭。libbcc和libbpf為兩者提供底層支持。

BPF程序編寫可以借助工具
BCC開發(fā)的動(dòng)態(tài)追蹤工具集

5.2 環(huán)境準(zhǔn)備

我的測試環(huán)境是centos8.5版本晨川,內(nèi)核版本為4.18证九,而BPF最好用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

內(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后即可用原來配置編譯

#編譯內(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

安裝BPF相關(guān)庫和工具:

yum install libbpf-devel make clang llvm elfutils-libelf-devel bpftool bcc-tools bcc-devel
  1. llvm : 將eBPF程序編譯成字節(jié)碼工具愧怜。
  2. c代碼構(gòu)建工具make
  3. eBPF工具集BCC和它依賴的頭文件。
  4. libelf庫以及ebpf管理工具ebpftool妈拌。
  5. 用戶程序通過BPF映射查詢到BPF字節(jié)碼的字節(jié)碼運(yùn)行結(jié)果拥坛。

5.3 依賴BCC開發(fā)BPF的helloworld

步驟如下:

  1. 用C語言開發(fā)一個(gè)eBPF程序;
  2. 用LLVM把eBPF程序編譯成BPF字節(jié)碼供炎;
  3. 通過bpf系統(tǒng)調(diào)用渴逻,把BPF字節(jié)碼提交給內(nèi)核疾党;
  4. 內(nèi)核驗(yàn)證并運(yùn)行BPF字節(jié)碼音诫,并把相應(yīng)狀態(tài)保存到BPF映射中;
  5. 用戶程序通過 BPF 映射查詢 BPF 字節(jié)碼雪位,得到執(zhí)行結(jié)果竭钝;


    BPF開發(fā)執(zhí)行過程

這個(gè)流程一般比較麻煩,可以利用BCC來簡化,用python腳本加載BPF程序香罐,編譯為字節(jié)碼卧波,并通過系統(tǒng)調(diào)用將BPF字節(jié)碼,運(yùn)行BPF字節(jié)碼庇茫;

5.3.1 用C開發(fā)一個(gè)eBPF程序

int hello(void *ctx)
{
    bpf_trace_printk("Hello, World!");
    return 0;
}

bpf_trace_printk 是常用的BPF輔助函數(shù)港粱,它就是簡單的打印一個(gè)字符串;不過eBPF輸出是內(nèi)核調(diào)試文件: /sys/kernel/debug/tracing/trace_pipe

5.3.2 使用python和BCC開發(fā)BPF的加載程序

#!/usr/bin/env python3
# 1) 導(dǎo)入BCC庫中的BPF模塊
from bcc import BPF

# 2) 加載C程序開發(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()

運(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!'

問題解決

問題一 編譯過程磁盤空間滿了

按照[https://blog.csdn.net/xionglangs/article/details/108866146]擴(kuò)展磁盤查坪;(https://blog.csdn.net/xionglangs/article/details/108866146)

問題二 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

解決辦法:
注釋掉.config中的CONFIG_DEBUG_INFO_BTF 或 yum install dwarves

問題三 編譯需要支持bpf

 編譯內(nèi)核的時(shí)候bpf的編譯選項(xiàng)打開,在.config文件中添加或修改
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
# [optional, for tc filters]
CONFIG_NET_CLS_BPF=m
# [optional, for tc actions]
CONFIG_NET_ACT_BPF=m
CONFIG_BPF_JIT=y
# [for Linux kernel versions 4.1 through 4.6]
CONFIG_HAVE_BPF_JIT=y
# [for Linux kernel versions 4.7 and later]
CONFIG_HAVE_EBPF_JIT=y
# [optional, for kprobes]
CONFIG_BPF_EVENTS=y
# Need kernel headers through /sys/kernel/kheaders.tar.xz
CONFIG_IKHEADERS=y


CONFIG_NET_SCH_SFQ=m
CONFIG_NET_ACT_POLICE=m
CONFIG_NET_ACT_GACT=m
CONFIG_DUMMY=m
CONFIG_VXLAN=m

問題四 make M=samples/bpf報(bào)錯(cuò)

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. 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=]

解決辦法:

vim /root/core/linux-5.16.10/samples/bpf/bpftool//bootstrap/libbpf//include/bpf/bpf_helper_defs.h
添加頭文件:
#include <asm/types.h>
#include <linux/types.h>

問題五 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]# 

更改.config 配置:

CONFIG_DEBUG_INFO_BTF=y
make -j4

問題六 fatal error: 'gnu/stubs-32.h' file not found

升級(jí):
yum install glibc-devel
yum install glibc-devel.i686

參考

[詳細(xì)介紹了BPF程序編譯生成字節(jié)碼過程](https://www.cnblogs.com/lfri/p/15402973.html)
[https://maao.cloud/2021/03/01/%E7%AC%94%E8%AE%B0-BPF-and-XDP-Reference-Guide-cilium/#LLVM](https://maao.cloud/2021/03/01/%E7%AC%94%E8%AE%B0-BPF-and-XDP-Reference-Guide-cilium/#LLVM)
[技術(shù)|深入理解 BPF:一個(gè)閱讀清單 (linux.cn)](https://linux.cn/article-9507-1.html)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宁炫,一起剝皮案震驚了整個(gè)濱河市偿曙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌羔巢,老刑警劉巖望忆,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異竿秆,居然都是意外死亡启摄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門幽钢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鞋仍,“玉大人,你說我怎么就攤上這事搅吁⊥矗” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵谎懦,是天一觀的道長肚豺。 經(jīng)常有香客問我,道長界拦,這世上最難降的妖魔是什么吸申? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮享甸,結(jié)果婚禮上截碴,老公的妹妹穿的比我還像新娘。我一直安慰自己蛉威,他們只是感情好日丹,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蚯嫌,像睡著了一般哲虾。 火紅的嫁衣襯著肌膚如雪丙躏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天束凑,我揣著相機(jī)與錄音晒旅,去河邊找鬼。 笑死汪诉,一個(gè)胖子當(dāng)著我的面吹牛废恋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扒寄,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼拴签,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了旗们?” 一聲冷哼從身側(cè)響起蚓哩,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎上渴,沒想到半個(gè)月后岸梨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稠氮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年曹阔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隔披。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赃份,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奢米,到底是詐尸還是另有隱情抓韩,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布鬓长,位于F島的核電站谒拴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏涉波。R本人自食惡果不足惜英上,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望啤覆。 院中可真熱鬧苍日,春花似錦、人聲如沸窗声。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嫌佑。三九已至豆茫,卻和暖如春侨歉,著一層夾襖步出監(jiān)牢的瞬間屋摇,已是汗流浹背揩魂。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炮温,地道東北人火脉。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像柒啤,于是被迫代替她去往敵國和親倦挂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容