日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
BPFRingBuffer:使用場景、核心設(shè)計及程序示例

作為改進,內(nèi)核 5.8 引入另一個新的 BPF 數(shù)據(jù)結(jié)構(gòu):BPF ring buffer(環(huán)形緩沖區(qū),ringbuf),

成都創(chuàng)新互聯(lián)從2013年開始,先為天柱等服務(wù)建站,天柱等地企業(yè),進行企業(yè)商務(wù)咨詢服務(wù)。為天柱企業(yè)網(wǎng)站制作PC+手機+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。

  • 相比 perf buffer,它內(nèi)存效率更高、保證事件順序,性能也不輸前者;
  • 在使用上,既提供了與 perf buffer 類似的 API ,以方便用戶遷移;又提供了一套新的 reserve/commit API(先預(yù)留再提交),以實現(xiàn)更高性能。此外,實驗與真實環(huán)境的壓測結(jié)果都表明,從 BPF 程序發(fā)送數(shù)據(jù)給用戶空間時, 應(yīng)該選擇 BPF ring buffer。

1.ringbuf 相比 perfbuf的改進

perfbuf 是 per-CPU 環(huán)形緩沖區(qū)(circular buffers),能實現(xiàn)高效的 “內(nèi)核-用戶空間”數(shù)據(jù)交互,在實際中也非常有用,但 per-CPU 的設(shè)計 導(dǎo)致兩個嚴重缺陷:

  • 內(nèi)存使用效率低下(inefficient use of memory)
  • 事件順序無法保證(event re-ordering)

