Skip to content

Commit c028138

Browse files
committed
btf: mmap vmlinux if possible
The kernel recently gained the ability to mmap vmlinux BTF, which allows us to not allocate a couple megabytes of heap memory. See torvalds/linux@a539e2a Signed-off-by: Lorenz Bauer <lmb@isovalent.com>
1 parent 1f9b5f7 commit c028138

File tree

4 files changed

+54
-10
lines changed

4 files changed

+54
-10
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77

88
env:
99
TMPDIR: /tmp
10-
CI_MAX_KERNEL_VERSION: '6.11'
10+
CI_MAX_KERNEL_VERSION: '6.15'
1111
CI_MAX_EFW_VERSION: '0.21.0'
1212
CI_MIN_CLANG_VERSION: '13'
1313
go_version: '~1.24'

btf/kernel.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import (
55
"fmt"
66
"os"
77
"path/filepath"
8+
"runtime"
89
"slices"
910
"sort"
1011
"sync"
1112

1213
"github.com/cilium/ebpf/internal"
1314
"github.com/cilium/ebpf/internal/linux"
1415
"github.com/cilium/ebpf/internal/platform"
16+
"github.com/cilium/ebpf/internal/unix"
1517
)
1618

1719
// globalCache amortises decoding BTF across all users of the library.
@@ -110,7 +112,7 @@ func loadCachedKernelModuleSpec(module string) (*Spec, error) {
110112
return spec, nil
111113
}
112114

113-
func loadKernelSpec() (_ *Spec, _ error) {
115+
func loadKernelSpec() (*Spec, error) {
114116
if platform.IsWindows {
115117
return nil, internal.ErrNotSupportedOnOS
116118
}
@@ -119,8 +121,31 @@ func loadKernelSpec() (_ *Spec, _ error) {
119121
if err == nil {
120122
defer fh.Close()
121123

122-
spec, err := LoadSplitSpecFromReader(fh, nil)
123-
return spec, err
124+
info, err := fh.Stat()
125+
if err != nil {
126+
return nil, fmt.Errorf("stat vmlinux: %w", err)
127+
}
128+
129+
// NB: It's not safe to mmap arbitrary files because mmap(2) doesn't
130+
// guarantee that changes made after mmap are not visible in the mapping.
131+
//
132+
// This is not a problem for vmlinux, since it is always a read-only file.
133+
raw, err := unix.Mmap(int(fh.Fd()), 0, int(info.Size()), unix.PROT_READ, unix.MAP_PRIVATE)
134+
if err != nil {
135+
return LoadSplitSpecFromReader(fh, nil)
136+
}
137+
138+
spec, err := loadRawSpec(raw, nil)
139+
if err != nil {
140+
_ = unix.Munmap(raw)
141+
return nil, fmt.Errorf("load vmlinux: %w", err)
142+
}
143+
144+
runtime.SetFinalizer(spec.decoder.sharedBuf, func(_ *sharedBuf) {
145+
_ = unix.Munmap(raw)
146+
})
147+
148+
return spec, nil
124149
}
125150

126151
file, err := findVMLinux()

btf/kernel_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package btf
22

33
import (
44
"os"
5+
"runtime"
56
"testing"
67

78
"github.com/cilium/ebpf/internal/testutils"
@@ -14,10 +15,19 @@ func TestLoadKernelSpec(t *testing.T) {
1415
t.Skip("/sys/kernel/btf/vmlinux not present")
1516
}
1617

17-
_, err := LoadKernelSpec()
18+
spec, err := LoadKernelSpec()
1819
if err != nil {
1920
t.Fatal("Can't load kernel spec:", err)
2021
}
22+
23+
if !testutils.IsVersionLessThan(t, "linux:6.15") {
24+
maps, err := os.ReadFile("/proc/self/maps")
25+
qt.Assert(t, qt.IsNil(err))
26+
qt.Assert(t, qt.StringContains(string(maps), " /sys/kernel/btf/vmlinux\n"))
27+
}
28+
29+
// Prevent finalizer from unmapping vmlinux.
30+
runtime.KeepAlive(spec)
2131
}
2232

2333
func TestLoadKernelModuleSpec(t *testing.T) {

btf/unmarshal.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,22 @@ import (
1313
"sync"
1414
)
1515

16+
// sharedBuf is a buffer which may be shared between multiple decoders.
17+
//
18+
// It must not be modified. Some sharedBuf may be backed by an mmap-ed file, in
19+
// which case the sharedBuf has a finalizer. sharedBuf must therefore always be
20+
// passed as a pointer.
21+
type sharedBuf struct {
22+
raw []byte
23+
}
24+
1625
type decoder struct {
1726
// Immutable fields, may be shared.
1827

1928
base *decoder
2029
byteOrder binary.ByteOrder
21-
raw []byte
22-
strings *stringTable
30+
*sharedBuf
31+
strings *stringTable
2332
// The ID for offsets[0].
2433
firstTypeID TypeID
2534
// Map from TypeID to offset of the marshaled data in raw. Contains an entry
@@ -121,7 +130,7 @@ func newDecoder(raw []byte, bo binary.ByteOrder, strings *stringTable, base *dec
121130
return &decoder{
122131
base,
123132
bo,
124-
raw,
133+
&sharedBuf{raw},
125134
strings,
126135
firstTypeID,
127136
offsets,
@@ -177,7 +186,7 @@ func rebaseDecoder(d *decoder, base *decoder) (*decoder, error) {
177186
return &decoder{
178187
base,
179188
d.byteOrder,
180-
d.raw,
189+
d.sharedBuf,
181190
d.strings,
182191
d.firstTypeID,
183192
d.offsets,
@@ -220,7 +229,7 @@ func (d *decoder) copy(copiedTypes map[Type]Type) *decoder {
220229
return &decoder{
221230
d.base.copy(copiedTypes),
222231
d.byteOrder,
223-
d.raw,
232+
d.sharedBuf,
224233
d.strings,
225234
d.firstTypeID,
226235
d.offsets,

0 commit comments

Comments
 (0)