- 深入理解eBPF與可觀測(cè)性
- 毛文安 鄭昱笙 程書(shū)意 廖肇燕
- 1594字
- 2025-06-09 17:11:28
1.3.2 eBPF的JIT編譯原理
eBPF程序可以通過(guò)兩種方式運(yùn)行:解釋器(Interpreter)和JIT編譯器。
1)解釋器:可用于直接執(zhí)行eBPF程序。內(nèi)核將解釋器視為一種特殊的虛擬機(jī),它可以逐條解釋和執(zhí)行eBPF指令。解釋器的優(yōu)點(diǎn)是簡(jiǎn)單、易于實(shí)現(xiàn)和調(diào)試,它不需要進(jìn)行額外的編譯。然而,解釋器的執(zhí)行速度相對(duì)較慢,因?yàn)樗枰獙?duì)每個(gè)指令進(jìn)行解釋和執(zhí)行。
2)JIT編譯器:JIT編譯器的優(yōu)點(diǎn)是速度更快,因?yàn)樗鼘BPF程序編譯為本地機(jī)器碼,在每個(gè)指令執(zhí)行時(shí)才進(jìn)行解釋。此外,JIT編譯器還可以進(jìn)行各種優(yōu)化,提高程序的執(zhí)行效率。然而,JIT編譯器的實(shí)現(xiàn)較為復(fù)雜,需要考慮安全性和兼容性等因素。
在實(shí)際應(yīng)用中,eBPF程序通常會(huì)根據(jù)需求和場(chǎng)景選擇使用解釋器還是JIT編譯器。解釋器適用于簡(jiǎn)單的eBPF程序或快速原型開(kāi)發(fā)場(chǎng)景,而JIT編譯器則適用于要求性能和效率的場(chǎng)景。需要注意的是,eBPF解釋器和JIT編譯器并不是互斥的,可以在同一系統(tǒng)中同時(shí)存在。在運(yùn)行eBPF程序時(shí),內(nèi)核會(huì)根據(jù)具體情況選擇使用解釋器或JIT編譯器來(lái)執(zhí)行程序。當(dāng)內(nèi)核開(kāi)啟CONFIG_BPF_JIT_ALWAYS_ON選項(xiàng)時(shí),eBPF程序會(huì)進(jìn)行JIT編譯,反之則會(huì)使用eBPF解釋器運(yùn)行eBPF程序。
圖1-14是eBPF JIT的主要流程,大致分為5個(gè)步驟。

圖1-14 eBPF JIT的主要流程
下面對(duì)這5個(gè)步驟進(jìn)行簡(jiǎn)要說(shuō)明。
1.生成prologue
在計(jì)算機(jī)程序中,prologue(序言)是指程序的開(kāi)頭部分,通常包括一系列指令,用于設(shè)置函數(shù)調(diào)用所需的狀態(tài)。在函數(shù)調(diào)用開(kāi)始之前,程序需要先執(zhí)行prologue中的指令,以確保函數(shù)調(diào)用所需的環(huán)境和狀態(tài)已經(jīng)被正確地構(gòu)建與初始化。prologue通常包括以下指令。
1)push rbp:將當(dāng)前函數(shù)的基址指針入棧,以保存調(diào)用函數(shù)之前的rbp值。
2)mov rbp,rsp:將當(dāng)前函數(shù)的棧頂指針復(fù)制到rbp寄存器中,以作為基址指針使用。
3)sub rsp,n:為當(dāng)前函數(shù)分配一定的棧空間,用于存儲(chǔ)局部變量和函數(shù)參數(shù)。
生成prologue的示例代碼如下所示。