因此內(nèi)核 5.8 引入了 ringbuf 來解決這個問題。ringbuf 是一個“多生產(chǎn)者、單消費者”(multi-producer, single-consumer,MPSC) 隊列,可安全地在多個 CPU 之間共享和操作。perfbuf 支持的一些功能它都支持,包括,

  • 可變長數(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 的下列問題:

  • 可變長數(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 為每個 CPU 分配一個獨立的緩沖區(qū),這意味著開發(fā)者通常需要 在內(nèi)存效率和數(shù)據(jù)丟失之間做出折中:

  • 越大的 per-CPU buffer 越能避免丟數(shù)據(jù),但也意味著大部分時間里,大部分內(nèi)存都是浪費的;
  • 盡量小的 per-CPU buffer 能提高內(nèi)存使用效率,但在數(shù)據(jù)量陡增(毛刺)時將導(dǎo)致丟數(shù)據(jù)。對于那些大部分時間都比較空閑、周期性來一大波數(shù)據(jù)的場景, 這個問題尤其突出,很難在兩者之間取得一個很好的平衡。

ringbuf 的解決方式是分配一個所有 CPU 共享的大緩沖區(qū),

  • “大緩沖區(qū)”意味著能更好地容忍數(shù)據(jù)量毛刺
  • “共享”則意味著內(nèi)存使用效率更高

另外,ringbuf 內(nèi)存效率的擴展性也更好,比如 CPU 數(shù)量從 16 增加到 32 時,

  • perfbuf 的總 buffer 會跟著翻倍,因為它是 per-CPU buffer;
  • ringbuf 的總 buffer 不一定需要翻倍,就足以處理擴容之后的數(shù)據(jù)量。

1.2 保證事件順序(event ordering)

如果 BPF 應(yīng)用要跟蹤一系列關(guān)聯(lián)事件(correlated events),例如進程的啟動和終止、 網(wǎng)絡(luò)連接的生命周期事件等,那保持事件的順序就非常關(guān)鍵。perfbuf 在這種場景下有一些問題:如果這些事件發(fā)生的間隔非常短(幾毫秒)并且分散 在不同 CPU 上,那事件的發(fā)送順序可能就會亂掉 ——這同樣是 perbuf 的 per-CPU 特性決定的。

舉個真實例子,幾年前我寫的一個應(yīng)用需要跟蹤進程 fork/exec/exit 事件,收集進程級別(per-process)的資源使用量。BPF 程序?qū)⑦@些事件 寫入 perfbuf,但它們到達的順序經(jīng)常亂掉。這是因為內(nèi)核調(diào)度器在不同 CPU 上調(diào)度進程時, 對于那些存活時間很短的進程,fork(), exec(), and exit() 會在極短的時間內(nèi)在不同 CPU 上執(zhí)行。這里的問題很清楚,但要解決這個問題,就需要在應(yīng)用邏輯中加入大量的判斷和處理, 只有親自做過才知道有多復(fù)雜。

但對于 ringbuf 來說,這根本不是問題,因為它是共享的同一個緩沖區(qū)。ringbuf 保證 如果事件 A 發(fā)生在事件 B 之前,那 A 一定會先于 B 被提交,也會在 B 之前被消費。這個特性顯著簡化了應(yīng)用處理邏輯。

1.3 減少數(shù)據(jù)復(fù)制(wasted data copy)

BPF 程序使用 perfbuf 時,必須先初始化一份事件數(shù)據(jù),然后將它復(fù)制到 perfbuf, 然后才能發(fā)送到用戶空間。這意味著數(shù)據(jù)會被復(fù)制兩次:

  • 第一次:復(fù)制到一個局部變量(a local variable)或 per-CPU array (BPF 的??臻g很小,因此較大的變量無法放到棧上,后面有例子)中;
  • 第二次:復(fù)制到 perfbuf 中。更糟糕的是,如果 perfbuf 已經(jīng)沒有足夠空間放數(shù)據(jù)了,那第一步的復(fù)制完全是浪費的。

BPF ringbuf 提供了一個可選的 reservation/submit API 來避免這種問題。

  • 首先申請為數(shù)據(jù)預(yù)留空間(reserve the space);
  • 預(yù)留成功后;

應(yīng)用就可以直接將準備發(fā)送的數(shù)據(jù)放到 ringbuf 了,從而節(jié)省了 perfbuf 中的第一次復(fù)制,

將數(shù)據(jù)提交到用戶空間將是一件極其高效、不會失敗的操作,也不涉及任何額外的內(nèi)存復(fù)制。如果因為 buffer 沒有空間而預(yù)留失敗了,那 BPF 程序馬上就能知道,從而也不用再 執(zhí)行 perfbuf 中的第一步復(fù)制。后面會有具體例子。

2.ringbuf 使用場景和性能

2.1 常規(guī)場景

對于所有實際場景(尤其是那些基于bcc/libbpf 的默認配置在使用 perfbuf 的場景), ringbuf 的性能都優(yōu)于 perfbuf 性能。各種不同場景的仿真壓測(synthetic benchmarking) 結(jié)果見內(nèi)核 patch。

2.2 高吞吐場景

Per-CPU buffer 特性的 perfbuf 在理論上能支持更高的數(shù)據(jù)吞吐, 但這只有在每秒百萬級事件(millions of events per second)的場景下才會顯現(xiàn)。

在編寫了一個真實場景的高吞吐應(yīng)用之后,我們證實了 ringbuf 在作為與 perfbuf 類似的 per-CPU buffer 使用時,仍然可以作為 perfbuf 的一個高性能替代品,尤其是用到手動管理事件通知(manual data availability notification)機制時。

  • BPF side
  • user-space side

2.3 不可掩碼中斷(non-maskable interrupt)場景

唯一需要注意、最好先試驗一下的場景:BPF 程序必須在 NMI (non-maskable interrupt) context 中執(zhí)行時,例如處理 cpu-cycles 等 perf events 時。

ringbuf 內(nèi)部使用了一個非常輕量級的 spin-lock,這意味著如果 NMI context 中有競爭,data reservation 可能會失敗。因此,在 NMI context 中,如果 CPU 競爭非常嚴重,可能會 導(dǎo)致丟數(shù)據(jù),雖然此時 ringbuf 仍然有可用空間。

2.4 小結(jié)

除了 NMI context 之外,在其他所有場景中優(yōu)先選擇 ringbuf 而不是 perfbuf 都是非常明智的。

3.示例程序(show me the code)

完整代碼見 bpf-ringbuf-examples project。

BPF 程序的功能是 trace 所有進程的 exec() 操作,也就是創(chuàng)建新進程事件。

每次 exec() 事件:收集進程 ID (pid)、進程名字 (comm)、可執(zhí)行文件路徑 (filename),然后發(fā)送給用戶空間程序;用戶空間簡單通過 printf() 打印輸出。用三種不同方式實現(xiàn),輸出都類似:

$ sudo ./ringbuf-reserve-commit    # or ./ringbuf-output, or ./perfbuf-output
TIME EVENT PID COMM FILENAME
19:17:39 EXEC 3232062 sh /bin/sh
19:17:39 EXEC 3232062 timeout /usr/bin/timeout
19:17:39 EXEC 3232063 ipmitool /usr/bin/ipmitool
19:17:39 EXEC 3232065 env /usr/bin/env
19:17:39 EXEC 3232066 env /usr/bin/env
19:17:39 EXEC 3232065 timeout /bin/timeout
19:17:39 EXEC 3232066 timeout /bin/timeout
19:17:39 EXEC 3232067 sh /bin/sh
19:17:39 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];
};

