抽絲剝繭:從 Linux 源碼探索 eBPF 的實現(xiàn)
去年學習 eBPF,分享過 幾篇 eBPF 方面的學習筆記[1],都是面向 eBPF 的應用。為了準備下一篇文章,這次決定從 Linux 源碼入手,深入了解 eBPF 的工作原理。因此這篇又是一篇學習筆記,假如你對 eBPF 的工作原理也感興趣,不如跟隨我的腳步一起。文章中若有任何問題,請不吝賜教。
這里不會再對 eBPF 進行過多的介紹,可以參考我的另一篇 使用 eBPF 技術實現(xiàn)更快的網絡數(shù)據(jù)包傳輸[2],結合 追蹤 Kubernetes 中的數(shù)據(jù)包[3] 可以了解 eBPF 的基本內容以及其在網絡加速方面的應用。
接下來我們還是使用 eBPF sockops[4] 中的程序 bpf_sockops[5] 為例, 配合 Linux v6.8[6] 源碼探索 eBPF 的工作原理。
注:由于公眾號排版問題閱讀可能不友好,可以點擊閱讀原文跳轉到博客閱讀。
圖片
BPF 程序操作
在 load.sh[7] 腳本中,完成了程序的加載和掛載操作,下面的命令使用 bpftool[8] 分別完成 BPF 程序的加載和掛載。
#load
sudo bpftool prog load bpf_sockops.o "/sys/fs/bpf/bpf_sockop"
#attach
sudo bpftool cgroup attach "/sys/fs/cgroup/unified/" sock_ops pinned "/sys/fs/bpf/bpf_sockop"
這里 bpftool 是對內核函數(shù) bpf() 封裝的命令行工具,用于管理和操作 BPF 程序與 Map。
加載
sudo bpftool prog load bpf_sockops.o "/sys/fs/bpf/bpf_sockop"
命令 bpftool prog load 將 bpf_sockops.o 加載到路徑 /sys/fs/bpf/bpf_sockop 中。
bpftool 對 BPF 程序的加載是由調用 `bpf()`[9] 指定命令 BPF_PROG_LOAD 并傳入 加載選項`bpf_prog_load_opts`[10] 來完成的:
syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr))
- syscall bpf()[11] bpf 系統(tǒng)函數(shù)
- bpf_prog_load[13] 為程序分配內存、初始化、檢查證書、運行 verifier、創(chuàng)建文件描述符(fd)等
- \_\_sys_bpf[12] 執(zhí)行 bpf 命令 BPF_PROG_LOAD
加載成功后的程序,然后就可以進行掛載了。
掛載
sudo bpftool cgroup attach "/sys/fs/cgroup/unified/" sock_ops pinned "/sys/fs/bpf/bpf_sockop"
命令 bpftool cgroup attach 將加載(pin 到文件系統(tǒng)中)的程序 /sys/fs/bpf/bpf_sockop 掛載到 cgroup /sys/fs/cgroup/unified/,掛載的類型為 sock_ops。這個 sock_ops 是 bpftool 所使用的庫 libbpf 定義,也被是 ELF 部件名,對應著 BPF 程序類型[14] BPF_PROG_TYPE_SOCK_OPS,掛載類型[15] 為 BPF_CGROUP_SOCK_OPS。
在 eBPF 編程中,ELF(Executable and Linkable Format)文件用于存儲編譯后的 eBPF 程序和相關數(shù)據(jù)。ELF 文件由多個部分(sections)組成,每個部分包含不同類型的信息,比如程序代碼、符號表、調試信息等。
libbpf 類型 sock_ops => BPF 程序類型 BPF_PROG_TYPE_SOCK_OPS => 掛載類型 BPF_CGROUP_SOCK_OPS,對應到程序 bpf_sockops.c 中部件名(__section)為 sockops 的代碼塊。
關于 sock_ops 掛載點:
sock_ops 通常指的是在 Linux 內核中處理套接字操作的一系列函數(shù)和操作。
sock_ops 具體可以包括一系列的操作,如創(chuàng)建套接字、綁定套接字到特定地址和端口、監(jiān)聽來自其他套接字的連接請求、接受連接請求、發(fā)送和接收數(shù)據(jù)、以及關閉套接字等。這些操作通常通過一組預定義的 API 來提供,例如 POSIX 套接字 API,它定義了一系列函數(shù),如 socket()、bind()、listen()、accept()、send()、recv() 和 close() 等,供應用程序調用。
這次 bpftool 是通過 bpf() 執(zhí)行執(zhí)行 BPF_PROG_ATTACH 并傳入 掛載選項 `bpf_prog_attach_opts`[16] 來完成的。
syscall(__NR_bpf, BPF_PROG_ATTACH, &attr, sizeof(attr))
- syscall bpf()[17] bpf 系統(tǒng)函數(shù)
cgroup_bpf_prog_attach[19]
\_\_cgroup_bpf_attach[21]
bpf_prog_put[22] 檢查 cgroup 上是否存在相同掛載類型的程序,如果存在,則進行替換。
static_branch_inc[23] 如果不存在,則將 cgroup_bpf_enabled_key 計數(shù)器中,該掛載類型的計數(shù) +1。
cgroup_bpf_prog_attach[20]
bpf_prog_attach[18]
cgroup_bpf_enabled_key 特定類型 cgroup BPF 程序的計數(shù)器。
!!! 在運行時,會用到該計數(shù)器。
到此,我們已經成功將程序掛載到 cgroup 的 sock_ops 上。
套接字操作 sock_ops
套接字的操作很多,這里以連接建立過程中服務端 accept 操作為例。
圖片
依然是從系統(tǒng)調用 accept 開始。
- accept[24]
do_accept[26] 此處 ops->accept() 中的 ops 對應著 proto_ops inet_stream_ops[27] 有狀態(tài)的 socket(如 TCP) 的相關操作
inet_accept[29] sk1->sk_prot->accept() 這里的 sk_prot 提供了 TCP 協(xié)議 `proto tcp_prot`[30] 的具體操作
tcp_v4_rcv[34] 此時第一次握手剛開始,sock(套接字在內核協(xié)議棧這層的體現(xiàn)) 的狀態(tài)還是 TCP_LISTEN
tcp_v4_do_rcv[35] 在連接成功建立前,每次握手都會對狀態(tài)進行處理。
tcp_init_transfer[37] sock 的狀態(tài)被設置為 BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB,開始進行數(shù)據(jù)傳輸。
BPF_CGROUP_RUN_PROG_SOCK_OPS[39] 執(zhí)行掛載類型為 BPF_CGROUP_SOCK_OPS 的 BPF 程序。
bpf_skops_established[38]
tcp_rcv_state_process[36] 我們直接看最后一次握手,也就是收到客戶端的 ACK,完成與客戶端連接的建立。
tcp_prot.accept[31]
inet_csk_accept 開始處理三次握手,調用 TCP 協(xié)議的實現(xiàn)來處理。inet_init[32] 注冊了 IPPROTO_TCP 也就是 TCP 協(xié)議的實現(xiàn),也就是 net_protocol tcp_protocol[33],其 handler 為 tcp_v4_rcv。
inet_stream_ops.accept[28]
\_\_sys_accept4_file[25]
BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB 是 socket.accept() 接受連接請求并完成連接建立的操作符,也是眾多 `sock_ops` 操作符[40] 中的一個。這些操作符,可以被看作是 事件 Event[41],程序的觸發(fā)則是由事件驅動的。例如:
- 如果客戶端發(fā)起連接請求并完成三次握手后的操作符是 BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB;
- 套接字進入監(jiān)聽狀態(tài)時的操作符是 BPF_SOCK_OPS_TCP_LISTEN_CB;
- 數(shù)據(jù)被確認 BPF_SOCK_OPS_DATA_ACK_CB
- TCP 狀態(tài)改變 BPF_SOCK_OPS_STATE_CB
最后就是 BPF 程序的執(zhí)行了,不多做贅述,有興趣的看這里的 分析[42]。
參考資料
[1] 幾篇 eBPF 方面的學習筆記: https://atbug.com/search?s=ebpf
[2] 使用 eBPF 技術實現(xiàn)更快的網絡數(shù)據(jù)包傳輸: https://atbug.com/accelerate-network-packets-transmission/
[3] 追蹤 Kubernetes 中的數(shù)據(jù)包: https://atbug.com/tracing-network-packets-in-kubernetes/
[4] eBPF sockops: https://github.com/addozhang/ebpf-sockops/
[5] bpf_sockops: https://github.com/addozhang/ebpf-sockops/blob/master/bpf_sockops.c#L28
[6] Linux v6.8: https://github.com/torvalds/linux/tree/v6.8
[7] load.sh: https://github.com/addozhang/ebpf-sockops/blob/master/load.sh
[8] bpftool: https://github.com/libbpf/bpftool
[9] bpf(): https://github.com/torvalds/linux/blob/715d82ba636cb3629a6e18a33bb9dbe53f9936ee/kernel/bpf/syscall.c#L5559
[10] 加載選項bpf_prog_load_opts: https://github.com/torvalds/linux/blob/05c31b4ab20527c4d1695130aaecc54ef59a0e54/tools/lib/bpf/bpf.h#L64
[11] syscall bpf(): https://github.com/torvalds/linux/blob/715d82ba636cb3629a6e18a33bb9dbe53f9936ee/kernel/bpf/syscall.c#L5559
[12] __sys_bpf: https://github.com/torvalds/linux/blob/715d82ba636cb3629a6e18a33bb9dbe53f9936ee/kernel/bpf/syscall.c#L5456
[13] bpf_prog_load: https://github.com/torvalds/linux/blob/715d82ba636cb3629a6e18a33bb9dbe53f9936ee/kernel/bpf/syscall.c#L2595
[14] BPF 程序類型: https://github.com/torvalds/linux/blob/d17aff807f845cf93926c28705216639c7279110/tools/include/uapi/linux/bpf.h#L964
[15] 掛載類型: https://github.com/torvalds/linux/blob/d17aff807f845cf93926c28705216639c7279110/tools/include/uapi/linux/bpf.h#L1000
[16] 掛載選項 bpf_prog_attach_opts: https://github.com/torvalds/linux/blob/05c31b4ab20527c4d1695130aaecc54ef59a0e54/tools/lib/bpf/bpf.h#L321C8-L321C28
[17] syscall bpf(): https://github.com/torvalds/linux/blob/715d82ba636cb3629a6e18a33bb9dbe53f9936ee/kernel/bpf/syscall.c#L5466
[18] bpf_prog_attach: https://github.com/torvalds/linux/blob/715d82ba636cb3629a6e18a33bb9dbe53f9936ee/kernel/bpf/syscall.c#L3936
[19] cgroup_bpf_prog_attach: https://github.com/torvalds/linux/blob/v6.8/kernel/bpf/cgroup.c#L1130
[20] cgroup_bpf_prog_attach: https://github.com/torvalds/linux/blob/v6.8/kernel/bpf/cgroup.c#L733
[21] __cgroup_bpf_attach: https://github.com/torvalds/linux/blob/v6.8/kernel/bpf/cgroup.c#L607
[22] bpf_prog_put: https://github.com/torvalds/linux/blob/v6.8/kernel/bpf/cgroup.c#L700
[23] static_branch_inc: https://github.com/torvalds/linux/blob/v6.8/kernel/bpf/cgroup.c#L702
[24] accept: https://github.com/torvalds/linux/blob/v6.8/net/socket.c#L2016
[25] __sys_accept4_file: https://github.com/torvalds/linux/blob/v6.8/net/socket.c#L1969
[26] do_accept: https://github.com/torvalds/linux/blob/v6.8/net/socket.c#L1929
[27] proto_ops inet_stream_ops: https://github.com/torvalds/linux/blob/eef00a82c568944f113f2de738156ac591bbd5cd/net/ipv4/af_inet.c#L1051
[28] inet_stream_ops.accept: https://github.com/torvalds/linux/blob/eef00a82c568944f113f2de738156ac591bbd5cd/net/ipv4/af_inet.c#L1058
[29] inet_accept: https://github.com/torvalds/linux/blob/eef00a82c568944f113f2de738156ac591bbd5cd/net/ipv4/af_inet.c#L780
[30] proto tcp_prot: https://github.com/torvalds/linux/blob/da7dfaa6d6f731c30eca6ffa808b83634d43e26f/net/ipv4/tcp_ipv4.c#L3314
[31] tcp_prot.accept: https://github.com/torvalds/linux/blob/da7dfaa6d6f731c30eca6ffa808b83634d43e26f/net/ipv4/tcp_ipv4.c#L3314
[32] inet_init: https://github.com/torvalds/linux/blob/eef00a82c568944f113f2de738156ac591bbd5cd/net/ipv4/af_inet.c#L1997
[33] net_protocol tcp_protocol: https://github.com/torvalds/linux/blob/eef00a82c568944f113f2de738156ac591bbd5cd/net/ipv4/af_inet.c#L1754
[34] tcp_v4_rcv: https://github.com/torvalds/linux/blob/da7dfaa6d6f731c30eca6ffa808b83634d43e26f/net/ipv4/tcp_ipv4.c#L2319
[35] tcp_v4_do_rcv: https://github.com/torvalds/linux/blob/da7dfaa6d6f731c30eca6ffa808b83634d43e26f/net/ipv4/tcp_ipv4.c#L1929
[36] tcp_rcv_state_process: https://github.com/torvalds/linux/blob/v6.8/net/ipv4/tcp_input.c#L6724
[37] tcp_init_transfer: https://github.com/torvalds/linux/blob/v6.8/net/ipv4/tcp_input.c#L6208
[38] bpf_skops_established: https://github.com/torvalds/linux/blob/v6.8/net/ipv4/tcp_input.c#L192
[39] BPF_CGROUP_RUN_PROG_SOCK_OPS: https://github.com/torvalds/linux/blob/v6.8/include/linux/bpf-cgroup.h#L350
[40] sock_ops 操作符: sock_ops
[41] 事件 Event: https://atbug.com/accelerate-network-packets-transmission/#事件驅動
[42] 分析: https://atbug.com/accelerate-network-packets-transmission/#實現(xiàn)