在博客 eBPF Talk: 揭秘 BPF map 前生今世,已讲解了 bpf map 从定义到创建的整个过程。
不过博客中讲解的加载器是 C 语言的 libbpf。
在此,就讲解一下纯 Go 语言的加载器 cilium/ebpf 中 bpf map 的处理过程吧。
不似 libbpf 的处理方式:
1
2
|
prog->insns[insn_off].src_reg = BPF_PSEUDO_MAP_FD; // 值在内核中定义为 1
prog->insns[insn_off].imm = ctx->map_fds[map_idx]; // ctx->map_fds[map_idx] 即为保存的 map fd 值。
|
cilium/ebpf
的处理过程复杂一些。
BPF_PSEUDO_MAP_FD
在 cilium/ebpf
中,将 src_reg
标记为 BPF_PSEUDO_MAP_FD 是在 ELF 解析阶段。
详细函数调用路径如下:
1
2
3
4
5
6
7
8
9
|
LoadCollectionSpecFromReader()
|-->loadProgramSections()
|-->loadFunctions()
|-->relocateInstruction()
|-->switch target.kind {
case mapSection, btfMapSection:
// ...
ins.Src = asm.PseudoMapFD // 1
}
|
创建 map
如 prog->insns[insn_off].imm = ctx->map_fds[map_idx];
代码,map 的 fd 哪里来呢?
此时,需要加载器先去创建 map,再将 map 的 fd 赋值给对应指令的 imm
。
创建 map 的函数调用路径如下:
1
2
3
4
5
6
7
8
|
LoadAndAssign()
|-->getValue()
|-->loadMap()
|-->newMapWithOptions()
|-->createMap()
|-->func MapCreate(attr *MapCreateAttr) (*FD, error) {
fd, err := BPF(BPF_MAP_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
|-->r1, _, errNo := unix.Syscall(unix.SYS_BPF, uintptr(cmd), uintptr(attr), size)
|
由 bpf map 源码导读 可知,BPF 系统调用中创建 bpf map 的时候,系统调用的返回值是一个 int
;该 int
小于 0 时则是一个 errno,否则即是一个 map fd。
绑定 map fd
在创建了 map 之后,加载器中就知道了该 map 对应的 fd。
之后,在处理 bpf prog 的时候,将 map fd 赋值给指令的 imm
即可。
使用 map fd 的函数调用路径如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
LoadAndAssign()
|-->getValue()
|-->loadProgram()
|-->newProgramWithOptions()
|-->insns.Marshal(buf, internal.NativeEndian)
|-->insns.encodeMapPointers()
|-->m := ins.Map()
fd := m.FD()
ins.encodeMapFD(m.FD())
|-->func (ins *Instruction) encodeMapFD(fd int) {
// Preserve the offset value for direct map loads.
offset := uint64(ins.Constant) & (math.MaxUint32 << 32)
rawFd := uint64(uint32(fd))
ins.Constant = int64(offset | rawFd)
}
|
至此,在 cilium/ebpf
加载阶段 bpf map 已准备就绪,后续可以直接操作 *ebpf.Map
了。
小结
常用 cilium/ebpf
,所以打算多多探索其究竟。