cilium/ebpf是开发ebpf用户态程序的前端框架,通过它可以方便将ebpf程序加载到内核并挂载执行。
cilium/ebpf提供了bpf2go工具,可以通过go generate+bpf2go工具,将c语言编写的ebpf程序,生成bpf.go和bpf.o。
在开发ebpf用户态程序时,可以调用生成的bpf.go中的函数,完成ebpf程序的加载、挂载和执行。
下面以cilium/ebpf/examples下面的tracepoint_in_c为例,讲解其使用过程。
一.bpf2go工具
cilium/ebpf实现了bpf2go工具,代码入口在cmd/bpf2go/main.go中。
可以通过go build命令生成bpf2go:
# go build -o cmd/ebpf2go cmd/bpf2go/*.go
二.内核态程序
内核态的ebpf程序使用c编写:
- 监听了内存分配函数:kmem/mm_page_alloc;
- tracepoint被触发时,将map中的计数器+1;
map的定义:
// tracepoint.c
struct bpf_map_def SEC("maps") counting_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(u32),
.value_size = sizeof(u64),
.max_entries = 1,
};
监听点和处理函数:
发生内存分配时,更新map的key对应的value,值+1;
// tracepoint.c SEC("tracepoint/kmem/mm_page_alloc") int mm_page_alloc(struct alloc_info *info) { u32 key = 0; u64 initval = 1, *valp; valp = bpf_map_lookup_elem(&counting_map, &key); if (!valp) { bpf_map_update_elem(&counting_map, &key, &initval, BPF_ANY); return 0; } __sync_fetch_and_add(valp, 1); return 0; }
三.用户态程序
用户态应用程序通常命名为main.go。
首先,在main.go中,为ebpf程序添加go generate注解:
// main.go
...
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go bpf tracepoint.c -- -I../headers
...
执行go generate命令后,将生成:
Bpf_bpfel.o 和 Bpf_bpfel.go
- 为little-endian架构生成,比如amd64、arm64、riscv64和loong64;
Bpf_bpfeb.o 和 bpf_bpfeb.go
- 为big-endian架构生成,比如s390(x)、mips和sparc;
# go generate
Compiled github.com/cilium/ebpf/examples/tracepoint_in_c/bpf_bpfel.o
Stripped github.com/cilium/ebpf/examples/tracepoint_in_c/bpf_bpfel.o
Wrote github.com/cilium/ebpf/examples/tracepoint_in_c/bpf_bpfel.go
Compiled github.com/cilium/ebpf/examples/tracepoint_in_c/bpf_bpfeb.o
Stripped github.com/cilium/ebpf/examples/tracepoint_in_c/bpf_bpfeb.o
Wrote github.com/cilium/ebpf/examples/tracepoint_in_c/bpf_bpfeb.go
生成的.go文件末尾,均包含 //go:embed 注解,该注解会将.o文件包含到最终的go二进制文件中,便于程序的分发:
// bpf_bpfel.go
…
// Do not access this directly.
//
//go:embed bpf_bpfeb.o
var _BpfBytes []byte
然后,编写main.go代码,它会调用bpf_bpfel.go中的函数,执行ebpf程序的加载、挂载和执行:
- 使用1s的定时器,每隔1s读取map中的数据;
// main.go
func main() {
...
// Load pre-compiled programs and maps into the kernel.
objs := bpfObjects{}
if err := loadBpfObjects(&objs, nil); err != nil {
log.Fatalf("loading objects: %v", err)
}
defer objs.Close()
// Open a tracepoint and attach the pre-compiled program. Each time
// the kernel function enters, the program will increment the execution
// counter by 1. The read loop below polls this map value once per
// second.
// The first two arguments are taken from the following pathname:
// /sys/kernel/tracing/events/kmem/mm_page_alloc
kp, err := link.Tracepoint("kmem", "mm_page_alloc", objs.MmPageAlloc, nil)
defer kp.Close()
// Read loop reporting the total amount of times the kernel
// function was entered, once per second.
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for range ticker.C {
var value uint64
if err := objs.CountingMap.Lookup(mapKey, &value); err != nil {
log.Fatalf("reading map: %v", err)
}
log.Printf("%v times", value)
}
}
执行go build生成二进制可执行文件:
# CGO_ENABLED=0 GOARCH=amd64 go build
执行二进制文件:
# ./tracepoint_in_c
2023/11/07 20:49:44 Waiting for events..
2023/11/07 20:49:45 14 times
2023/11/07 20:49:46 26 times
2023/11/07 20:49:47 49 times
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。