2. callee-saved寄存器壓棧
在函數(shù)調(diào)用過(guò)程中,callee-saved和caller-saved是兩種常見(jiàn)的寄存器保存策略。callee-saved寄存器是指在函數(shù)調(diào)用前,被調(diào)用函數(shù)(callee)需要將其使用的一些寄存器值保存在棧中,以保證在函數(shù)返回后能夠正確恢復(fù)原始的寄存器狀態(tài)。callee-saved寄存器通常是被調(diào)用函數(shù)自己使用的,不會(huì)影響到調(diào)用函數(shù)(caller)的寄存器狀態(tài)。caller-saved寄存器則是指在函數(shù)調(diào)用前,調(diào)用函數(shù)需要將其使用的一些寄存器值保存在棧中,以保證在函數(shù)返回后能夠正確恢復(fù)原始的寄存器狀態(tài)。
eBPF的callee-saved寄存器是r6~r9,對(duì)應(yīng)的x86-64的callee-saved寄存器主要如下。
1)RBX:基地址寄存器,保存數(shù)組和數(shù)據(jù)結(jié)構(gòu)的基地址。
2)RBP:基指針寄存器,保存棧幀的基地址。
3)r12~r15:通用寄存器,用于存儲(chǔ)臨時(shí)變量和中間結(jié)果。
但是,我們并未使用RBP、r12寄存器,因此可以不壓棧。RBP寄存器相當(dāng)于eBPF里面的r10寄存器,它是只讀的,所以不需要保存。下面的代碼片段是callee-saved寄存器壓棧的內(nèi)核源碼。

3.指令集轉(zhuǎn)換
eBPF指令集的設(shè)計(jì)初衷是盡量貼近底層機(jī)器指令集,因此在JIT編譯過(guò)程中,能夠相對(duì)順利地將每條eBPF指令轉(zhuǎn)換為相應(yīng)的機(jī)器指令。這種指令轉(zhuǎn)換過(guò)程與編譯器領(lǐng)域中的指令選擇相似,即將編譯器的中間代碼映射到目標(biāo)指令集。在編譯器中,指令選擇通常采用樹(shù)模式匹配的方法。該方法考慮多種可能的候選指令序列,并選擇預(yù)期代價(jià)最低的序列。相比之下,eBPF的JIT實(shí)現(xiàn)要簡(jiǎn)單得多。如有興趣深入了解,讀者可以查閱內(nèi)核中的do_jit函數(shù),以獲取更多細(xì)節(jié)。
4. callee-saved寄存器出棧
在程序結(jié)束時(shí)(遇到BPF_EXIT指令),需要恢復(fù)callee-saved寄存器的值,即將callee-saved寄存器出棧。和callee-saved寄存器入棧時(shí)一樣,需要按照相反的順序?qū)allee-saved寄存器彈出棧,即以r15、r14、r13和RBX的順序出棧。下面的代碼片段是callee-saved寄存器出棧的內(nèi)核源碼。


5.生成epilogue
epilogue(尾聲)和prologue在功能上是相對(duì)的,主要用于恢復(fù)調(diào)用者(caller)的狀態(tài)。生成epilogue需要使用兩條主要指令:leave和ret。leave指令實(shí)際上執(zhí)行了“mov rsp,rbp”和“pop rbp”兩條指令,其作用是將棧指針rsp設(shè)置為棧幀基指針rbp的值,并彈出棧幀基指針rbp,以恢復(fù)調(diào)用者的棧幀。ret指令則負(fù)責(zé)從棧中彈出棧頂元素,將其作為返回地址,并跳轉(zhuǎn)至調(diào)用者的下一條指令。
下面的代碼片段展示了leave和ret指令的大致插入位置,可以看到:當(dāng)遇到BPF_EXIT指令時(shí),JIT編譯器會(huì)生成epilogue,即插入leave和ret指令。

- 電腦組裝與系統(tǒng)安裝
- Mastering ElasticSearch
- Linux系統(tǒng)文件安全實(shí)戰(zhàn)全攻略
- Windows Server 2012 Hyper-V:Deploying the Hyper-V Enterprise Server Virtualization Platform
- 阿里云數(shù)字新基建系列:云原生操作系統(tǒng)Kubernetes
- Ganglia系統(tǒng)監(jiān)控
- 奔跑吧 Linux內(nèi)核(入門(mén)篇)
- 嵌入式實(shí)時(shí)操作系統(tǒng)μC/OS原理與實(shí)踐
- STM32庫(kù)開(kāi)發(fā)實(shí)戰(zhàn)指南:基于STM32F4
- Linux基礎(chǔ)使用與案例
- 分布式系統(tǒng)設(shè)計(jì)實(shí)踐
- Learn SwiftUI
- Docker容器技術(shù)與應(yīng)用
- Raspberry Pi入門(mén)指南
- 樹(shù)莓派+傳感器:創(chuàng)建智能交互項(xiàng)目的實(shí)用方法、工具及最佳實(shí)踐