這里有意讓這個結(jié)構(gòu)體的大小超過 512 字節(jié),這樣 event 變量就無法 放到 BPF ??臻g(max 512Byte)上,后面會看到 perfbuf 和 ringbuf 程序分別怎么處理。

3.1 perfbuf 示例

內(nèi)核 BPF 程序

// 聲明一個 perfbuf map。幾點注意:
// 1. 不用特意設(shè)置 max_entries,libbpf 會自動將其設(shè)置為 CPU 數(shù)量;
// 2. 這個 map 的 per-CPU buffer 大小是 userspace 設(shè)置的,后面會看到
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");

// 一個 struct event 變量的大小超過了 512 字節(jié),無法放到 BPF 棧上,
// 因此聲明一個 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(更多信息見 這里)。

看一個關(guān)鍵點:使用 libbpf user-space perfbuffer_new() API 來創(chuàng)建一個 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,每個 page 是 4KB,因此總大?。? pages x 4096 byte/page = 32KB。

3.2 ringbuf 示例

完整代碼:

  • BPF-side code
  • user-space code

內(nèi)核 BPF 程序

bpf_ringbuf_output() 在設(shè)計上遵循了bpf_perf_event_output() 的語義, 以使應(yīng)用從 perfbuf 遷移到 ringbuf 時更容易。為了看出二者有多相似,這里展示下 兩個示例代碼的 diff。

--- src/perfbuf-output.bpf.c  2020-10-25 18:52:22.247019800 -0700
+++ src/ringbuf-output.bpf.c 2020-10-25 18:44:14.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;
}

只有兩個小改動:

ringbuf map 的大小(max_entries)可以在 BPF 側(cè)指定了,注意這是所有 CPU 共享的大小。

  • 在 userspace 側(cè)來設(shè)置(或 override) max_entries 也是可以的,API 是 bpf_map__set_max_entries();
  • max_entries 的單位是字節(jié),必須是內(nèi)核頁大小( 幾乎永遠是 4096)的倍數(shù),也必須是 2 的冪次。

bpf_perf_event_output() 替換成了類似的 bpf_ringbuf_output(),后者更簡單,不需要 BPF context 參數(shù)。

用戶空間程序

事件 handler 簽名有點變化:

  • 會返回錯誤信息(進而終止 consumer 循環(huán))
  • 參數(shù)里面去掉了產(chǎn)生這個事件的 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 對你很重要,那你需要自己在 BPF 代碼中記錄它。

另外,ringbuffer API 不提供丟失數(shù)據(jù)(lost samples)的回調(diào)函數(shù),而 perfbuffer 是支持的。如果需要這個功能,必須自己在 BPF 代碼中處理。這樣的設(shè)計對于一個(所有 CPU)共享的 ring buffer 能最小化鎖競爭, 同時也避免了為不需要的功能買單:在實際中,這功能除了能用戶在 userspace 打印出有數(shù)據(jù)丟失之外,其他基本也做不了什么, 而類似的目的在 BPF 中可以更顯式和高效地完成。

