- Published on
eBPF 완벽 가이드 — 커널 안의 작은 가상 머신: Verifier, JIT, CO-RE, Maps, Attach Points, XDP, LSM, sched_ext (2025)
- Authors

- Name
- Youngju Kim
- @fjvbn20031
들어가며 — eBPF는 Linux의 새로운 신경계다
eBPF를 처음 접한 사람의 반응은 보통 "이게 진짜 가능해?"이다. 사용자 공간 프로그램이 커널 내부의 임의 지점에 작은 코드 조각을 끼워 넣고, 그 코드가 커널의 자료구조를 안전하게 읽고, 결과를 사용자 공간으로 돌려준다. 게다가 그 코드는 검증기를 통과해야만 실행되므로, 잘못된 코드가 커널을 무너뜨릴 수 없다.
이는 30년 전이라면 미친 소리로 들렸을 것이다. 그러나 2025년의 Linux는 정확히 이 모델 위에서 모니터링, 보안, 네트워킹, 그리고 심지어 스케줄링까지 확장하고 있다. Cilium은 eBPF로 쿠버네티스 네트워킹 전체를 다시 썼다. Falco와 Tetragon은 eBPF로 런타임 보안을 한다. Datadog의 시스템 메트릭 수집 에이전트는 eBPF에 의존한다. Linux 6.12에서는 eBPF가 스케줄러까지 확장되었다 (sched_ext).
이 글은 eBPF를 처음 들어보는 사람부터 이미 BCC 도구를 써본 사람까지 모두를 위한 것이다. 1992년 cBPF의 14줄짜리 ISA에서 시작해, 2014년 Alexei Starovoitov의 첫 eBPF 패치, 검증기의 마법, JIT 컴파일, CO-RE, 그리고 모던 Linux의 모든 어태치 포인트까지 1,400줄로 정리한다.
이 글은 Linux 내부 구조 시리즈와 자매 작품이다. 시리즈는 "커널이 무엇을 하는가"를 다뤘다면, 이 글은 "사용자가 커널을 어떻게 확장할 수 있는가"를 다룬다.
1. 역사 — cBPF에서 eBPF까지
1.1 1992 — Berkeley Packet Filter
1992년, Steven McCanne과 Van Jacobson은 BSD 운영체제용 새 패킷 필터링 메커니즘을 발표했다. 이전 패킷 필터(CSPF)는 트리 기반 표현식이었고 매우 느렸다. 그들의 새 모델은 단순한 가상 머신이었다:
- 32비트 누산기(
A)와 인덱스 레지스터(X) - 16개의 32비트 스크래치 메모리 슬롯
- 패킷에서 데이터를 읽거나 슬롯과 비교하는 단순한 명령어들
- 점프 명령어로 결정 트리 표현
이 모델은 BSD에서 큰 성공을 거두었고, 곧 Linux와 Solaris로 포팅되었다. tcpdump, libpcap이 이 위에서 동작한다. 표현식 tcp port 80은 내부적으로 약 20개의 cBPF 명령어로 컴파일된다.
cBPF는 30년 동안 실질적으로 변하지 않았다. 단순했고, 잘 동작했고, 패킷 필터링이라는 좁은 영역에서는 충분했다.
1.2 2013 — Alexei Starovoitov의 첫 패치
2013년, PLUMgrid에서 일하던 Alexei Starovoitov는 LKML에 큰 패치를 제출했다. 제목: "extended BPF". 핵심 변경:
- 32비트 → 64비트 레지스터
- 2개 → 11개 레지스터 (
R0-R10) - 함수 호출 명령어
- 더 많은 산술/비트 연산
- x86_64에 매우 가까운 ISA — JIT 컴파일이 거의 1:1
처음에는 회의적인 반응이 많았다. "왜 BPF를 확장하나? 그건 패킷 필터링용 아닌가?" 그러나 Alexei의 비전은 더 컸다 — 사용자 공간이 안전하게 커널 안에서 실행할 수 있는 작은 코드의 일반 인터페이스.
1.3 2014 — Linux 3.18 메인라인
2014년 9월, Linux 3.18에 첫 eBPF 패치가 머지되었다. 처음에는 socket filter 용도였다 (cBPF의 직접 후속). 그러나 곧 매 릴리스마다 새 어태치 포인트가 추가되었다:
- 2014 (3.18): 기본 인프라, socket filter
- 2015 (3.19): kprobe 어태치
- 2015 (4.1): tc (traffic control) 어태치
- 2016 (4.4): tracepoint 어태치
- 2016 (4.8): XDP 어태치
- 2017 (4.10): cgroup 어태치, perf_event 어태치
- 2018 (4.15): BTF 도입 (CO-RE의 토대)
- 2018 (4.18): socket lookup
- 2019 (5.7): LSM 어태치 (KRSI)
- 2020 (5.8): BPF_RINGBUF
- 2021 (5.13): 스택 트레이스 BPF helper
- 2022 (5.15): bpf_loop helper
- 2023 (6.4): BPF stack walking 향상
- 2024 (6.12): sched_ext 메인라인 머지
오늘의 eBPF는 거의 모든 커널 서브시스템과 통합되어 있다. 30년 전 패킷 필터링용 작은 가상 머신이 Linux의 일부 운영 모델 자체를 바꿔놓았다.
★ Insight ─────────────────────────────────────
- 이름의 혼란: "eBPF"는 공식 이름이 아니다. 커널 코드는 그냥 "BPF"라고 부른다. 외부에서는 "extended BPF"의 줄임말 "eBPF"가 더 일반적이다. 옛날 BPF는 "classic BPF" 또는 "cBPF"로 구별한다.
- 왜 "패킷 필터"가 일반 가상 머신이 되었나: 핵심 통찰은 "검증된 안전한 코드를 커널 안에서 실행한다"는 모델이 패킷 필터링뿐 아니라 거의 모든 커널 확장에 적용 가능하다는 것이었다. cBPF가 가졌던 "자체 종료 보장 + 메모리 안전" 속성을 더 풍부한 ISA로 가져온 것이 eBPF.
- Linux 외부에는 거의 없다: BSD 진영에는 일부 eBPF 포팅이 있지만 (DTrace를 가진 BSD는 eBPF가 덜 매력적), Windows는 2021년부터 ebpf-for-windows라는 별도 프로젝트가 있다. 그러나 "eBPF는 곧 Linux"라고 해도 거의 맞다.
─────────────────────────────────────────────────
2. eBPF 가상 머신 — ISA와 레지스터
2.1 11개의 64비트 레지스터
eBPF VM은 매우 단순하다:
R0: 함수 반환값, 프로그램 종료값R1~R5: 함수 인자 (1-5)R6~R9: 호출자 저장 (callee-saved)R10: 스택 프레임 포인터 (read-only)
이 인터페이스는 의도적으로 x86_64의 calling convention과 매우 비슷하다. JIT 컴파일이 1:1에 가까워진다.
2.2 스택
각 프로그램은 512바이트의 스택을 가진다. R10이 그 base를 가리킨다. 작아 보이지만 검증기가 모든 사용을 추적해야 하므로 작게 잡혀 있다.
2.3 명령어 인코딩
eBPF 명령어는 64비트 고정 크기:
+----+----+----+--------+
| op | dst | src | offset | imm |
| 8 | 4 | 4 | 16 | 32 |
+----+----+----+--------+
op: 8비트 opcodedst/src: 4비트 레지스터 인덱스offset: 16비트 부호 있는 오프셋 (점프, 메모리 액세스)imm: 32비트 즉치값
2.4 명령어 카테고리
핵심 카테고리:
- ALU: 산술/논리 연산 (32비트와 64비트)
- Memory: load/store
- Branch: 조건 점프
- Call: 헬퍼 함수 호출
- Exit: 프로그램 종료
예시:
BPF_MOV64_IMM(BPF_REG_0, 0) // R0 = 0
BPF_MOV64_REG(BPF_REG_1, BPF_REG_10) // R1 = R10 (frame pointer)
BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, -8) // R1 += -8
BPF_LD_MAP_FD(BPF_REG_1, map_fd) // R1 = map fd
BPF_CALL_FUNC(BPF_FUNC_map_lookup_elem) // call helper
BPF_EXIT_INSN() // exit
2.5 200줄짜리 ISA
전체 eBPF ISA는 약 200줄의 C 코드로 표현 가능하다 (include/uapi/linux/bpf.h 참고). 매우 작다. 그러나 이 작은 ISA로 매우 풍부한 일을 할 수 있다.
3. eBPF 프로그램의 라이프사이클
3.1 작성 — C에서 BPF 바이트코드까지
전형적인 흐름:
// hello.bpf.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
char LICENSE[] SEC("license") = "GPL";
SEC("kprobe/sys_open")
int hello(struct pt_regs *ctx) {
char fmt[] = "Hello from kprobe!\n";
bpf_trace_printk(fmt, sizeof(fmt));
return 0;
}
컴파일:
clang -O2 -target bpf -c hello.bpf.c -o hello.bpf.o
-target bpf가 핵심. clang의 BPF 백엔드가 eBPF 명령어로 컴파일한다. 결과는 ELF 파일이고, 그 안에 BPF 바이트코드가 들어 있다.
3.2 로드 — bpf() 시스템 콜
ELF에서 바이트코드를 추출해 bpf(BPF_PROG_LOAD, ...) 시스템 콜로 커널에 보낸다. libbpf가 이 과정을 캡슐화한다:
struct bpf_object *obj = bpf_object__open("hello.bpf.o");
bpf_object__load(obj); // 검증 + JIT
struct bpf_program *prog = bpf_object__find_program_by_name(obj, "hello");
bpf_program__attach(prog); // kprobe에 어태치
3.3 검증기
bpf(BPF_PROG_LOAD, ...)이 호출되면 커널은 먼저 **검증기(verifier)**를 돌린다. 검증기는 프로그램이 안전한지 검사한다:
- 모든 메모리 액세스가 유효한가
- 모든 점프가 정의된 위치로 가는가
- 무한 루프는 없는가
- 헬퍼 호출이 적절한 컨텍스트에서 이루어지는가
- 스택 사용량이 한도 안인가
검증기는 일반적으로 5단계로 진행된다 (다음 절에서 자세히).
3.4 JIT 컴파일
검증을 통과하면 JIT 컴파일러가 BPF 바이트코드를 네이티브 머신 코드로 컴파일한다. x86_64에서는 거의 1:1 매핑. ARM64에서도 비슷하다.
JIT는 옵션이지만 거의 모든 모던 시스템에서 켜져 있다 (net.core.bpf_jit_enable=1). 인터프리터보다 약 10-100배 빠르다.
3.5 어태치
JIT된 프로그램을 특정 어태치 포인트에 연결한다. 예를 들어 kprobe/sys_open이라는 SEC 이름은 sys_open 함수의 진입점에 kprobe를 걸겠다는 의미다. libbpf가 BPF_PROG_ATTACH 시스템 콜을 호출해 연결한다.
이 시점부터 sys_open이 호출될 때마다 BPF 프로그램이 실행된다.
4. 검증기 — eBPF의 진짜 마법
4.1 무엇을 보장하는가
검증기는 다음을 정적으로 보장한다:
- 메모리 안전성: 모든 load/store가 유효한 영역
- 타입 안전성: 포인터를 정수처럼 취급하지 않음
- 종료 보장: 무한 루프 없음 (또는 명시적 bound)
- 스택 안전성: 스택 오버플로우 없음
- 호출 안전성: 헬퍼 호출이 적절한 인자와 컨텍스트로 이루어짐
이 모든 것을 코드를 실행하지 않고 정적 분석으로 보장한다. 이는 매우 어려운 문제다.
4.2 어떻게 동작하나 — 추상 해석
검증기의 핵심 알고리즘은 **추상 해석(abstract interpretation)**이다. 모든 가능한 실행 경로를 시뮬레이션하면서, 각 시점의 레지스터/스택 상태를 "추상 값"으로 추적한다.
추상 값의 예:
R0 = SCALAR_VALUE, range [0, 100]R1 = PTR_TO_MAP_VALUE, off 0..16R2 = PTR_TO_PACKET, off 14..1500R3 = NOT_INIT
각 명령어를 처리하면서 이 추상 값을 갱신한다. 분기에서는 양쪽 경로를 모두 탐색.
4.3 path explosion 회피 — pruning
순진하게 모든 경로를 탐색하면 지수 폭발이 일어난다. 검증기는 이미 본 상태를 기억하고 (states_cache), 같은 상태에 재진입하면 그 경로를 가지치기한다.
이는 매우 효과적이지만, 복잡한 프로그램에서는 여전히 10초 이상 검증에 걸릴 수 있다. 검증 상태 수가 백만 개를 넘어가면 검증기는 포기한다 (-EFBIG 또는 -ENOSPC).
4.4 메모리 액세스 검증
int *p = bpf_map_lookup_elem(&my_map, &key);
*p = 42; // 검증 실패!
이 코드는 검증을 실패한다. bpf_map_lookup_elem은 NULL을 반환할 수 있는데, NULL 체크 없이 역참조하기 때문이다. 올바른 코드:
int *p = bpf_map_lookup_elem(&my_map, &key);
if (p) {
*p = 42; // OK
}
검증기는 if (p)를 보고 그 분기 안에서 p가 NULL이 아니라는 사실을 추적한다. 그래서 *p 액세스가 안전함을 알 수 있다.
4.5 종료 보장
루프는 검증기의 골칫거리다. eBPF는 처음에는 루프를 아예 금지했다. 모든 루프는 컴파일 타임에 unroll되어야 했다.
5.3부터 bounded loops가 허용되었다. 검증기가 루프 카운트가 유한함을 증명할 수 있으면 OK:
#pragma unroll
for (int i = 0; i < 10; i++) {
/* ... */
}
5.13부터는 bpf_loop 헬퍼가 추가되어, 더 유연한 루프가 가능해졌다:
static int callback(__u32 idx, void *data) {
/* ... */
return 0; // 0 = continue, 1 = stop
}
bpf_loop(1000, callback, &my_data, 0);
4.6 스택 사용량
각 프로그램은 512바이트 스택을 가진다. 큰 구조체를 스택에 두면 빠르게 한도를 넘는다.
SEC("kprobe/sys_open")
int hello(struct pt_regs *ctx) {
char buf[600]; // 검증 실패! 스택 한도 초과
return 0;
}
대안: BPF_MAP_TYPE_PERCPU_ARRAY를 "큰 스크래치 영역"으로 사용.
4.7 검증기 디버깅
검증기가 실패하면 매우 긴 에러 메시지가 나온다. bpftool prog show + verifier_log_level=2로 자세히 볼 수 있다. 한 줄 한 줄 명령어와 함께 검증기의 추상 상태가 출력된다:
0: (b7) r1 = 0
R1_w=0 R10=fp0
1: (61) r2 = *(u32 *)(r1 +0)
R1 invalid mem access 'inv'
processed 2 insns ...
이 로그를 읽는 능력이 eBPF 개발자의 핵심 스킬이다.
5. BPF Maps — 사용자 공간과의 통신
5.1 무엇이 필요한가
BPF 프로그램은 휘발적이다 — 호출이 끝나면 모든 로컬 상태가 사라진다. 데이터를 영속화하거나 사용자 공간과 공유하려면 별도의 메커니즘이 필요하다. 그것이 BPF Maps.
Maps는 키-값 저장소다. BPF 프로그램이 헬퍼 함수로 접근하고, 사용자 공간이 시스템 콜로 접근한다.
5.2 Map 종류 (17가지+)
핵심 종류:
| 종류 | 용도 |
|---|---|
BPF_MAP_TYPE_HASH | 해시 테이블 (가장 일반적) |
BPF_MAP_TYPE_ARRAY | 고정 크기 배열, 인덱스 기반 |
BPF_MAP_TYPE_PERCPU_HASH | CPU별 해시 (락 없음) |
BPF_MAP_TYPE_PERCPU_ARRAY | CPU별 배열 |
BPF_MAP_TYPE_LRU_HASH | LRU 회수가 있는 해시 |
BPF_MAP_TYPE_LPM_TRIE | Longest prefix match trie (라우팅용) |
BPF_MAP_TYPE_PROG_ARRAY | BPF 프로그램 배열 (tail call용) |
BPF_MAP_TYPE_PERF_EVENT_ARRAY | perf 이벤트 출력용 |
BPF_MAP_TYPE_RINGBUF | 새로운 ring buffer (5.8+) |
BPF_MAP_TYPE_QUEUE | FIFO |
BPF_MAP_TYPE_STACK | LIFO |
BPF_MAP_TYPE_SK_STORAGE | 소켓별 저장소 |
BPF_MAP_TYPE_TASK_STORAGE | 태스크별 저장소 |
BPF_MAP_TYPE_INODE_STORAGE | inode별 저장소 |
BPF_MAP_TYPE_CGROUP_STORAGE | cgroup별 저장소 |
BPF_MAP_TYPE_BLOOM_FILTER | 블룸 필터 |
BPF_MAP_TYPE_USER_RINGBUF | 사용자 → 커널 ringbuf (5.19+) |
5.3 Hash Map 예제
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, __u32);
__type(value, __u64);
} counter_map SEC(".maps");
SEC("kprobe/sys_open")
int count_opens(struct pt_regs *ctx) {
__u32 pid = bpf_get_current_pid_tgid() >> 32;
__u64 *count = bpf_map_lookup_elem(&counter_map, &pid);
if (count) {
__sync_fetch_and_add(count, 1);
} else {
__u64 init = 1;
bpf_map_update_elem(&counter_map, &pid, &init, BPF_ANY);
}
return 0;
}
이 프로그램은 매 sys_open 호출마다 PID별 카운터를 증가시킨다. 사용자 공간에서는 bpf_map__lookup_elem으로 같은 맵을 조회한다.
5.4 PERCPU 변형 — 락 없는 카운터
BPF_MAP_TYPE_PERCPU_HASH는 각 CPU별로 별도의 해시 테이블을 가진다. 락이 필요 없다 — 같은 CPU의 BPF 프로그램만 그 CPU의 맵에 접근하기 때문.
사용자 공간이 PERCPU 맵을 읽을 때는 모든 CPU의 값을 다 받아온다. 합치는 것은 사용자 공간의 책임.
PERCPU 맵은 카운터, 통계, 히스토그램에 매우 유용하다. 락 경쟁이 없어서 매우 빠르다.
5.5 RINGBUF — 새로운 이벤트 출력
전통적으로 BPF 프로그램이 사용자 공간으로 이벤트를 보낼 때는 BPF_MAP_TYPE_PERF_EVENT_ARRAY를 썼다. 이는 CPU별 perf 링 버퍼로, 각 CPU에 별도의 ring을 가진다.
5.8에서 도입된 BPF_MAP_TYPE_RINGBUF는 더 우아하다:
- 단일 공유 ring buffer (CPU 간 분배 없음)
- 더 적은 메모리
- BPF 프로그램이 가변 크기 이벤트를 직접 reserve/commit 가능
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024);
} events SEC(".maps");
SEC("tp/sched/sched_process_exec")
int on_exec(struct trace_event_raw_sched_process_exec *ctx) {
struct event *e = bpf_ringbuf_reserve(&events, 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_ringbuf_submit(e, 0);
return 0;
}
대부분의 새 BPF 도구는 ringbuf를 쓴다. perf event array는 호환성을 위해 남아 있다.
6. 헬퍼 함수 — 200개가 넘는 커널 인터페이스
6.1 헬퍼란
BPF 프로그램은 임의의 커널 함수를 호출할 수 없다. 대신 검증된 "헬퍼 함수" 집합만 호출 가능하다. 헬퍼는 커널이 명시적으로 노출한 안전한 인터페이스이다.
5.0 시점에 약 100개였던 헬퍼는 6.x 시점에 200개를 훨씬 넘는다.
6.2 핵심 헬퍼들
가장 자주 쓰는 것들:
| 헬퍼 | 용도 |
|---|---|
bpf_map_lookup_elem | 맵에서 값 읽기 |
bpf_map_update_elem | 맵에 값 쓰기 |
bpf_map_delete_elem | 맵에서 값 삭제 |
bpf_get_current_pid_tgid | 현재 PID/TID |
bpf_get_current_uid_gid | 현재 UID/GID |
bpf_get_current_comm | 현재 프로세스 이름 |
bpf_get_current_task | 현재 task_struct 포인터 |
bpf_ktime_get_ns | 단조 시간 (나노초) |
bpf_trace_printk | 디버그 printf (/sys/kernel/debug/tracing/trace_pipe) |
bpf_perf_event_output | perf event array에 이벤트 출력 |
bpf_ringbuf_reserve/bpf_ringbuf_submit | ringbuf 출력 |
bpf_get_stack/bpf_get_stackid | 스택 트레이스 |
bpf_probe_read_kernel/bpf_probe_read_user | 안전한 메모리 읽기 |
bpf_skb_load_bytes | 패킷에서 바이트 읽기 |
bpf_redirect | 패킷 리다이렉션 |
bpf_xdp_adjust_head | XDP 패킷 헤더 조정 |
bpf_jiffies64 | 현재 jiffies |
bpf_send_signal | 시그널 보내기 (5.3+) |
6.3 헬퍼의 안전성
각 헬퍼는 자기 인자 타입을 명시한다. 검증기가 호출 시점에 인자 타입을 검사한다.
예를 들어 bpf_map_lookup_elem의 시그니처:
void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)
검증기는 map이 BPF_MAP_TYPE_HASH 같은 타입의 포인터인지, key가 그 맵의 key_size만큼의 메모리를 가리키는지 확인한다.
6.4 GPL과 비-GPL 헬퍼
일부 헬퍼는 GPL-only이다. 비-GPL 라이선스의 BPF 프로그램은 호출할 수 없다. bpf_trace_printk가 그렇다 (디버깅용이므로).
char LICENSE[] SEC("license") = "GPL"; // GPL helper 사용 가능
상업 BPF 도구의 대부분은 GPL이다.
7. Attach Points — 어디에 붙일 수 있나
eBPF의 진짜 힘은 다양한 어태치 포인트에서 나온다. 각 어태치 포인트는 자기만의 "context"(인자)와 헬퍼 집합을 가진다.
7.1 kprobe / kretprobe
거의 모든 커널 함수의 진입/리턴 지점에 어태치할 수 있다.
SEC("kprobe/vfs_read")
int on_vfs_read(struct pt_regs *ctx) {
/* ... */
return 0;
}
진입점에서는 pt_regs를 통해 인자에 접근. 리턴점에서는 PT_REGS_RC(ctx)로 반환값에 접근.
장점: 거의 모든 커널 함수에 붙을 수 있음. 단점: 함수 시그니처가 커널 버전마다 다를 수 있음. CO-RE로 일부 완화.
7.2 fentry / fexit (BPF Trampoline)
5.5에서 도입된 더 빠른 대안. kprobe는 INT3 명령어를 끼워 넣지만, fentry/fexit는 ftrace 인프라를 활용해 직접 호출한다. 약 10배 빠르다.
SEC("fentry/vfs_read")
int BPF_PROG(on_vfs_read_entry, struct file *file, char *buf, size_t count) {
/* 인자에 직접 접근 가능, BTF 덕분 */
return 0;
}
7.3 tracepoint
커널 코드에 미리 박힌 안정적인 추적 포인트. kprobe와 달리 함수 이름이 변해도 깨지지 않는다.
SEC("tp/sched/sched_process_exec")
int on_exec(struct trace_event_raw_sched_process_exec *ctx) {
/* tracepoint 인자에 직접 접근 */
return 0;
}
/sys/kernel/debug/tracing/events/에서 모든 사용 가능한 tracepoint를 볼 수 있다.
7.4 raw_tracepoint
tracepoint의 더 빠른 버전. tracepoint 인자를 디코딩하지 않고 그대로 받는다 — 약간의 코드 작성 부담이 있지만 더 빠르다.
7.5 uprobe / uretprobe
사용자 공간 함수에도 붙일 수 있다. 예: libc의 malloc에 어태치해서 모든 호출 추적.
SEC("uprobe//usr/lib/libc.so.6:malloc")
int on_malloc(struct pt_regs *ctx) {
size_t size = PT_REGS_PARM1(ctx);
/* ... */
return 0;
}
매우 강력하지만 비싸다 — 매 호출마다 사용자→커널 트랩이 일어난다.
7.6 perf_event
perf 이벤트 (CPU 사이클, 캐시 미스 등)에 어태치. CPU 프로파일링의 토대.
SEC("perf_event")
int sample(struct bpf_perf_event_data *ctx) {
/* 매 N 사이클마다 호출됨 */
return 0;
}
7.7 XDP — eXpress Data Path
네트워크 인터페이스의 수신 경로 가장 앞에 어태치. 패킷이 sk_buff로 변환되기 전, 드라이버 직후에 호출된다. 매우 빠르다.
SEC("xdp")
int xdp_prog(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
if (data + sizeof(struct ethhdr) > data_end)
return XDP_PASS;
struct ethhdr *eth = data;
if (eth->h_proto == bpf_htons(ETH_P_IP)) {
/* IP packet */
}
return XDP_PASS; // 또는 XDP_DROP, XDP_TX, XDP_REDIRECT
}
XDP 액션:
XDP_PASS: 일반 네트워크 스택으로XDP_DROP: 드롭XDP_TX: 같은 NIC로 다시 전송XDP_REDIRECT: 다른 NIC로 전송XDP_ABORTED: 에러
XDP는 DDoS 보호, 로드 밸런싱, 패킷 재작성에 매우 인기 있다. Cloudflare가 자기 인프라에 XDP를 광범위하게 사용한다.
7.8 tc (Traffic Control)
XDP보다 약간 늦은 단계에서 어태치. sk_buff가 이미 만들어졌으므로 더 풍부한 정보를 가진다 (cgroup, socket 등). XDP만큼 빠르지는 않지만 더 유연.
tc-bpf로 어태치:
tc qdisc add dev eth0 clsact
tc filter add dev eth0 ingress bpf da obj my_prog.bpf.o
7.9 cgroup hooks
cgroup 안의 모든 프로세스의 특정 시스템 콜에 어태치 가능. 예를 들어 한 cgroup의 모든 connect 호출을 막는 등:
SEC("cgroup/connect4")
int restrict_connect(struct bpf_sock_addr *ctx) {
if (ctx->user_port == bpf_htons(22)) {
return 0; // SSH 차단
}
return 1;
}
컨테이너 보안 정책의 토대이다.
7.10 LSM hooks (KRSI)
5.7에서 도입. Linux Security Module의 모든 후크에 BPF 프로그램을 끼울 수 있다. SELinux/AppArmor를 BPF로 대체하거나 보강할 수 있다.
SEC("lsm/file_open")
int BPF_PROG(check_file_open, struct file *file, int ret) {
/* 파일 열기를 검사하고 거부할 수 있음 */
return -EPERM; // 또는 0 = OK
}
Tetragon이 이 모델 위에서 작동한다.
7.11 sched_ext (6.12+)
가장 새로운 어태치 포인트. 사용자 공간이 BPF로 스케줄링 정책을 작성할 수 있게 한다. Linux 스케줄러 글에서 자세히 다뤘다.
7.12 socket lookup, sock_ops, sk_msg
소켓 처리의 다양한 단계에 어태치할 수 있다. Cilium의 사이드카 없는 서비스 메시가 이를 활용한다.
8. CO-RE — Compile Once, Run Everywhere
8.1 문제
eBPF 프로그램은 종종 커널 자료구조 (task_struct, sk_buff 등)를 읽는다. 그러나 이 구조체의 레이아웃은 커널 버전, 컴파일 옵션마다 다르다. 빌드한 머신과 실행할 머신이 다르면 깨진다.
옛날 BCC는 이 문제를 "런타임에 컴파일"로 해결했다. 사용자 머신에 clang과 커널 헤더를 모두 설치하고, 매 실행마다 컴파일했다. 느렸고, 디스크 공간을 많이 먹었고, 운영에 부적합.
8.2 BTF — BPF Type Format
BTF는 커널 자료구조의 메타데이터를 임베드하는 경량 디버깅 정보 포맷이다. DWARF의 단순화 버전. 5.2부터 커널이 자기 BTF를 /sys/kernel/btf/vmlinux에 노출한다.
BTF는 모든 커널 구조체의 필드 이름과 오프셋을 포함한다. 사용자 공간 도구가 이를 읽으면 "이 커널에서 task_struct->mm은 어디에 있는가"를 알 수 있다.
8.3 CO-RE의 동작
CO-RE는 BTF를 기반으로 BPF 프로그램이 다른 커널 버전에서도 동작하게 한다.
#include <vmlinux.h> // 호스트 커널의 BTF에서 생성한 헤더
#include <bpf/bpf_core_read.h>
SEC("kprobe/sys_open")
int hello(struct pt_regs *ctx) {
struct task_struct *task = (void *)bpf_get_current_task();
pid_t pid = BPF_CORE_READ(task, pid); // 매크로 마법
/* ... */
return 0;
}
BPF_CORE_READ 매크로는 컴파일 타임에 "이 필드의 오프셋"을 직접 인라인하지 않는다. 대신 "필드 위치를 BTF로 lookup하라"는 재배치(relocation) 정보를 ELF에 남긴다.
런타임에 libbpf가 그 재배치를 처리한다 — 호스트 커널의 BTF를 보고 실제 오프셋을 채워 넣는다. 같은 BPF ELF가 5.10 커널과 6.5 커널에서 모두 동작한다.
8.4 vmlinux.h 생성
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
이 파일은 약 4MB이고 모든 커널 구조체를 정의한다. BPF 코드는 이를 include한다.
8.5 Field existence 검사
CO-RE의 또 다른 기능: 필드가 존재하는지 검사. 필드가 새로 추가/제거되었을 때 유연하게 대응.
if (bpf_core_field_exists(task->cgroups)) {
/* 이 필드가 있는 커널 */
} else {
/* 없는 커널 */
}
8.6 영향
CO-RE 덕분에 BPF 도구가 정말로 portable해졌다. Falco, Cilium, Tetragon, Pixie 같은 모던 도구는 모두 CO-RE를 쓴다. 한 번 빌드한 바이너리가 어떤 커널에서도 동작한다 (BTF가 있다는 전제 하에).
9. 사용자 공간 도구 — libbpf, BCC, bpftrace
9.1 BCC — 옛날 방식
BCC(BPF Compiler Collection)는 가장 오래된 BPF 도구셋이다. Python/Lua wrapper로 BPF 프로그램을 쉽게 짤 수 있게 해준다.
from bcc import BPF
prog = """
int hello(void *ctx) {
bpf_trace_printk("Hello!\\n");
return 0;
}
"""
b = BPF(text=prog)
b.attach_kprobe(event="sys_open", fn_name="hello")
b.trace_print()
문제: 매 실행마다 컴파일. clang + 커널 헤더 필요. 큰 디스크 공간. 운영 환경에 부적합.
BCC는 여전히 많은 도구에서 쓰이고, 예제 보관소로서 가치가 크다 (/usr/share/bcc/tools/에 200개 이상의 도구가 있다).
9.2 libbpf — 모던 방식
libbpf는 C 라이브러리로, BPF 프로그램의 로드/어태치를 캡슐화한다. CO-RE를 지원한다.
#include <bpf/libbpf.h>
int main() {
struct bpf_object *obj = bpf_object__open_file("hello.bpf.o", NULL);
bpf_object__load(obj);
struct bpf_program *prog = bpf_object__find_program_by_name(obj, "hello");
bpf_program__attach(prog);
while (1) sleep(1);
return 0;
}
빌드 후 한 번만 배포하면 된다. Datadog, Cilium, Tetragon이 모두 libbpf 기반.
9.3 bpftrace — DSL
가장 빠른 입문 도구. awk와 비슷한 DSL로 BPF 프로그램을 한 줄로 짠다.
# 매 sys_open 호출 카운트
bpftrace -e 'kprobe:sys_open { @[comm] = count(); }'
# vfs_read의 latency 분포 (히스토그램)
bpftrace -e '
kprobe:vfs_read { @start[tid] = nsecs; }
kretprobe:vfs_read /@start[tid]/ {
@lat = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
# 스택 트레이스가 포함된 페이지 폴트 추적
bpftrace -e 'tracepoint:exceptions:page_fault_user { @[ustack] = count(); }'
bpftrace는 내부적으로 libbpf를 쓴다. DSL을 BPF C 코드로 변환하고, clang으로 컴파일하고, libbpf로 로드한다.
9.4 어떤 도구를 쓸 것인가
- 즉석 진단: bpftrace
- 상용 도구 / 오랫동안 돌릴 에이전트: libbpf (C/Rust/Go)
- 참고용 / 기존 BCC 도구 활용: BCC
대부분의 새 BPF 코드는 libbpf로 이주하고 있다. BCC는 점점 "예제 보관소" 역할로 한정되고 있다.
10. 사례 1 — bpftrace 한 줄 진단
bpftrace의 강력함을 보여주는 실전 예제들:
10.1 어떤 프로세스가 디스크를 가장 많이 읽나
bpftrace -e '
tracepoint:block:block_rq_issue { @[comm] = sum(args->bytes); }
'
10.2 누가 시스템 콜을 가장 많이 호출하나
bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
10.3 TCP retransmission 추적
bpftrace -e '
kprobe:tcp_retransmit_skb {
@[comm] = count();
}
'
10.4 어떤 함수가 가장 오래 걸리나
bpftrace -e '
kprobe:vfs_read { @start[tid] = nsecs; }
kretprobe:vfs_read /@start[tid]/ {
$duration = nsecs - @start[tid];
@hist = hist($duration / 1000);
delete(@start[tid]);
}'
10.5 OOM kill 시 전체 컨텍스트
bpftrace -e '
kprobe:oom_kill_process {
printf("OOM kill: comm=%s pid=%d ustack=%s\n",
comm, pid, ustack);
}'
각 한 줄이 정교한 도구가 했어야 할 일을 한다. 운영 디버깅의 새 기준점.
11. 사례 2 — XDP DDoS 방어
11.1 시나리오
UDP 플러드 공격을 받고 있다. 초당 수백만 개의 패킷이 들어오고 NIC가 마비되고 있다. 방어 코드는 패킷이 sk_buff로 변환되기 전에 작동해야 한다.
11.2 XDP 프로그램
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
struct {
__uint(type, BPF_MAP_TYPE_LPM_TRIE);
__type(key, struct bpf_lpm_trie_key);
__type(value, __u32);
__uint(max_entries, 1024);
__uint(map_flags, BPF_F_NO_PREALLOC);
} blacklist SEC(".maps");
SEC("xdp")
int xdp_drop(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end) return XDP_PASS;
if (eth->h_proto != bpf_htons(ETH_P_IP)) return XDP_PASS;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end) return XDP_PASS;
/* IP를 LPM trie에서 lookup */
struct {
__u32 prefixlen;
__u32 addr;
} key = { .prefixlen = 32, .addr = ip->saddr };
if (bpf_map_lookup_elem(&blacklist, &key)) {
return XDP_DROP; // 블랙리스트면 즉시 드롭
}
return XDP_PASS;
}
char LICENSE[] SEC("license") = "GPL";
11.3 어태치
ip link set dev eth0 xdpgeneric obj xdp_drop.bpf.o sec xdp
11.4 결과
이 프로그램은 패킷이 sk_buff로 변환되기 전에 드롭한다. 약 10배 빠르다 (24Mpps vs 2.5Mpps 같은 수치). Cloudflare가 자기 인프라에 매우 비슷한 패턴을 사용한다.
XDP가 드롭한 패킷은 시스템 metric에 거의 영향을 주지 않는다 — sk_buff alloc도 하지 않으므로 메모리도 안 쓰고 CPU도 거의 안 쓴다.
12. 사례 3 — Cilium의 쿠버네티스 네트워킹
12.1 비전
Cilium은 쿠버네티스의 네트워킹/보안/관찰성을 eBPF로 다시 쓰는 프로젝트다. iptables 기반의 kube-proxy를 완전히 대체한다.
12.2 무엇이 다른가
전통적인 쿠버네티스 네트워킹:
- iptables 규칙 수만 개 (서비스 수에 비례)
- 새 서비스마다 모든 노드에서 iptables 갱신
- 패킷 처리에 conntrack, NAT, 라우팅 모두 거침
- 큰 클러스터에서는 정말 느림
Cilium의 모델:
- BPF maps에 서비스/엔드포인트 정보 저장
- 패킷 처리는 BPF 프로그램이 직접 수행
- iptables 거의 필요 없음
- 큰 클러스터에서도 일관된 성능
12.3 사이드카 없는 서비스 메시
Cilium은 sock_ops와 sk_msg를 활용해 사이드카 프록시 없이 L7 통신을 가로챌 수 있다. 전통적인 Istio/Linkerd 모델은 매 파드에 Envoy를 사이드카로 띄우는데, 이는 메모리/CPU/지연 비용이 크다.
Cilium의 sidecarless 모델은 노드별로 한 개의 프록시 (또는 0개)만 띄우고, BPF로 트래픽을 그 프록시로 리다이렉트한다.
12.4 Tetragon — 보안
Cilium 팀이 만든 또 다른 도구. LSM 후크와 tracepoint를 활용해 모든 컨테이너 활동을 모니터링한다. "이 컨테이너가 /etc/passwd를 읽었다"를 실시간으로 알 수 있다.
전통적인 Falco와 비슷하지만 더 깊다. 정책 위반 시 시그널을 보내거나 즉시 차단할 수 있다.
★ Insight ─────────────────────────────────────
- eBPF가 만든 새로운 회사들: Isovalent (Cilium 회사, 2024년 Cisco가 인수), Polar Signals (continuous profiling), Pixie (관찰성), Groundcover, Levitate Security. 모두 eBPF가 가능하게 한 새 카테고리이다.
- iptables 시대의 종료: 쿠버네티스 인프라에서 iptables는 이제 레거시로 취급된다. nftables가 일부 대체했지만, 진짜 미래는 eBPF다. Cilium이 사실상 표준이 되어가고 있다.
- 사이드카 없는 메시의 의미: 쿠버네티스 클러스터에서 사이드카 프록시는 종종 노드 메모리의 30% 이상을 차지한다. eBPF로 대체하면 그 메모리가 해방된다. 이는 비용 측면에서 매우 큰 차이다.
─────────────────────────────────────────────────
13. 사례 4 — Falco 런타임 보안
Falco는 sysdig가 만든 런타임 보안 도구다. 컨테이너의 비정상 동작을 감지해 알람을 보낸다. 예: /etc/shadow를 읽는 컨테이너, root로 escalate 시도, suspicious shell spawn 등.
전통적으로는 sysdig 커널 모듈을 사용했지만, 최신 버전은 eBPF로 이전했다. 모든 시스템 콜을 가로채고 규칙 엔진에 보낸다.
# Falco 규칙 예시
- rule: Read sensitive file
desc: An attempt to read sensitive file
condition: open_read and sensitive_files
output: Sensitive file opened (user=%user.name file=%fd.name)
priority: WARNING
eBPF 덕분에 커널 모듈이 필요 없고, 어떤 커널 버전에서도 동작한다.
14. 사례 5 — bpftune 자동 튜닝
Oracle이 만든 도구. eBPF로 시스템 메트릭을 모니터링하고, 자동으로 sysctl 값을 조정한다. 예:
- TCP 연결이 자주 timeout →
tcp_keepalive_time줄임 - 메모리 압박 자주 발생 →
vm.swappiness조정 - 디스크 IO bottleneck → readahead 크기 늘림
전통적으로 시스템 튜닝은 사람이 수동으로 했다. bpftune은 이를 데이터 기반으로 자동화한다. eBPF가 없었다면 매 메트릭마다 별도의 도구를 띄워야 했을 것이다.
15. 보안 — eBPF의 위험
eBPF는 강력한 만큼 잘못된 손에 들어가면 위험하다.
15.1 BPF 권한
BPF 프로그램 로드는 보통 CAP_BPF (5.8+)와 CAP_PERF_MON 또는 CAP_NET_ADMIN이 필요하다. 이전에는 CAP_SYS_ADMIN (root 권한과 거의 동일)이 필요했다.
15.2 검증기 우회
검증기는 정적 분석이고, 100% 완벽하지 않다. 과거 몇 차례의 CVE가 검증기를 속여 임의 메모리 읽기/쓰기를 가능하게 했다:
- CVE-2022-23222: BPF pointer arithmetic 검증 결함
- CVE-2021-45402: 32비트 분기 검증 결함
- CVE-2021-3490: ALU32 boundary tracking 결함
이런 결함은 발견될 때마다 빠르게 수정되지만, 검증기가 점점 복잡해지면서 새 결함의 가능성도 늘어난다.
15.3 unprivileged_bpf_disabled
Linux는 일반 사용자의 BPF 사용을 기본적으로 막을 수 있다:
echo 1 > /proc/sys/kernel/unprivileged_bpf_disabled
대부분의 배포판이 이를 기본으로 켠다. 일반 사용자는 BPF 프로그램을 로드할 수 없다.
15.4 BPF LSM과 BPF 자체 보호
BPF LSM이 있다는 것은 BPF로 BPF를 제어할 수 있다는 의미이다. "이 cgroup의 BPF 프로그램 로드는 거부한다" 같은 정책을 BPF로 표현할 수 있다.
15.5 사이드 채널
BPF 프로그램은 어떤 권한 정보도 노출할 수 있다. Spectre 같은 사이드 채널 공격이 BPF로 가능하다는 연구가 있다. 검증기는 일부 우려스러운 패턴을 거부하지만, 완벽한 방어는 어렵다.
16. 디버깅 — bpftool
bpftool은 BPF 인프라의 만능 도구이다.
16.1 로드된 프로그램 보기
bpftool prog list
1: kprobe name hello tag a1b2c3d4e5f60718
loaded_at 2026-04-15T10:30:00+0900 uid 0
xlated 200B jited 256B memlock 4096B
btf_id 5
16.2 프로그램 BPF 코드 덤프
bpftool prog dump xlated id 1
검증을 통과한 BPF 명령어들을 보여준다.
bpftool prog dump jited id 1
JIT된 네이티브 코드를 보여준다.
16.3 맵 보기
bpftool map list
bpftool map dump id 5
16.4 BTF 덤프
bpftool btf dump file /sys/kernel/btf/vmlinux | less
16.5 검증기 로그
프로그램 로드 시 자세한 검증기 로그를 보고 싶으면:
bpftool prog load my.bpf.o /sys/fs/bpf/my_prog --log_level 7
17. 미래 — eBPF의 다음 단계
17.1 sched_ext
Linux 스케줄러 글에서 다뤘다. 사용자 공간이 BPF로 스케줄링 정책을 작성할 수 있게 한다. 6.12에서 메인라인 머지.
17.2 struct_ops
BPF 프로그램이 커널 인터페이스의 구현체가 될 수 있게 한다. 예를 들어 bpf_struct_ops 메커니즘으로 TCP congestion control 알고리즘을 BPF로 구현할 수 있다.
17.3 BPF for filesystem operations
파일시스템 후크에 BPF를 어태치할 수 있는 작업이 진행 중이다. 사용자 공간 정의 파일시스템 정책 (예: 캐시 정책, 배치 정책)이 가능해진다.
17.4 BPF in eBPF
자기 자신을 호출하는 BPF? 일부 추상화 작업에서 가능. tail call의 일반화.
17.5 다른 OS로의 확장
- ebpf-for-windows: Microsoft가 후원. Windows 커널에 eBPF 인프라를 가져오려는 시도.
- uBPF: 사용자 공간에서 BPF VM을 돌리는 라이브러리. AWS Firecracker가 사용.
eBPF가 OS 경계를 넘는 표준이 될 가능성이 있다.
18. 결론 — eBPF는 끝나지 않는다
이 글을 다 읽었다면, 다음 질문에 답할 수 있을 것이다:
- eBPF는 무엇이고 cBPF와 무엇이 다른가?
- 검증기는 어떻게 안전성을 보장하는가?
- BPF Map은 어떻게 사용자 공간과 통신하나?
- 어떤 어태치 포인트들이 있나?
- CO-RE는 무엇을 푸는가?
- libbpf, BCC, bpftrace의 차이는?
- XDP는 어떻게 빠른가?
- Cilium이 무엇을 다시 썼는가?
그러나 이 글은 시작에 불과하다. eBPF는 매년 새 기능이 들어오고, 매년 새 영역으로 확장된다. 1년 후의 eBPF는 오늘과 매우 다를 것이다.
eBPF를 배우는 가장 좋은 방법은 직접 해보는 것이다:
bpftrace한 줄 명령어로 시스템 진단 시작- BCC
/usr/share/bcc/tools/의 도구들을 읽고 수정 - libbpf 예제로 자기 도구 작성
- 검증기 에러를 만나면 한 줄 한 줄 읽기
이 단계를 거치면 eBPF가 더 이상 마법이 아니라, 강력하지만 이해 가능한 도구로 보이게 된다.
이 글로 Linux 내부 구조 시리즈와의 자매 작품도 마무리된다. 시리즈가 "커널이 무엇을 하는가"를 다뤘다면, 이 글은 "사용자가 커널을 어떻게 안전하게 확장하는가"를 다뤘다. 둘이 모이면 모던 Linux 시스템의 기본 정신이 그려진다.
다음 글에서는 [Cilium 내부 구조 딥다이브] 또는 [BPF로 만든 새 카테고리 도구들]을 다룰 예정이다.
부록 A — 참고 자료
- eBPF.io — eBPF 입문 허브.
- Linux Kernel Documentation: BPF — 공식 커널 문서.
- Brendan Gregg, "BPF Performance Tools" — BPF의 사실상 표준 교과서.
- Liz Rice, "Learning eBPF" — 입문서.
- Quentin Monnet의 BPF 블로그 — BPF 내부 구조 글들.
- Cilium Documentation — Cilium 사용/내부 구조.
- libbpf-bootstrap — libbpf 예제 모음, 가장 좋은 시작점.
- bpftrace one-liners — 한 줄 명령어 대백과.
- BCC tools — 200개 이상의 즉시 사용 가능한 도구.
부록 B — 자주 묻는 질문
Q: eBPF를 배우려면 어떻게 시작해야 하나? A: bpftrace로 시작. 한 줄 명령어를 따라하다 보면 자연스럽게 BPF 모델을 익히게 된다. 그 후 BCC 도구의 코드를 읽고, 마지막으로 libbpf-bootstrap으로 자기 도구 작성.
Q: eBPF 프로그램이 커널을 무너뜨릴 수 있나? A: 검증기가 실수로 통과시킨 결함이 있다면 가능. 그러나 이는 매우 드물고 빠르게 패치된다. 정상적인 사용에서는 BPF가 커널 충돌을 일으키지 않는다.
Q: kprobe와 tracepoint, 어떤 것을 써야 하나? A: tracepoint가 있으면 tracepoint. 안정적이고 빠르다. tracepoint가 없는 곳에는 kprobe (또는 fentry).
Q: BCC와 libbpf 중 무엇을 쓸 것인가? A: 새 코드는 무조건 libbpf. BCC는 옛 도구 유지 보수 또는 학습용.
Q: XDP와 tc 중 무엇이 더 빠른가? A: XDP. 패킷이 sk_buff로 변환되기 전에 처리. tc는 sk_buff 이후라서 약간 느리지만 더 풍부한 정보를 가진다.
Q: Cilium은 정말 iptables를 완전히 대체하나? A: 거의 그렇다. Cilium 모드에서는 kube-proxy의 iptables 규칙을 만들지 않는다. 다만 host의 일부 기본 규칙은 여전히 있을 수 있다.
Q: eBPF와 DTrace의 관계는? A: DTrace는 Solaris의 동적 추적 인프라. 비슷한 모델이지만 더 일찍 만들어졌고 다른 길을 갔다. eBPF는 cBPF에서 출발했고 더 일반적이다. 오늘날의 eBPF는 DTrace가 했던 거의 모든 일을 할 수 있다.
Q: eBPF가 Java/Go GC pause를 추적할 수 있나? A: 가능. uprobe로 GC 진입/리턴 함수에 어태치하면 GC 시간을 측정할 수 있다. JVM Flight Recorder 대안으로 사용 가능.
부록 C — 미니 용어집
- BPF: Berkeley Packet Filter. 1992년 BSD에서 시작.
- cBPF: classic BPF. 옛날 패킷 필터 ISA.
- eBPF: extended BPF. 2014+ 모던 BPF.
- Verifier: BPF 프로그램의 안전성을 정적 검증하는 모듈.
- JIT: Just-In-Time 컴파일러. BPF 바이트코드를 네이티브 코드로 변환.
- BTF: BPF Type Format. 커널 자료구조의 메타데이터.
- CO-RE: Compile Once, Run Everywhere. BTF 기반 portable BPF.
- libbpf: BPF 프로그램 로드/어태치 라이브러리.
- BCC: BPF Compiler Collection. 옛날 BPF 도구셋.
- bpftrace: BPF용 awk-like DSL.
- bpftool: BPF 인프라 디버깅 도구.
- kprobe: 커널 함수 진입점에 후크.
- kretprobe: 커널 함수 리턴점에 후크.
- fentry/fexit: ftrace trampoline 기반의 빠른 kprobe 대체.
- tracepoint: 커널에 박힌 안정적인 추적 포인트.
- uprobe: 사용자 공간 함수에 후크.
- XDP: eXpress Data Path. NIC 드라이버 직후의 BPF 후크.
- tc: Traffic Control. sk_buff 이후의 BPF 후크.
- LSM: Linux Security Module. 보안 후크.
- KRSI: Kernel Runtime Security Instrumentation. BPF LSM의 다른 이름.
- sched_ext: BPF로 작성하는 스케줄러 (6.12+).
- struct_ops: BPF가 커널 인터페이스의 구현체가 되는 메커니즘.
- PERCPU map: CPU별로 분리된 맵. 락 없음.
- RINGBUF: 새로운 BPF event ring buffer (5.8+).
- BPF tail call: BPF 프로그램이 다른 BPF 프로그램으로 점프.
- Cilium: BPF로 다시 쓴 쿠버네티스 네트워킹/보안.
- Tetragon: BPF 기반 런타임 보안 (Cilium 팀).
- Falco: BPF 기반 런타임 보안 (Sysdig).
- bpftune: BPF로 자동 시스템 튜닝 (Oracle).
이 글이 Linux 내부 구조 시리즈와의 자매 작품이다. 시리즈는 커널이 사용자에게 해주는 일을 다뤘다. 이 글은 사용자가 안전하게 커널 안으로 들어가는 방법을 다뤘다. 두 풍경이 모이면 모던 Linux의 정신이 그려진다.