<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          eBPF 入門教程

          共 4351字,需瀏覽 9分鐘

           ·

          2021-10-18 02:30

          作者:Adrian Ratiu
          譯者:狄衛(wèi)華

          1. 前言

          有興趣了解更多關(guān)于 eBPF 技術(shù)的底層細(xì)節(jié)?那么請(qǐng)繼續(xù)移步,我們將深入研究 eBPF 的底層細(xì)節(jié),從其虛擬機(jī)機(jī)制和工具,到在遠(yuǎn)程資源受限的嵌入式設(shè)備上運(yùn)行跟蹤。

          注意:本系列博客文章將集中在 eBPF 技術(shù),因此對(duì)于我們來(lái)講,文中 BPF 和 eBPF 等同,可相互使用。BPF 名字/縮寫已經(jīng)沒(méi)有太大的意義,因?yàn)檫@個(gè)項(xiàng)目的發(fā)展遠(yuǎn)遠(yuǎn)超出了它最初的范圍。BPF 和 eBPF 在該系列中會(huì)交替使用。

          • 第 1 部分[1]第 2 部分[2] 為新人或那些希望通過(guò)深入了解 eBPF 技術(shù)棧的底層技術(shù)來(lái)進(jìn)一步了解 eBPF 技術(shù)的人提供了深入介紹。
          • 第 3 部分[3]是對(duì)用戶空間工具的概述,旨在提高生產(chǎn)力,建立在第 1 部分和第 2 部分中介紹的底層虛擬機(jī)機(jī)制之上。
          • 第 4 部分[4]側(cè)重于在資源有限的嵌入式系統(tǒng)上運(yùn)行 eBPF 程序,在嵌入式系統(tǒng)中完整的工具鏈技術(shù)棧(BCC/LLVM/python 等)是不可行的。我們將使用占用資源較小的嵌入式工具在 32 位 ARM 上交叉編譯和運(yùn)行 eBPF 程序。只對(duì)該部分感興趣的讀者可選擇跳過(guò)其他部分。
          • 第 5 部分[5]是關(guān)于用戶空間追蹤。到目前為止,我們的努力都集中在內(nèi)核追蹤上,所以是時(shí)候我們關(guān)注一下用戶進(jìn)程了。

          如有疑問(wèn)時(shí),可使用該流程圖:

          2. eBPF 是什么?

          eBPF 是一個(gè)基于寄存器的虛擬機(jī),使用自定義的 64 位 RISC 指令集,能夠在 Linux 內(nèi)核內(nèi)運(yùn)行即時(shí)本地編譯的 "BPF 程序",并能訪問(wèn)內(nèi)核功能和內(nèi)存的一個(gè)子集。這是一個(gè)完整的虛擬機(jī)實(shí)現(xiàn),不要與基于內(nèi)核的虛擬機(jī)(KVM)相混淆,后者是一個(gè)模塊,目的是使 Linux 能夠作為其他虛擬機(jī)的管理程序。eBPF 也是主線內(nèi)核的一部分,所以它不像其他框架那樣需要任何第三方模塊(LTTng[6]SystemTap[7]),而且?guī)缀跛械?Linux 發(fā)行版都默認(rèn)啟用。熟悉 DTrace 的讀者可能會(huì)發(fā)現(xiàn) DTrace/BPFtrace 對(duì)比[8]非常有用。

          在內(nèi)核內(nèi)運(yùn)行一個(gè)完整的虛擬機(jī)主要是考慮便利和安全。雖然 eBPF 程序所做的操作都可以通過(guò)正常的內(nèi)核模塊來(lái)處理,但直接的內(nèi)核編程是一件非常危險(xiǎn)的事情 - 這可能會(huì)導(dǎo)致系統(tǒng)鎖定、內(nèi)存損壞和進(jìn)程崩潰,從而導(dǎo)致安全漏洞和其他意外的效果,特別是在生產(chǎn)設(shè)備上(eBPF 經(jīng)常被用來(lái)檢查生產(chǎn)中的系統(tǒng)),所以通過(guò)一個(gè)安全的虛擬機(jī)運(yùn)行本地 JIT 編譯的快速內(nèi)核代碼對(duì)于安全監(jiān)控和沙盒、網(wǎng)絡(luò)過(guò)濾、程序跟蹤、性能分析和調(diào)試都是非常有價(jià)值的。部分簡(jiǎn)單的樣例可以在這篇優(yōu)秀的 eBPF 參考[9]中找到。

          基于設(shè)計(jì),eBPF 虛擬機(jī)和其程序有意地設(shè)計(jì)為不是圖靈完備的:即不允許有循環(huán)(正在進(jìn)行的工作是支持有界循環(huán)【譯者注:已經(jīng)支持有界循環(huán),#pragma unroll 指令】),所以每個(gè) eBPF 程序都需要保證完成而不會(huì)被掛起、所有的內(nèi)存訪問(wèn)都是有界和類型檢查的(包括寄存器,一個(gè) MOV 指令可以改變一個(gè)寄存器的類型)、不能包含空解引用、一個(gè)程序必須最多擁有 BPF_MAXINSNS 指令(默認(rèn) 4096)、"主"函數(shù)需要一個(gè)參數(shù)(context)等等。當(dāng) eBPF 程序被加載到內(nèi)核中,其指令被驗(yàn)證模塊解析為有向環(huán)狀圖,上述的限制使得正確性可以得到簡(jiǎn)單而快速的驗(yàn)證。

          譯者注:BPF_MAXINSNS 這個(gè)限制已經(jīng)被放寬至 100 萬(wàn)條指令(BPF_COMPLEXITY_LIMIT_INSNS),但是非特權(quán)執(zhí)行的 BPF 程序這個(gè)限制仍然會(huì)保留。

          歷史上,eBPF (cBPF) 虛擬機(jī)只在內(nèi)核中可用,用于過(guò)濾網(wǎng)絡(luò)數(shù)據(jù)包,與用戶空間程序沒(méi)有交互,因此被稱為 "伯克利數(shù)據(jù)包過(guò)濾器"【譯者注:早期的 BPF 實(shí)現(xiàn)被稱為經(jīng)典 cBPF】。從內(nèi)核 v3.18(2014 年)開始,該虛擬機(jī)也通過(guò) bpf() syscall[10]uapi/linux/bpf.h[11] 暴露在用戶空間,這導(dǎo)致其指令集在當(dāng)時(shí)被凍結(jié),成為公共 ABI,盡管后來(lái)仍然可以(并且已經(jīng))添加新指令。

          因?yàn)閮?nèi)核內(nèi)的 eBPF 實(shí)現(xiàn)是根據(jù) GPLv2 授權(quán)的,它不能輕易地被非 GPL 用戶重新分發(fā),所以也有一個(gè)替代的 Apache 授權(quán)的用戶空間 eBPF 虛擬機(jī)實(shí)現(xiàn),稱為 "uBPF"。撇開法律條文不談,基于用戶空間的實(shí)現(xiàn)對(duì)于追蹤那些需要避免內(nèi)核-用戶空間上下文切換成本的性能關(guān)鍵型應(yīng)用很有用。

          3. eBPF 是怎么工作的?

          eBPF 程序在事件觸發(fā)時(shí)由內(nèi)核運(yùn)行,所以可以被看作是一種函數(shù)掛鉤或事件驅(qū)動(dòng)的編程形式。從用戶空間運(yùn)行按需 eBPF 程序的價(jià)值較小,因?yàn)樗械陌葱栌脩粽{(diào)用已經(jīng)通過(guò)正常的非 VM 內(nèi)核 API 調(diào)用("syscalls")來(lái)處理,這里 VM 字節(jié)碼帶來(lái)的價(jià)值很小。事件可由 kprobes/uprobes、tracepoints、dtrace probes、socket 等產(chǎn)生。這允許在內(nèi)核和用戶進(jìn)程的指令中鉤住(hook)和檢查任何函數(shù)的內(nèi)存、攔截文件操作、檢查特定的網(wǎng)絡(luò)數(shù)據(jù)包等等。一個(gè)比較好的參考是 Linux 內(nèi)核版本對(duì)應(yīng)的 BPF 功能[12]

          如前所述,事件觸發(fā)了附加的 eBPF 程序的執(zhí)行,后續(xù)可以將信息保存至 map 和環(huán)形緩沖區(qū)(ringbuffer)或調(diào)用一些特定 API 定義的內(nèi)核函數(shù)的子集。一個(gè) eBPF 程序可以鏈接到多個(gè)事件,不同的 eBPF 程序也可以訪問(wèn)相同的 map 以共享數(shù)據(jù)。一個(gè)被稱為 "program array" 的特殊讀/寫 map 存儲(chǔ)了對(duì)通過(guò) bpf() 系統(tǒng)調(diào)用加載的其他 eBPF 程序的引用,在該 map 中成功的查找則會(huì)觸發(fā)一個(gè)跳轉(zhuǎn),而且并不返回到原來(lái)的 eBPF 程序。這種 eBPF 嵌套也有限制,以避免無(wú)限的遞歸循環(huán)。

          運(yùn)行 eBPF 程序的步驟:

          1. 用戶空間將字節(jié)碼和程序類型一起發(fā)送到內(nèi)核,程序類型決定了可以訪問(wèn)的內(nèi)核區(qū)域【譯者注:主要是 BPF 幫助函數(shù)的各種子集】。
          2. 內(nèi)核在字節(jié)碼上運(yùn)行驗(yàn)證器,以確保程序可以安全運(yùn)行(kernel/bpf/verifier.c)。
          3. 內(nèi)核將字節(jié)碼編譯為本地代碼,并將其插入(或附加到)指定的代碼位置。【譯者注:如果啟用了 JIT 功能,字節(jié)碼編譯為本地代碼】。
          4. 插入的代碼將數(shù)據(jù)寫入環(huán)形緩沖區(qū)或通用鍵值 map。
          5. 用戶空間從共享 map 或環(huán)形緩沖區(qū)中讀取結(jié)果值。

          map 和環(huán)形緩沖區(qū)結(jié)構(gòu)是由內(nèi)核管理的(就像管道和 FIFO 一樣),獨(dú)立于掛載的 eBPF 或訪問(wèn)它們的用戶程序。對(duì) map 和環(huán)形緩沖區(qū)結(jié)構(gòu)的訪問(wèn)是異步的,通過(guò)文件描述符和引用計(jì)數(shù)實(shí)現(xiàn),可確保只要有至少一個(gè)程序還在訪問(wèn),結(jié)構(gòu)就能夠存在。加載的 JIT 后代碼通常在加載其的用戶進(jìn)程終止時(shí)被刪除,盡管在某些情況下,它仍然可以在加載進(jìn)程的生命期之后繼續(xù)存在。

          為了方便編寫 eBPF 程序和避免進(jìn)行原始的 bpf()系統(tǒng)調(diào)用,內(nèi)核提供了方便的 libbpf 庫(kù)[13],包含系統(tǒng)調(diào)用函數(shù)包裝器,如bpf_load_program[14] 和結(jié)構(gòu)定義(如 bpf_map[15]),在 LGPL 2.1 和 BSD 2-Clause 下雙重許可,可以靜態(tài)鏈接或作為 DSO。內(nèi)核代碼也提供了一些使用 libbpf 簡(jiǎn)潔的例子,位于目錄 samples/bpf/[16] 中。

          4. 樣例學(xué)習(xí)

          內(nèi)核開發(fā)者非常可憐,因?yàn)閮?nèi)核是一個(gè)獨(dú)立的項(xiàng)目,因而沒(méi)有用戶空間諸如 Glibc、LLVM、JavaScript 和 WebAssembly 諸如此類的好東西! - 這就是為什么內(nèi)核中 eBPF 例子中會(huì)包含原始字節(jié)碼或通過(guò) libbpf 加載預(yù)組裝的字節(jié)碼文件。我們可以在 sock_example.c[17] 中看到這一點(diǎn),這是一個(gè)簡(jiǎn)單的用戶空間程序,使用 eBPF 來(lái)計(jì)算環(huán)回接口上統(tǒng)計(jì)接收到 ?TCP、UDP 和 ICMP 協(xié)議包的數(shù)量。

          我們跳過(guò)微不足道的的 main[18]open_raw_sock[19] 函數(shù),而專注于神奇的代碼 test_sock[20]

          static?int?test_sock(void)
          {
          ?int?sock?=?-1,?map_fd,?prog_fd,?i,?key;
          ?long?long?value?=?0,?tcp_cnt,?udp_cnt,?icmp_cnt;

          ?map_fd?=?bpf_create_map(BPF_MAP_TYPE_ARRAY,?sizeof(key),?sizeof(value),?256,?0);
          ?if?(map_fd?0)?{printf("failed?to?create?map'%s'\n",?strerror(errno));
          ??goto?cleanup;
          ?}

          ?struct?bpf_insn?prog[]?=?{BPF_MOV64_REG(BPF_REG_6,?BPF_REG_1),
          ??BPF_LD_ABS(BPF_B,?ETH_HLEN?+?offsetof(struct?iphdr,?protocol)?/*?R0?=?ip->proto?*/),
          ??BPF_STX_MEM(BPF_W,?BPF_REG_10,?BPF_REG_0,?-4),?/*?*(u32?*)(fp?-?4)?=?r0?*/
          ??BPF_MOV64_REG(BPF_REG_2,?BPF_REG_10),
          ??BPF_ALU64_IMM(BPF_ADD,?BPF_REG_2,?-4),?/*?r2?=?fp?-?4?*/
          ??BPF_LD_MAP_FD(BPF_REG_1,?map_fd),
          ??BPF_RAW_INSN(BPF_JMP?|?BPF_CALL,?0,?0,?0,?BPF_FUNC_map_lookup_elem),
          ??BPF_JMP_IMM(BPF_JEQ,?BPF_REG_0,?0,?2),
          ??BPF_MOV64_IMM(BPF_REG_1,?1),?/*?r1?=?1?*/
          ??BPF_RAW_INSN(BPF_STX?|?BPF_XADD?|?BPF_DW,?BPF_REG_0,?BPF_REG_1,?0,?0),?/*?xadd?r0?+=?r1?*/
          ??BPF_MOV64_IMM(BPF_REG_0,?0),?/*?r0?=?0?*/
          ??BPF_EXIT_INSN(),};
          ?size_t?insns_cnt?=?sizeof(prog)?/?sizeof(struct?bpf_insn);

          ?prog_fd?=?bpf_load_program(BPF_PROG_TYPE_SOCKET_FILTER,?prog,?insns_cnt,
          ???????"GPL",?0,?bpf_log_buf,?BPF_LOG_BUF_SIZE);
          ?if?(prog_fd?0)?{printf("failed?to?load?prog'%s'\n",?strerror(errno));
          ??goto?cleanup;
          ?}

          ?sock?=?open_raw_sock("lo");

          ?if?(setsockopt(sock,?SOL_SOCKET,?SO_ATTACH_BPF,?&prog_fd,?sizeof(prog_fd))?0)?{printf("setsockopt?%s\n",?strerror(errno));
          ??goto?cleanup;
          ?}

          首先,通過(guò) libbpf API 創(chuàng)建一個(gè) BPF map,該行為就像一個(gè)最大 256 個(gè)元素的固定大小的數(shù)組。按 IPROTO_*[21] 定義的鍵索引網(wǎng)絡(luò)協(xié)議(2 字節(jié)的 word),值代表各自的數(shù)據(jù)包計(jì)數(shù)(4 字節(jié)大小)。除了數(shù)組,eBPF 映射還實(shí)現(xiàn)了其他數(shù)據(jù)結(jié)構(gòu)類型[22],如棧或隊(duì)列。

          接下來(lái),eBPF 的字節(jié)碼指令數(shù)組使用方便的內(nèi)核宏[23]進(jìn)行定義。在這里,我們不會(huì)討論字節(jié)碼的細(xì)節(jié)(這將在第 2 部分描述機(jī)器后進(jìn)行)。更高的層次上,字節(jié)碼從數(shù)據(jù)包緩沖區(qū)中讀取協(xié)議字,在 map 中查找,并增加特定的數(shù)據(jù)包計(jì)數(shù)。

          然后 BPF 字節(jié)碼被加載到內(nèi)核中,并通過(guò) libbpf 的 bpf_load_program 返回 fd 引用來(lái)驗(yàn)證正確/安全。調(diào)用指定了 eBPF 程序類型[24],這決定了它可以訪問(wèn)哪些內(nèi)核子集。因?yàn)闃永且粋€(gè) SOCKET_FILTER 類型,因此提供了一個(gè)指向當(dāng)前網(wǎng)絡(luò)包的參數(shù)。最后,eBPF 的字節(jié)碼通過(guò)套接字層被附加到一個(gè)特定的原始套接字上,之后在原始套接字上接受到的每一個(gè)數(shù)據(jù)包運(yùn)行 eBPF 字節(jié)碼,無(wú)論協(xié)議如何。

          剩余的工作就是讓用戶進(jìn)程開始輪詢共享 map 的數(shù)據(jù)。

          ?for?(i?=?0;?i?10;?i++)?{
          ??key?=?IPPROTO_TCP;
          ??assert(bpf_map_lookup_elem(map_fd,?&key,?&tcp_cnt)?==?0);

          ??key?=?IPPROTO_UDP;
          ??assert(bpf_map_lookup_elem(map_fd,?&key,?&udp_cnt)?==?0);

          ??key?=?IPPROTO_ICMP;
          ??assert(bpf_map_lookup_elem(map_fd,?&key,?&icmp_cnt)?==?0);

          ??printf("TCP?%lld?UDP?%lld?ICMP?%lld?packets\n",
          ?????????tcp_cnt,?udp_cnt,?icmp_cnt);
          ??sleep(1);
          ?}
          }

          5. 總結(jié)

          第 1 部分介紹了 eBPF 的基礎(chǔ)知識(shí),我們通過(guò)如何加載字節(jié)碼和與 eBPF 虛擬機(jī)通信的例子進(jìn)行了講述。由于篇幅限制,編譯和運(yùn)行例子作為留給讀者的練習(xí)。我們也有意不去分析具體的 eBPF 字節(jié)碼指令,因?yàn)檫@將是第 2 部分的重點(diǎn)。在我們研究的例子中,用戶空間通過(guò) libbpf 直接用 C 語(yǔ)言從內(nèi)核虛擬機(jī)中讀取 eBPF map 值(使用 10 次 1 秒的睡眠!),這很笨重,而且容易出錯(cuò),而且很快就會(huì)變得很復(fù)雜,所以在第 3 部分,我們將研究更高級(jí)別的工具,通過(guò)腳本或特定領(lǐng)域的語(yǔ)言自動(dòng)與虛擬機(jī)交互。

          繼續(xù)閱讀(eBPF 概述,第 2 部分:機(jī)器和字節(jié)碼)[25]...

          引用鏈接

          [1]

          第 1 部分: https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/

          [2]

          第 2 部分: https://www.collabora.com/news-and-blog/blog/2019/04/15/an-ebpf-overview-part-2-machine-and-bytecode/

          [3]

          第 3 部分: https://www.collabora.com/news-and-blog/blog/2019/04/26/an-ebpf-overview-part-3-walking-up-the-software-stack/

          [4]

          第 4 部分: https://www.collabora.com/news-and-blog/blog/2019/05/06/an-ebpf-overview-part-4-working-with-embedded-systems/

          [5]

          第 5 部分: https://www.collabora.com/news-and-blog/blog/2019/05/14/an-ebpf-overview-part-5-tracing-user-processes/

          [6]

          LTTng: https://lttng.org/docs/v2.10/#doc-lttng-modules

          [7]

          SystemTap: https://kernelnewbies.org/SystemTap

          [8]

          DTrace/BPFtrace 對(duì)比: http://www.brendangregg.com/blog/2018-10-08/dtrace-for-linux-2018.html

          [9]

          eBPF 參考: http://www.brendangregg.com/ebpf.html

          [10]

          bpf() syscall: https://github.com/torvalds/linux/blob/v4.20/tools/lib/bpf

          [11]

          uapi/linux/bpf.h: https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h

          [12]

          Linux 內(nèi)核版本對(duì)應(yīng)的 BPF 功能: https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md

          [13]

          libbpf 庫(kù): https://github.com/torvalds/linux/blob/v4.20/tools/lib/bpf

          [14]

          bpf_load_program: https://github.com/torvalds/linux/blob/v4.20/tools/lib/bpf/bpf.c#L214

          [15]

          bpf_map: https://github.com/torvalds/linux/blob/v4.20/tools/lib/bpf/libbpf.c#L157

          [16]

          samples/bpf/: https://github.com/torvalds/linux/blob/v4.20/samples/bpf/

          [17]

          sock_example.c: https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c

          [18]

          main: https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L98

          [19]

          open_raw_sock: https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.h#L13

          [20]

          test_sock: https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L35

          [21]

          IPROTO_*: https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/in.h#L28

          [22]

          其他數(shù)據(jù)結(jié)構(gòu)類型: https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h#L113

          [23]

          內(nèi)核宏: https://github.com/torvalds/linux/blob/v4.20/samples/bpf/bpf_insn.h

          [24]

          程序類型: https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h#L138

          [25]

          繼續(xù)閱讀(eBPF 概述,第 2 部分:機(jī)器和字節(jié)碼): https://www.collabora.com/news-and-blog/blog/2019/04/15/an-ebpf-overview-part-2-machine-and-bytecode/

          瀏覽 57
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91丝袜视频 | 射了好多别动哦初恋哥vs | 欧美三级韩国三级日本三斤在线观看en | 狠狠撸五月天 | 亚洲成人免费AV |