第二個不同是 ringbuffer_new() API 更加簡潔:

 /* 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\n");
+ fprintf(stderr, "Failed to create ring buffer\n");
goto cleanup;
}

接下來基本上就是文本替換一下的事情了:perf_buffer__poll()- ring_buffer__poll()

 printf("%-8s %-5s %-7s %-16s %s\n",
"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\n", err);
+ printf("Error polling ring buffer: %d\n", err);
break;
}
}

3.3 ringbuf reserve/commit API 示例

bpf_ringbuf_output() API 的目的是確保從 perfbuf 到 ringbuf 遷移時無需對 BPF 代 碼做重大改動,但這也意味著它繼承了 perfbuf API 的一些缺點:

  • 額外的內(nèi)存復(fù)制(extra memory copy)

這意味著需要額外的空間來構(gòu)建 event 變量,然后將其復(fù)制到 buffer。不僅低效, 而且經(jīng)常需要引入只有一個元素的 per-CPU array,增加了不必要的處理復(fù)雜性。

  • 非常晚的 buffer 空間申請(data reservation)

如果這一步失敗了(例如由于用戶空間消費不及時導(dǎo)致 buffer 滿了,或者有大量 突發(fā)事件導(dǎo)致 buffer 溢出了),那上一步的工作將變得完全無效,浪費內(nèi)存空間和計算資源。

原理

如果能提前知道事件將在第二步被丟棄,就無需做第一步了, 節(jié)省一些內(nèi)存和計算資源,消費端反而因此而消費地更快一些。但 xxx_output()風格的API 是無法實現(xiàn)這個目的的。這就是為什么引入了新的bpfringbufreserve()/bpfringbufcommit() API。

提前預(yù)留空間,或者能立即發(fā)現(xiàn)沒有可以空間了(返回 NULL);

預(yù)留成功后,一旦數(shù)據(jù)寫好了,將它發(fā)送到 userspace 是一個不會失敗的操作。也就是說只要 bpf_ringbuf_reserve() 返回非空,那隨后的 bpf_ringbuf_commit() 就永遠會成功,因此它沒有返回值。另外,ring buffer 中預(yù)留的空間在被提交之前,用戶空間是看不到的, 因此 BPF 程序可以從容地組織自己的 event 數(shù)據(jù),不管它有多復(fù)雜、需要多少步驟。這種方式也避免了額外的內(nèi)存復(fù)制和臨時存儲空間(extra memory copying and temporary storage spaces)。

限制

唯一的限制是:BPF 校驗器在校驗時(at verification time), 必須知道預(yù)留數(shù)據(jù)的大小 (size of the reservation),因此不支持動態(tài)大小的事件數(shù)據(jù)。

  • 對于動態(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 18:44:14.510630322 -0700
+++ src/ringbuf-reserve-submit.bpf.c 2020-10-25 18:36:53.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 完全一樣,因為這個 API 涉及到的只是提交方(生產(chǎn)方), 消費方還是一樣的方式來消費。

4.ringbuf 事件通知控制

4.1 事件通知開銷

在高吞吐場景中,最大的性能損失經(jīng)常來自提交數(shù)據(jù)時,內(nèi)核的信號通知開銷(in-kernel signalling of data availability) ,也就是內(nèi)核的 poll/epoll 通知阻塞在讀數(shù)據(jù)上的 userspace handler 接收數(shù)據(jù)。

這一點對 perfbuf 和 ringbuf 都是一樣的。

4.2 perbuf 解決方式

perfbuf 處理這種場景的方式是提供了一個采樣通知(sampled notification)機制:每 N 個事件才會發(fā)送一次通知。用戶空間創(chuàng)建 perfbuf 時可以指定這個參數(shù)。

這種機制能否解決問題,因具體場景而異。

4.3 ringbuf 解決方式

ringbuf 選了一條不同的路:bpfringbufoutput() 和 bpfringbufcommit() 都支持一個額外的 flags 參數(shù),

  • BPF_RB_NO_WAKEUP:不觸發(fā)通知
  • BPF_RB_FORCE_WAKEUP:會觸發(fā)通知

基于這個 flags,用戶能實現(xiàn)更加精確的通知控制。例子見 BPF ringbuf benchmark。

默認情況下,如果沒指定任何 flag,ringbuf 會采用自適應(yīng)通知 (adaptive notification)機制,根據(jù) userspace 消費者是否有滯后(lagging)來動態(tài) 調(diào)整通知間隔,盡量確保 userspace 消費者既不用承擔額外開銷,又不丟失任何數(shù)據(jù)。這種默認配置在大部分場景下都是有效和安全的,但如果想獲得極致性能,那 顯式控制數(shù)據(jù)通知就是有必要的,需要結(jié)合具體應(yīng)用場景和處理邏輯來設(shè)計。

5.總結(jié)

本文介紹了 BPF ring buffer 解決的問題及其背后的設(shè)計。

文中給出的示例代碼和內(nèi)核代碼鏈接,展示了 ringbuf API 的基礎(chǔ)和高級用法。希望閱讀本文之后,讀者能對 ringbuf 有一個很好的理解和把握,能根據(jù)自己的具體應(yīng)用 選擇合適的 API 來使用。

作者簡介

趙亞楠,攜程資深架構(gòu)師,負責攜程云平臺網(wǎng)絡(luò)虛擬化、云原生安全、內(nèi)核等基礎(chǔ)設(shè)施研發(fā)工作。

譯者序本文翻譯自 BPF 核心開發(fā)者 Andrii Nakryiko 2020 的一篇文章:BPF ring buffer。

文章介紹了 BPF ring buffer 解決的問題及背后的設(shè)計,并給出了一些代碼示例和內(nèi)核 patch 鏈接,深度和廣度兼?zhèn)?,是學習 ring buffer 的極佳參考。

由于譯者水平有限,本文不免存在遺漏或錯誤之處。如有疑問,請查閱原文。


分享名稱:BPFRingBuffer:使用場景、核心設(shè)計及程序示例
當前地址:http://www.5511xx.com/article/ccseodg.html