一、eBPF是什么
eBPF是extended BPF的縮寫,而BPF是Berkeley Packet Filter的縮寫。對linux網(wǎng)絡(luò)比較熟悉的伙伴對BPF應(yīng)該比較了解,它通過特定的語法規(guī)則使用基于寄存器的虛擬機來描述包過濾的行為。比較常用的功能是通過過濾來統(tǒng)計流量,tcpdump工具就是基于BPF實現(xiàn)的。而eBPF對它進(jìn)行了擴展來實現(xiàn)更多的功能。
主要區(qū)別如下:
1)允許使用C 語言編寫代碼片段,并通過LLVM編譯成eBPF 字節(jié)碼;2)cBPF 只實現(xiàn)了SOCKET_FILTER,而eBPF還有KPROBE 、PERF等。3)BPF使用socket 實現(xiàn)了用戶態(tài)與內(nèi)核交互,eBPF 則定義了一個專用于eBPF 的新的系統(tǒng)調(diào)用,用于裝載BPF 代碼段、創(chuàng)建和讀取BPF map,更加通用。4)BPF map 機制,用于在內(nèi)核中以key-value 的方式臨時存儲BPF 代碼產(chǎn)生的數(shù)據(jù)。
對于eBPF可以簡單的理解成kernel實現(xiàn)了一個虛擬機機制,將類C代碼編譯成字節(jié)碼(后文有詳細(xì)解釋),掛在到內(nèi)核的鉤子上,當(dāng)鉤子被觸發(fā)時,kernel在虛擬機的“沙盒”中運行字節(jié)碼,這樣既能方便的實現(xiàn)很多功能,也能通過沙箱保證內(nèi)核的安全性。
二、eBPF能干什么
如果說BPF專注于流量監(jiān)控,那么eBPF主要專注的是性能領(lǐng)域,通過各種鉤子,能在用戶空間得到系統(tǒng)各種性能指標(biāo)。可以大到監(jiān)控系統(tǒng)整體的統(tǒng)計指標(biāo),也可以小到一個系統(tǒng)函數(shù)的運行時間。
這里需要提一下開源項目 BPF Compiler Collection (BCC),這是一個很方便的基于eBPF的系統(tǒng)監(jiān)視工具,下面這張BCC的說明圖就能很好的說明我們使用eBPF能夠做到的事。BCC在android系統(tǒng)上也可以運行,但是要對系統(tǒng)進(jìn)行一定程度的修改,后續(xù)可能會寫單獨的文章進(jìn)行講解。對于內(nèi)核開發(fā)者我還比較關(guān)注怎么自己來實現(xiàn)監(jiān)控的功能,下文也將做簡單的講解。
從上圖,我么可以看到,eBPF幾乎能監(jiān)控系統(tǒng)的所有方面:
1)應(yīng)用及虛擬機的各種指標(biāo)2)系統(tǒng)庫性能監(jiān)控3)kernel系統(tǒng)調(diào)用性能4)文件系統(tǒng)性能5)網(wǎng)絡(luò)調(diào)用性能6)CPU調(diào)度器性能7)內(nèi)存管理性能8)中斷性能
三、eBPF框架
在開始說明之前先解釋下eBPF上的名詞,來幫忙更好的理解。
1)eBPF bytecode:將C語言寫的鉤子代碼,通過clang編譯成二進(jìn)制字節(jié)碼,通過程序加載到內(nèi)核中,鉤子觸發(fā)后在kernel “虛擬機”中運行。2)JIT: Just-in-time compilation,將字節(jié)碼編譯成本地機器碼來提升運行速度,和Java中的概念類似。
3)Maps:鉤子代碼可以將一些統(tǒng)計類信息保存在鍵值對的map中,來與用戶空間程序進(jìn)行通信,傳遞數(shù)據(jù)。
關(guān)于eBPF機制詳細(xì)的講解網(wǎng)上有很多,這里就不展開了,這里先上一張圖,這里包括了使用或者編寫ebpf涉及到的所有東西,下面會對這個圖進(jìn)行詳細(xì)的講解。
1)foo_kern.c 鉤子實現(xiàn)代碼,主要負(fù)責(zé):
聲明使用的Map節(jié)點
聲明鉤子掛載點及處理函數(shù)
2)通過LLVM/clang編譯成字節(jié)碼
編譯命令:clang --target=bpf
android平臺有集成eBPF的編譯,后文會提到
3)foo_user.c 用戶空間處理函數(shù),主要負(fù)責(zé):
將foo_kern.c 編譯成的字節(jié)碼加載到kenel中
讀取Map中的信息并處理輸出給用戶
4)kernel當(dāng)收到eBPF的加載請求時,會先對字節(jié)碼進(jìn)行驗證,并通過JIT編譯為機器碼,當(dāng)鉤子事件來臨后,調(diào)用鉤子函數(shù) kernel會對加載的字節(jié)碼進(jìn)行驗證,來保證系統(tǒng)的安全性,主要驗證規(guī)則如下:
a. 檢查是否聲明了GNU GPL,檢查kernel的版本是否支持
b. 函數(shù)調(diào)用規(guī)則:
允許bpf函數(shù)之間的相互調(diào)用
只允許調(diào)用kernel允許的BPF helper函數(shù),具體可以參考linux/bpf.h文件
上述以外的函數(shù)及動態(tài)鏈接都是不允許的。
c. 流程處理規(guī)則:
不允許使用loop循環(huán)以防止進(jìn)入死循環(huán)卡死kernel
不允許有不可到達(dá)的分支代碼
d. 堆棧大小被限制在MAX_BPF_STACK范圍內(nèi)。
e. 編譯的字節(jié)碼大小被限制在BPF_COMPLEXITY_LIMIT_INSNS范圍內(nèi)。
5)鉤子掛載點,主要包括:
另外在kernel的源代碼中samples/bpf目錄下有大量的示例,感興趣的可以閱讀下。
四、eBPF在Android平臺的使用
經(jīng)過上面枯燥的講解,大家應(yīng)該對eBPF有了基礎(chǔ)的認(rèn)識,下面我們就來通過android平臺上的一個監(jiān)控性能的小例子來實操下。
這個小例子的需求是統(tǒng)計系統(tǒng)中每個應(yīng)用在一段時間內(nèi)系統(tǒng)調(diào)用的次數(shù)。
1. android系統(tǒng)對eBPF的編譯支持
目前android編譯系統(tǒng)已經(jīng)對eBPF進(jìn)行了集成,通過android.bp就能很方便的在android源代碼中編譯eBPF的字節(jié)碼。
android.bp示例:
相關(guān)的編譯代碼在soong的bpf.go,雖然google關(guān)于soong的文檔很少,但是至少代碼是比較清晰的。
這里的$ccCmd一般是clang, 所以它的編譯命令主要是clang --target=bpf。和普通的bpf編譯沒有區(qū)別。
2. eBPF鉤子代碼實現(xiàn)
解決了編譯問題,下一步我們開始實現(xiàn)鉤子代碼,我們準(zhǔn)備使用tracepoint鉤子,首先要找到我們需要的tracepoint函數(shù)sys_enter和sys_exit。
函數(shù)定義在include/trace/events/syscalls.h文件中
1)sys_enter的trace參數(shù)是id 和長度為6的數(shù)組。2)sys_exit的trace參數(shù)是兩個長整形數(shù) id 和ret。
找到了鉤子后,下一步就可以編寫鉤子處理代碼了:
1)定義map保存系統(tǒng)調(diào)用統(tǒng)計信息,在DEFINE_BPF_MAP聲明map的同時,也會生成刪,改,查的宏函數(shù),例如本例中會生成如下函數(shù)
bpf_pid_syscall_map_lookup_elem
bpf_pid_syscall_map_update_elem
bpf_pid_syscall_map_delete_elem
2)定義回調(diào)函數(shù)參數(shù)類型,需要參考前面的tracepoint的定義。3)指定監(jiān)聽的tracepoint事件。4)使用bpf_trace_printk函數(shù)打印debug信息,會直接打印信息到ftrace中。5)在map中查找指定key。6)更新指定的key的值。
3. 加載鉤子代碼
我們只需要把我們編譯出來的*.o文件push到手機的system/etc/bpf目錄下,重啟手機,系統(tǒng)會自動加載我們的鉤子文件,加載成功后會在 /sys/fs/bpf目錄下顯示我們定義的map及prog文件。
系統(tǒng)加載代碼在system/bpf/bpfloader中,代碼很簡單。
主要有如下操作:
1)在early-init階段向下面兩個節(jié)點寫1
– /proc/sys/net/core/bpf_jit_enable
使能eBPF JIT,當(dāng)內(nèi)核設(shè)定BPF_JIT_ALWAYS_ON的時候,默認(rèn)為1
– /proc/sys/net/core/bpf_jit_kallsyms
使特權(quán)用戶可以通過kallsyms節(jié)點讀取kernel的symbols
2)啟動bpfloader service
– 讀取system/etc/bpf目錄下的*.o文件,調(diào)用libbpf_android.so中的loadProg函數(shù)加載進(jìn)內(nèi)核。
– 生成相應(yīng)的/sys/fs/bpf/節(jié)點。
– 設(shè)置屬性bpf.progs_loaded為1
sys節(jié)點分為map節(jié)點和prog節(jié)點兩種, 分別為map_《filename》_《mapname》, prog_《filename》_《mapname》
下面是Android Q版本上的節(jié)點信息。
可以使用下面的命令調(diào)試動態(tài)加載
4. 用戶空間程序?qū)崿F(xiàn)
下面我們需要編寫用戶空間的顯示程序,本質(zhì)上就是在用戶態(tài)通過系統(tǒng)調(diào)用把BPF map給讀出來。
1)eBPF統(tǒng)計只有在調(diào)用bpf_attach_tracepoint只有才會起作用。bpf_attach_tracepoint是bcc里面的函數(shù),android將bcc的一部分內(nèi)容打包成了libbpf,放到了系統(tǒng)庫里面。2)取得map的fd, bpf_obj_get會直接調(diào)用bpf的系統(tǒng)調(diào)用。3)將fd包裝成BpfMap,android在BpfMap.h中定義了很多方便的函數(shù)。4)遍歷map回調(diào)函數(shù)。返回值必須是android::ok(在android的新版本中已經(jīng)進(jìn)行修改)。
5. 運行結(jié)果查看
直接在目錄下執(zhí)行mm,將編譯出來的bpf.o push到/system/etc/bpf目錄下,將統(tǒng)計程序push到/system/bin目錄下,重啟,看下結(jié)果。
前面的是pid, 后面的是系統(tǒng)調(diào)用次數(shù)。
至此,如何在android平臺使用eBPF實現(xiàn)統(tǒng)計系統(tǒng)中每個pid在一段時間內(nèi)系統(tǒng)調(diào)用的次數(shù)的功能就介紹完了。
此外還有很多技術(shù)細(xì)節(jié)沒有深入研究,不過畢竟只是初探,就先講到這里了,后續(xù)有時間再進(jìn)一步深入研究。研究的時間還是比較短,如果有任何錯誤的地方歡迎指正。
eBPF 簡史 (下篇):
https://cloud.tencent.com/developer/article/1006318
goolge原生使用ebpf的兩篇文章:
https://source.android.com/devices/architecture/kernel/bpf
https://source.android.com/devices/tech/datausage/ebpf-traffic-monitor
BCC:
https://github.com/iovisor/bcc
編輯:jq
-
函數(shù)
+關(guān)注
關(guān)注
3文章
4331瀏覽量
62622 -
代碼
+關(guān)注
關(guān)注
30文章
4788瀏覽量
68617 -
編譯
+關(guān)注
關(guān)注
0文章
657瀏覽量
32872 -
BPF
+關(guān)注
關(guān)注
0文章
25瀏覽量
4006
原文標(biāo)題:android平臺eBPF初探
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論