有同事报告一个错误:lookup: cannot allocate memory
,并请求如何解决。
lookup
项目中使用的 eBPF 库是 cilium/ebpf
。查看一下 BPF map lookup 的源代码,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
// map.go
func (m *Map) Lookup(key, valueOut interface{}) error {
valuePtr, valueBytes := makeBuffer(valueOut, m.fullValueSize)
if err := m.lookup(key, valuePtr, 0); err != nil {
return err
}
return m.unmarshalValue(valueOut, valueBytes)
}
func (m *Map) lookup(key interface{}, valueOut sys.Pointer, flags MapLookupFlags) error {
keyPtr, err := m.marshalKey(key)
if err != nil {
return fmt.Errorf("can't marshal key: %w", err)
}
attr := sys.MapLookupElemAttr{
MapFd: m.fd.Uint(),
Key: keyPtr,
Value: valueOut,
Flags: uint64(flags),
}
if err = sys.MapLookupElem(&attr); err != nil {
return fmt.Errorf("lookup: %w", wrapMapError(err))
}
return nil
}
// internal/sys/types.go
func MapLookupElem(attr *MapLookupElemAttr) error {
_, err := BPF(BPF_MAP_LOOKUP_ELEM, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
return err
}
|
好家伙,看样子这个错误不是 cilium/ebpf
报的。那就扒开内核的遮羞布,看看 BPF 这个系统调用的源代码吧。
cannot allocate memory
BPF 这个系统的返回值是一个叫做 error no. 的整数,我们就看看 cannot allocate memory
对应哪个 error no. 吧。
1
2
3
4
|
# errno -l
...
ENOMEM 12 Cannot allocate memory
...
|
好吧,我们就看看 BPF map lookup 的内核源代码中会不会返回 ENOMEM
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// kernel/bpf/syscall.c
SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, size)
|-->case BPF_MAP_LOOKUP_ELEM:
err = map_lookup_elem(&attr);
break;
|-->key = __bpf_copy_key(ukey, map->key_size);
if (IS_ERR(key)) {
err = PTR_ERR(key);
goto err_put;
}
|-->if (key_size)
return memdup_user(ukey, key_size);
|-->p = kmalloc_track_caller(len, GFP_USER | __GFP_NOWARN);
if (!p)
return ERR_PTR(-ENOMEM);
|
由此可知,BPF map lookup 确实需要动态分配内存,用于复制 key/value
。
如何解决
从用户态应用程序而言,该问题无解,因为导致 lookup: cannot allocate memory
的原因是内核动态分配内存失败了。
P.S. 那位同事查了下 free -g
,的确是系统内存耗光了。
那要如何解决呢?需要从系统资源分配与利用的角度出发,限制用户应用程序的内存使用,预留一定比例的内存给内核处理突发事件。(未验证)
小结
诚如侯捷所说:源码面前,了无秘密;就包括本次的错误报告。