ring_buffer_reserve()
/ring_buffer_submit()
でより効率的に処理する
ここまで基本的なデータのやり取りの仕方がわかりました。次に実際にもっと詳細なデータを取得できるようになりたいところですが,
先ほどのBPFプログラムでは,bpf_
を用いてリングバッファーにそのデータを保存していました。このとき,
- バッファーが既に一杯のとき
EAGAIN
でエラーになる - 内部でデータの
memcpy()
が実施される
前者はEAGAIN
が返ってきたらもう一度実施すれば良いだけではあるのですが,
そこで,ring_
/ring_
が用意されています。
ring_
は指定したサイズの領域をリングバッファーに確保してくれるAPIです。戻り値として確保した領域のポインタが返されるため,buffer_ reserve() そこにデータを直接書き込むことでメモリコピーの回数を減らせます。また, 確保失敗時はエラーになるため, より早い段階で処理を継続するか諦めるかを判断できます。 ring_
は渡されたリングバッファーのアドレスが確定したと判断し,buffer_ submit() 必要に応じてユーザーランドにデータの受信を通知します。
他にも確保したものの,ring_
が使えます。
さて,
int syscalls__execve(struct trace_event_raw_sys_enter *ctx)
{
struct event *event = NULL;
/* リングバッファーの領域確保 */
event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
if (!event)
return 0;
event->pid = bpf_get_current_pid_tgid() >> 32;
/* リングバッファーの中身を確定し,ユーザーランドに通知 */
bpf_ringbuf_submit(event, 0);
return 0;
}
今回の例だとあまり利点は感じられないのですが,ring_
の最後の引数は,ring_
の最後の引数は,ring_
と同じ通知タイミングを示すフラグです。
実際にビルドして実行した結果は,v1.
タグを付けています。
一点,ring_
を呼んだあとに,ring_
を呼ぶとします。プログラムBはすぐにring_
でデータを送ったとしても,ring_
を呼び出したあとになります。
ちなみにring_
は内部的に,ring_
/ring_
と同等の処理を一度に行っています。
構造体データへの移植性の高いアクセス方法
BPFオブジェクトの再利用性を高めるために考えなくてはならないのが,
BPF CO-REではそのようなカーネルバージョンの差異を吸収するためのマクロを用意しています。マクロを使ってメンバーにアクセスすることで,
具体的な例として,bpf_
」task
)task->real_
」
そこでBPF CO-REではbpf_
で定義されているBPF_
」BPF_
マクロを利用した表現です。
例1:単純な構造体メンバーへのアロー演算子を利用したアクセス
src->a;
BPF_CORE_READ(src, a);
例2:多段のメンバーアクセス
src->a->b->c->d->e;
BPF_CORE_READ(src, a, b, c, d, e);
例3:アロー演算子とドット演算子の混在
src->a.b->c.d.e->f;
BPF_CORE_READ(src, a.b, c.d.e, f);
これだけ読めば,BPF_
が,BPF_
とBPF_
が用意されていますので,
実際にPPIDを取得するようにした場合の変更点を取り上げます。まず,
struct task_struct *task;
/* リングバッファーの領域確保 */
event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
if (!event)
return 0;
event->pid = bpf_get_current_pid_tgid() >> 32;
/* 現在のタスク構造体取得 */
task = (struct task_struct *)bpf_get_current_task();
/* task->real_parent->tgidへのアクセス */
event->ppid = (pid_t)BPF_CORE_READ(task, real_parent, tgid);
ちなみに共通の構造体定義はexecsnoop.
」execsnoop.
とexecsnoop.
の双方からインクルードするようにしました。ユーザーランドプログラム側のexecsnoop.
はスケルトンヘッダーファイルexecsnoop.
)
/* SPDX-License-Identifier: CC0-1.0 */
#ifndef __EXECSNOOP_H__
#define __EXECSNOOP_H__
struct event {
pid_t pid;
pid_t ppid;
};
#endif /* __EXECSNOOP_H__ */
ppid
メンバーが増えただけですね。またユーザーランドプログラム側も,
int handle_event_cb(void *ctx, void *data, size_t size)
{
struct event *event = data;
fprintf(stdout, "% 8d % 8d\n", event->pid, event->ppid);
return 0;
}
int main(void)
(中略)
fprintf(stdout, "%-8s %-8s\n", "PID", "PPID");
実際にビルドして実行しみましょう。リポジトリではv1.
タグを付けています。
$ git checkout r695/3 v1.3 $ make $ sudo ./execsnoop PID PPID 6021 2276 6022 6021 6023 2276 6024 6022
PIDだけでなく,