在博客 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,所以打算多多探索其究竟。