作者簡(jiǎn)介
趙亞楠,攜程資深架構(gòu)師,負(fù)責(zé)攜程云平臺(tái)網(wǎng)絡(luò)虛擬化、云原生安全、內(nèi)核等基礎(chǔ)設(shè)施研發(fā)工作。
譯者序本文翻譯自 BPF 核心開發(fā)者 Andrii Nakryiko 2020 的一篇文章:BPF ring buffer。
文章介紹了 BPF ring buffer 解決的問題及背后的設(shè)計(jì),并給出了一些代碼示例和內(nèi)核 patch 鏈接,深度和廣度兼?zhèn)洌菍W(xué)習(xí) ring buffer 的極佳參考。
由于譯者水平有限,本文不免存在遺漏或錯(cuò)誤之處。如有疑問,請(qǐng)查閱原文。
目錄
譯者序1 ringbuf 相比 perfbuf 的改進(jìn) 1.1 降低內(nèi)存開銷(memory overhead) 1.2 保證事件順序(event ordering) 1.3 減少數(shù)據(jù)復(fù)制(wasted data copy)2 ringbuf 使用場(chǎng)景和性能 2.1 常規(guī)場(chǎng)景 2.2 高吞吐場(chǎng)景 2.3 不可掩碼中斷(non-maskable interrupt)場(chǎng)景 2.4 小結(jié)3 示例程序(show me the code) 3.1 perfbuf 示例 內(nèi)核 BPF 程序 用戶空間程序 3.2 ringbuf 示例 內(nèi)核 BPF 程序 用戶空間程序 3.3 ringbuf reserve/commit API 示例 原理 限制 內(nèi)核 BPF 程序 用戶空間程序4 ringbuf 事件通知控制 4.1 事件通知開銷 4.2 perbuf 解決方式 4.3 ringbuf 解決方式5 總結(jié)其他相關(guān)資料(譯注)
以下是譯文。
很多場(chǎng)景下,BPF 程序都需要將數(shù)據(jù)發(fā)送到用戶空間(userspace), BPF perf buffer(perfbuf)是目前這一過程的事實(shí)標(biāo)準(zhǔn),但它存在一些問題,例如 浪費(fèi)內(nèi)存(因?yàn)槠?per-CPU 設(shè)計(jì))、事件順序無(wú)法保證等。
作為改進(jìn),內(nèi)核 5.8 引入另一個(gè)新的 BPF 數(shù)據(jù)結(jié)構(gòu):BPF ring buffer(環(huán)形緩沖區(qū),ringbuf),
-
相比 perf buffer,它內(nèi)存效率更高、保證事件順序,性能也不輸前者;
-
在使用上,既提供了與 perf buffer 類似的 API ,以方便用戶遷移;又提供了一套新的reserve/commit API(先預(yù)留再提交),以實(shí)現(xiàn)更高性能。
此外,實(shí)驗(yàn)與真實(shí)環(huán)境的壓測(cè)結(jié)果都表明,從 BPF 程序發(fā)送數(shù)據(jù)給用戶空間時(shí), 應(yīng)該首選 BPF ring buffer。
1.ringbuf 相比perfbuf的改進(jìn)
perfbuf 是 per-CPU 環(huán)形緩沖區(qū)(circular buffers),能實(shí)現(xiàn)高效的 “內(nèi)核-用戶空間”數(shù)據(jù)交互,在實(shí)際中也非常有用,但 per-CPU 的設(shè)計(jì) 導(dǎo)致兩個(gè)嚴(yán)重缺陷:
-
內(nèi)存使用效率低下(inefficient use of memory)
-
事件順序無(wú)法保證(event re-ordering)
因此內(nèi)核 5.8 引入了 ringbuf 來解決這個(gè)問題。ringbuf 是一個(gè)“多生產(chǎn)者、單消費(fèi)者”(multi-producer, single-consumer,MPSC) 隊(duì)列,可安全地在多個(gè) CPU 之間共享和操作。perfbuf 支持的一些功能它都支持,包括,
-
可變長(zhǎng)數(shù)據(jù)(variable-length data records);
-
通過 memory-mapped region 來高效地從 userspace 讀數(shù)據(jù),避免內(nèi)存復(fù)制或系統(tǒng)調(diào)用;
-
支持 epoll notifications 和 busy-loop 兩種獲取數(shù)據(jù)方式。
此外,它還解決了 perfbuf 的下列問題:
-
可變長(zhǎng)數(shù)據(jù)(variable-length data records);
-
通過 memory-mapped region 來高效地從 userspace 讀數(shù)據(jù),避免內(nèi)存復(fù)制或系統(tǒng)調(diào)用;
-
支持 epoll notifications 和 busy-loop 兩種獲取數(shù)據(jù)方式。
下面具體來看。
1.1 降低內(nèi)存開銷(memory overhead)
perfbuf 為每個(gè) CPU 分配一個(gè)獨(dú)立的緩沖區(qū),這意味著開發(fā)者通常需要 在內(nèi)存效率和數(shù)據(jù)丟失之間做出折中:
-
越大的 per-CPU buffer 越能避免丟數(shù)據(jù),但也意味著大部分時(shí)間里,大部分內(nèi)存都是浪費(fèi)的;
-
盡量小的 per-CPU buffer 能提高內(nèi)存使用效率,但在數(shù)據(jù)量陡增(毛刺)時(shí)將導(dǎo)致丟數(shù)據(jù)。
對(duì)于那些大部分時(shí)間都比較空閑、周期性來一大波數(shù)據(jù)的場(chǎng)景, 這個(gè)問題尤其突出,很難在兩者之間取得一個(gè)很好的平衡。
ringbuf 的解決方式是分配一個(gè)所有 CPU 共享的大緩沖區(qū),
-
“大緩沖區(qū)”意味著能更好地容忍數(shù)據(jù)量毛刺
-
“共享”則意味著內(nèi)存使用效率更高
另外,ringbuf 內(nèi)存效率的擴(kuò)展性也更好,比如 CPU 數(shù)量從 16 增加到 32 時(shí),
-
perfbuf 的總 buffer 會(huì)跟著翻倍,因?yàn)樗?per-CPU buffer;
-
ringbuf 的總 buffer 不一定需要翻倍,就足以處理擴(kuò)容之后的數(shù)據(jù)量。
1.2 保證事件順序(event ordering)
如果 BPF 應(yīng)用要跟蹤一系列關(guān)聯(lián)事件(correlated events),例如進(jìn)程的啟動(dòng)和終止、 網(wǎng)絡(luò)連接的生命周期事件等,那保持事件的順序就非常關(guān)鍵。perfbuf 在這種場(chǎng)景下有一些問題:如果這些事件發(fā)生的間隔非常短(幾毫秒)并且分散 在不同 CPU 上,那事件的發(fā)送順序可能就會(huì)亂掉 ——這同樣是 perbuf 的 per-CPU 特性決定的。
舉個(gè)真實(shí)例子,幾年前我寫的一個(gè)應(yīng)用需要跟蹤進(jìn)程 fork/exec/exit 事件,收集進(jìn)程級(jí)別(per-process)的資源使用量。BPF 程序?qū)⑦@些事件 寫入 perfbuf,但它們到達(dá)的順序經(jīng)常亂掉。這是因?yàn)閮?nèi)核調(diào)度器在不同 CPU 上調(diào)度進(jìn)程時(shí), 對(duì)于那些存活時(shí)間很短的進(jìn)程,fork(), exec(), and exit() 會(huì)在極短的時(shí)間內(nèi)在不同 CPU 上執(zhí)行。這里的問題很清楚,但要解決這個(gè)問題,就需要在應(yīng)用邏輯中加入大量的判斷和處理, 只有親自做過才知道有多復(fù)雜。
但對(duì)于 ringbuf 來說,這根本不是問題,因?yàn)樗枪蚕淼耐粋€(gè)緩沖區(qū)。ringbuf 保證 如果事件 A 發(fā)生在事件 B 之前,那 A 一定會(huì)先于 B 被提交,也會(huì)在 B 之前被消費(fèi)。這個(gè)特性顯著簡(jiǎn)化了應(yīng)用處理邏輯。
1.3 減少數(shù)據(jù)復(fù)制(wasted data copy)
BPF 程序使用 perfbuf 時(shí),必須先初始化一份事件數(shù)據(jù),然后將它復(fù)制到 perfbuf, 然后才能發(fā)送到用戶空間。這意味著數(shù)據(jù)會(huì)被復(fù)制兩次:
-
第一次:復(fù)制到一個(gè)局部變量(a local variable)或 per-CPU array (BPF 的??臻g很小,因此較大的變量無(wú)法放到棧上,后面有例子)中;
-
第二次:復(fù)制到perfbuf中。
更糟糕的是,如果 perfbuf 已經(jīng)沒有足夠空間放數(shù)據(jù)了,那第一步的復(fù)制完全是浪費(fèi)的。
BPF ringbuf 提供了一個(gè)可選的 reservation/submit API 來避免這種問題。
-
首先申請(qǐng)為數(shù)據(jù)預(yù)留空間(reserve the space),
-
預(yù)留成功后,
-
應(yīng)用就可以直接將準(zhǔn)備發(fā)送的數(shù)據(jù)放到 ringbuf 了,從而節(jié)省了 perfbuf 中的第一次復(fù)制,
-
將數(shù)據(jù)提交到用戶空間將是一件極其高效、不會(huì)失敗的操作,也不涉及任何額外的內(nèi)存復(fù)制。
-
-
如果因?yàn)?buffer 沒有空間而預(yù)留失敗了,那 BPF 程序馬上就能知道,從而也不用再 執(zhí)行 perfbuf 中的第一步復(fù)制。
后面會(huì)有具體例子。
2 ringbuf 使用場(chǎng)景和性能
2.1 常規(guī)場(chǎng)景
對(duì)于所有實(shí)際場(chǎng)景(尤其是那些基于bcc/libbpf 的默認(rèn)配置在使用 perfbuf 的場(chǎng)景), ringbuf 的性能都優(yōu)于 perfbuf 性能。各種不同場(chǎng)景的仿真壓測(cè)(synthetic benchmarking) 結(jié)果見內(nèi)核 patch。
2.2 高吞吐場(chǎng)景
Per-CPU buffer 特性的 perfbuf 在理論上能支持更高的數(shù)據(jù)吞吐, 但這只有在每秒百萬(wàn)級(jí)事件(millions of events per second)的場(chǎng)景下才會(huì)顯現(xiàn)。
在編寫了一個(gè)真實(shí)場(chǎng)景的高吞吐應(yīng)用之后,我們證實(shí)了 ringbuf 在作為與 perfbuf 類似的 per-CPU buffer 使用時(shí),仍然可以作為 perfbuf 的一個(gè)高性能替代品,尤其是用到手動(dòng)管理事件通知(manual data availability notification)機(jī)制時(shí)。
-
BPF side
-
user-space side
2.3 不可掩碼中斷(non-maskable interrupt)場(chǎng)景
唯一需要注意、最好先試驗(yàn)一下的場(chǎng)景:BPF 程序必須在 NMI (non-maskable interrupt) context 中執(zhí)行時(shí),例如處理 cpu-cycles 等 perf events 時(shí)。
ringbuf 內(nèi)部使用了一個(gè)非常輕量級(jí)的 spin-lock,這意味著如果 NMI context 中有競(jìng)爭(zhēng),data reservation 可能會(huì)失敗。因此,在 NMI context 中,如果 CPU 競(jìng)爭(zhēng)非常嚴(yán)重,可能會(huì) 導(dǎo)致丟數(shù)據(jù),雖然此時(shí) ringbuf 仍然有可用空間。
2.4 小結(jié)
除了 NMI context 之外,在其他所有場(chǎng)景中優(yōu)先選擇 ringbuf 而不是 perfbuf 都是非常明智的。
3 示例程序(show me the code)
完整代碼見 bpf-ringbuf-examples project。
BPF 程序的功能是 trace 所有進(jìn)程的 exec() 操作,也就是創(chuàng)建新進(jìn)程事件。
每次 exec() 事件:收集進(jìn)程 ID (pid)、進(jìn)程名字 (comm)、可執(zhí)行文件路徑 (filename),然后發(fā)送給用戶空間程序;用戶空間簡(jiǎn)單通過 printf() 打印輸出。用三種不同方式實(shí)現(xiàn),輸出都類似:
$ sudo ./ringbuf-reserve-commit # or ./ringbuf-output, or ./perfbuf-output
TIME EVENT PID COMM FILENAME
1939 EXEC 3232062 sh /bin/sh
1939 EXEC 3232062 timeout /usr/bin/timeout
1939 EXEC 3232063 ipmitool /usr/bin/ipmitool
1939 EXEC 3232065 env /usr/bin/env
1939 EXEC 3232066 env /usr/bin/env
1939 EXEC 3232065 timeout /bin/timeout
1939 EXEC 3232066 timeout /bin/timeout
1939 EXEC 3232067 sh /bin/sh
1939 EXEC 3232068 sh /bin/sh
^C
事件的結(jié)構(gòu)體定義:
#define TASK_COMM_LEN 16
#define MAX_FILENAME_LEN 512
// BPF 程序發(fā)送給 userspace 的事件
struct event {
int pid;
char comm[TASK_COMM_LEN];
char filename[MAX_FILENAME_LEN];
};
這里有意讓這個(gè)結(jié)構(gòu)體的大小超過 512 字節(jié),這樣 event 變量就無(wú)法 放到 BPF ??臻g(max 512Byte)上,后面會(huì)看到 perfbuf 和 ringbuf 程序分別怎么處理。
3.1 perfbuf 示例
內(nèi)核 BPF 程序
// 聲明一個(gè) perfbuf map。幾點(diǎn)注意:
// 1. 不用特意設(shè)置 max_entries,libbpf 會(huì)自動(dòng)將其設(shè)置為 CPU 數(shù)量;
// 2. 這個(gè) map 的 per-CPU buffer 大小是 userspace 設(shè)置的,后面會(huì)看到
struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); // perf buffer (array)
__uint(key_size, sizeof(int));
__uint(value_size, sizeof(int));
} pb SEC(".maps");
// 一個(gè) struct event 變量的大小超過了 512 字節(jié),無(wú)法放到 BPF 棧上,
// 因此聲明一個(gè) size=1 的 per-CPU array 來存放 event 變量
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); // per-cpu array
__uint(max_entries, 1);
__type(key, int);
__type(value, struct event);
} heap SEC(".maps");
SEC("tp/sched/sched_process_exec")
int handle_exec(struct trace_event_raw_sched_process_exec *ctx)
{
unsigned fname_off = ctx->__data_loc_filename & 0xFFFF;
struct event *e;
int zero = 0;
e = bpf_map_lookup_elem(&heap, &zero);
if (!e) /* can't happen */
return 0;
e->pid = bpf_get_current_pid_tgid() >> 32;
bpf_get_current_comm(&e->comm, sizeof(e->comm));
bpf_probe_read_str(&e->filename, sizeof(e->filename), (void *)ctx + fname_off);
// 發(fā)送事件,參數(shù)列表
bpf_perf_event_output(ctx, &pb, BPF_F_CURRENT_CPU, e, sizeof(*e));
return 0;
}
用戶空間程序
完整代碼 the user-space side, 基于 BPF skeleton(更多信息見 這里)。
看一個(gè)關(guān)鍵點(diǎn):使用 libbpf user-space perfbuffer_new() API 來創(chuàng)建一個(gè) perf buffer consumer:
struct perf_buffer *pb = NULL;
struct perf_buffer_opts pb_opts = {};
struct perfbuf_output_bpf *skel;
/* Set up ring buffer polling */
pb_opts.sample_cb = handle_event;
pb = perf_buffer__new(bpf_map__fd(skel->maps.pb), 8 /* 32KB per CPU */, &pb_opts);
這里設(shè)置 per-CPU buffer 為 32KB, 注意其中的 8 表示的是 number of memory pages,每個(gè) page 是 4KB,因此總大?。? pages x 4096 byte/page = 32KB。
3.2 ringbuf 示例
完整代碼:
-
BPF-side code
-
user-space code
內(nèi)核 BPF 程序
bpf_ringbuf_output()
在設(shè)計(jì)上遵循了bpf_perf_event_output()
的語(yǔ)義, 以使應(yīng)用從 perfbuf 遷移到 ringbuf 時(shí)更容易。為了看出二者有多相似,這里展示下 兩個(gè)示例代碼的 diff。
--- src/perfbuf-output.bpf.c 2020-10-25 1822.247019800 -0700
+++ src/ringbuf-output.bpf.c 2020-10-25 1814.510630322 -0700
@@ -6,12 +6,11 @@
char LICENSE[] SEC("license") = "Dual BSD/GPL";
-/* BPF perfbuf map */
+/* BPF ringbuf map */
struct {
- __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
- __uint(key_size, sizeof(int));
- __uint(value_size, sizeof(int));
-} pb SEC(".maps");
+ __uint(type, BPF_MAP_TYPE_RINGBUF);
+ __uint(max_entries, 256 * 1024 /* 256 KB */);
+} rb SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
@@ -35,7 +34,7 @@
bpf_get_current_comm(&e->comm, sizeof(e->comm));
bpf_probe_read_str(&e->filename, sizeof(e->filename), (void *)ctx + fname_off);
- bpf_perf_event_output(ctx, &pb, BPF_F_CURRENT_CPU, e, sizeof(*e));
+ bpf_ringbuf_output(&rb, e, sizeof(*e), 0);
return 0;
}
只有兩個(gè)小改動(dòng):
-
ringbuf map 的大?。╩ax_entries)可以在 BPF 側(cè)指定了,注意這是所有 CPU 共享的大小。
-
在 userspace 側(cè)來設(shè)置(或 override) max_entries 也是可以的,API 是 bpf_map__set_max_entries();
-
max_entries的單位是字節(jié),必須是內(nèi)核頁(yè)大小( 幾乎永遠(yuǎn)是 4096)的倍數(shù),也必須是 2 的冪次。
-
bpf_perf_event_output()替換成了類似的bpf_ringbuf_output(),后者更簡(jiǎn)單,不需要 BPF context 參數(shù)。
用戶空間程序
事件 handler 簽名有點(diǎn)變化:
-
會(huì)返回錯(cuò)誤信息(進(jìn)而終止 consumer 循環(huán))
-
參數(shù)里面去掉了產(chǎn)生這個(gè)事件的 CPU Index
-void handleevent(void *ctx, int cpu, void *data, unsigned int datasz)
+int handleevent(void *ctx, void *data, sizet data_sz)
{
const struct event *e = data;
struct tm *tm;
如果 CPU index 對(duì)你很重要,那你需要自己在 BPF 代碼中記錄它。
另外,ringbuffer API 不提供丟失數(shù)據(jù)(lost samples)的回調(diào)函數(shù),而 perfbuffer 是支持的。如果需要這個(gè)功能,必須自己在 BPF 代碼中處理。這樣的設(shè)計(jì)對(duì)于一個(gè)(所有 CPU)共享的 ring buffer 能最小化鎖競(jìng)爭(zhēng), 同時(shí)也避免了為不需要的功能買單:在實(shí)際中,這功能除了能用戶在 userspace 打印出有數(shù)據(jù)丟失之外,其他基本也做不了什么, 而類似的目的在 BPF 中可以更顯式和高效地完成。
第二個(gè)不同是 ringbuffer_new() API 更加簡(jiǎn)潔:
/* Set up ring buffer polling */
- pb_opts.sample_cb = handle_event;
- pb = perf_buffer__new(bpf_map__fd(skel->maps.pb), 8 /* 32KB per CPU */, &pb_opts);
- if (libbpf_get_error(pb)) {
+ rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL);
+ if (!rb) {
err = -1;
- fprintf(stderr, "Failed to create perf buffer ");
+ fprintf(stderr, "Failed to create ring buffer ");
goto cleanup;
}
接下來基本上就是文本替換一下的事情了:perf_buffer__poll()
-ring_buffer__poll()
printf("%-8s %-5s %-7s %-16s %s ",
"TIME", "EVENT", "PID", "COMM", "FILENAME");
while (!exiting) {
- err = perf_buffer__poll(pb, 100 /* timeout, ms */);
+ err = ring_buffer__poll(rb, 100 /* timeout, ms */);
/* Ctrl-C will cause -EINTR */
if (err == -EINTR) {
err = 0;
break;
}
if (err < 0) {
- printf("Error polling perf buffer: %d ", err);
+ printf("Error polling ring buffer: %d ", err);
break;
}
}
3.3 ringbuf reserve/commit API 示例
bpf_ringbuf_output()
API 的目的是確保從 perfbuf 到 ringbuf 遷移時(shí)無(wú)需對(duì) BPF 代 碼做重大改動(dòng),但這也意味著它繼承了 perfbuf API 的一些缺點(diǎn):
-
額外的內(nèi)存復(fù)制(extra memory copy)
這意味著需要額外的空間來構(gòu)建 event 變量,然后將其復(fù)制到 buffer。不僅低效, 而且經(jīng)常需要引入只有一個(gè)元素的 per-CPU array,增加了不必要的處理復(fù)雜性。
-
非常晚的 buffer 空間申請(qǐng)(data reservation)
如果這一步失敗了(例如由于用戶空間消費(fèi)不及時(shí)導(dǎo)致 buffer 滿了,或者有大量 突發(fā)事件導(dǎo)致 buffer 溢出了),那上一步的工作將變得完全無(wú)效,浪費(fèi)內(nèi)存空間和計(jì)算資源。
原理
如果能提前知道事件將在第二步被丟棄,就無(wú)需做第一步了, 節(jié)省一些內(nèi)存和計(jì)算資源,消費(fèi)端反而因此而消費(fèi)地更快一些。但 xxx_output()風(fēng)格的API 是無(wú)法實(shí)現(xiàn)這個(gè)目的的。這就是為什么引入了新的bpfringbufreserve()/bpfringbufcommit() API。
-
提前預(yù)留空間,或者能立即發(fā)現(xiàn)沒有可以空間了(返回
NULL
); -
預(yù)留成功后,一旦數(shù)據(jù)寫好了,將它發(fā)送到 userspace 是一個(gè)不會(huì)失敗的操作。
也就是說只要
bpf_ringbuf_reserve()
返回非空,那隨后的bpf_ringbuf_commit()
就永遠(yuǎn)會(huì)成功,因此它沒有返回值。
另外,ring buffer 中預(yù)留的空間在被提交之前,用戶空間是看不到的, 因此 BPF 程序可以從容地組織自己的 event 數(shù)據(jù),不管它有多復(fù)雜、需要多少步驟。這種方式也避免了額外的內(nèi)存復(fù)制和臨時(shí)存儲(chǔ)空間(extra memory copying and temporary storage spaces)。
限制
唯一的限制是:BPF 校驗(yàn)器在校驗(yàn)時(shí)(at verification time), 必須知道預(yù)留數(shù)據(jù)的大小 (size of the reservation),因此不支持動(dòng)態(tài)大小的事件數(shù)據(jù)。
-
對(duì)于動(dòng)態(tài)大小的數(shù)據(jù),用戶只能退回到用
bpf_ringbuf_output()
方式來提交,忍受額外的數(shù)據(jù)復(fù)制開銷; -
其他所有情況下,reserve/commit API 都應(yīng)該是首選。
內(nèi)核 BPF 程序
-
BPF
-
user-space
--- src/ringbuf-output.bpf.c 2020-10-25 1814.510630322 -0700
+++ src/ringbuf-reserve-submit.bpf.c 2020-10-25 1853.409470270 -0700
@@ -12,29 +12,21 @@
__uint(max_entries, 256 * 1024 /* 256 KB */);
} rb SEC(".maps");
-struct {
- __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
- __uint(max_entries, 1);
- __type(key, int);
- __type(value, struct event);
-} heap SEC(".maps");
-
SEC("tp/sched/sched_process_exec")
int handle_exec(struct trace_event_raw_sched_process_exec *ctx)
{
unsigned fname_off = ctx->__data_loc_filename & 0xFFFF;
struct event *e;
- int zero = 0;
- e = bpf_map_lookup_elem(&heap, &zero);
- if (!e) /* can't happen */
+ e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
+ if (!e)
return 0;
e->pid = bpf_get_current_pid_tgid() >> 32;
bpf_get_current_comm(&e->comm, sizeof(e->comm));
bpf_probe_read_str(&e->filename, sizeof(e->filename), (void *)ctx + fname_off);
- bpf_ringbuf_output(&rb, e, sizeof(*e), 0);
+ bpf_ringbuf_submit(e, 0);
return 0;
}
用戶空間程序
用戶空間代碼與之前的 ringbuf output API 完全一樣,因?yàn)檫@個(gè) API 涉及到的只是提交方(生產(chǎn)方), 消費(fèi)方還是一樣的方式來消費(fèi)。
4 ringbuf 事件通知控制
4.1 事件通知開銷
在高吞吐場(chǎng)景中,最大的性能損失經(jīng)常來自提交數(shù)據(jù)時(shí),內(nèi)核的信號(hào)通知開銷(in-kernel signalling of data availability) ,也就是內(nèi)核的 poll/epoll 通知阻塞在讀數(shù)據(jù)上的 userspace handler 接收數(shù)據(jù)。
這一點(diǎn)對(duì) perfbuf 和 ringbuf 都是一樣的。
4.2 perbuf 解決方式
perfbuf 處理這種場(chǎng)景的方式是提供了一個(gè)采樣通知(sampled notification)機(jī)制:每 N 個(gè)事件才會(huì)發(fā)送一次通知。用戶空間創(chuàng)建 perfbuf 時(shí)可以指定這個(gè)參數(shù)。
這種機(jī)制能否解決問題,因具體場(chǎng)景而異。
4.3 ringbuf 解決方式
ringbuf 選了一條不同的路:bpfringbufoutput() 和 bpfringbufcommit() 都支持一個(gè)額外的 flags 參數(shù),
-
BPF_RB_NO_WAKEUP
:不觸發(fā)通知 -
BPF_RB_FORCE_WAKEUP
:會(huì)觸發(fā)通知
基于這個(gè) flags,用戶能實(shí)現(xiàn)更加精確的通知控制。例子見 BPF ringbuf benchmark。
默認(rèn)情況下,如果沒指定任何 flag,ringbuf 會(huì)采用自適應(yīng)通知 (adaptive notification)機(jī)制,根據(jù) userspace 消費(fèi)者是否有滯后(lagging)來動(dòng)態(tài) 調(diào)整通知間隔,盡量確保 userspace 消費(fèi)者既不用承擔(dān)額外開銷,又不丟失任何數(shù)據(jù)。這種默認(rèn)配置在大部分場(chǎng)景下都是有效和安全的,但如果想獲得極致性能,那 顯式控制數(shù)據(jù)通知就是有必要的,需要結(jié)合具體應(yīng)用場(chǎng)景和處理邏輯來設(shè)計(jì)。
5 總結(jié)
本文介紹了 BPF ring buffer 解決的問題及其背后的設(shè)計(jì)。
文中給出的示例代碼和內(nèi)核代碼鏈接,展示了 ringbuf API 的基礎(chǔ)和高級(jí)用法。希望閱讀本文之后,讀者能對(duì) ringbuf 有一個(gè)很好的理解和把握,能根據(jù)自己的具體應(yīng)用 選擇合適的 API 來使用。
其他相關(guān)資料(譯注)
內(nèi)核文檔,BPF ring buffer
有一些更細(xì)節(jié)的設(shè)計(jì)與實(shí)現(xiàn),可作為本文補(bǔ)充。
原文標(biāo)題:[譯] BPF ring buffer:使用場(chǎng)景、核心設(shè)計(jì)及程序示例
文章出處:【微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
-
內(nèi)存
+關(guān)注
關(guān)注
8文章
2902瀏覽量
73536 -
程序
+關(guān)注
關(guān)注
115文章
3719瀏覽量
80356 -
BPF
+關(guān)注
關(guān)注
0文章
24瀏覽量
3926
原文標(biāo)題:[譯] BPF ring buffer:使用場(chǎng)景、核心設(shè)計(jì)及程序示例
文章出處:【微信號(hào):LinuxDev,微信公眾號(hào):Linux閱碼場(chǎng)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論