@@ -12,7 +12,8 @@ import (
12
12
"github.com/cilium/ebpf/internal/platform"
13
13
)
14
14
15
- var kernelBTF = struct {
15
+ // globalCache amortises decoding BTF across all users of the library.
16
+ var globalCache = struct {
16
17
sync.RWMutex
17
18
kernel * Spec
18
19
modules map [string ]* Spec
@@ -22,98 +23,112 @@ var kernelBTF = struct {
22
23
23
24
// FlushKernelSpec removes any cached kernel type information.
24
25
func FlushKernelSpec () {
25
- kernelBTF .Lock ()
26
- defer kernelBTF .Unlock ()
26
+ globalCache .Lock ()
27
+ defer globalCache .Unlock ()
27
28
28
- kernelBTF .kernel = nil
29
- kernelBTF .modules = make (map [string ]* Spec )
29
+ globalCache .kernel = nil
30
+ globalCache .modules = make (map [string ]* Spec )
30
31
}
31
32
32
33
// LoadKernelSpec returns the current kernel's BTF information.
33
34
//
34
35
// Defaults to /sys/kernel/btf/vmlinux and falls back to scanning the file system
35
36
// for vmlinux ELFs. Returns an error wrapping ErrNotSupported if BTF is not enabled.
37
+ //
38
+ // Consider using [Cache] instead.
36
39
func LoadKernelSpec () (* Spec , error ) {
37
- kernelBTF .RLock ()
38
- spec := kernelBTF .kernel
39
- kernelBTF .RUnlock ()
40
-
41
- if spec == nil {
42
- kernelBTF .Lock ()
43
- defer kernelBTF .Unlock ()
40
+ spec , err := loadCachedKernelSpec ()
41
+ return spec .Copy (), err
42
+ }
44
43
45
- spec = kernelBTF .kernel
46
- }
44
+ // load (and cache) the kernel spec.
45
+ //
46
+ // Does not copy Spec.
47
+ func loadCachedKernelSpec () (* Spec , error ) {
48
+ globalCache .RLock ()
49
+ spec := globalCache .kernel
50
+ globalCache .RUnlock ()
47
51
48
52
if spec != nil {
49
- return spec . Copy () , nil
53
+ return spec , nil
50
54
}
51
55
52
- spec , _ , err := loadKernelSpec ()
56
+ globalCache .Lock ()
57
+ defer globalCache .Unlock ()
58
+
59
+ spec , err := loadKernelSpec ()
53
60
if err != nil {
54
61
return nil , err
55
62
}
56
63
57
- kernelBTF .kernel = spec
58
- return spec . Copy () , nil
64
+ globalCache .kernel = spec
65
+ return spec , nil
59
66
}
60
67
61
68
// LoadKernelModuleSpec returns the BTF information for the named kernel module.
62
69
//
70
+ // Using [Cache.Module] is faster when loading BTF for more than one module.
71
+ //
63
72
// Defaults to /sys/kernel/btf/<module>.
64
73
// Returns an error wrapping ErrNotSupported if BTF is not enabled.
65
74
// Returns an error wrapping fs.ErrNotExist if BTF for the specific module doesn't exist.
66
75
func LoadKernelModuleSpec (module string ) (* Spec , error ) {
67
- kernelBTF .RLock ()
68
- spec := kernelBTF .modules [module ]
69
- kernelBTF .RUnlock ()
76
+ spec , err := loadCachedKernelModuleSpec (module )
77
+ return spec .Copy (), err
78
+ }
79
+
80
+ // load (and cache) a module spec.
81
+ //
82
+ // Does not copy Spec.
83
+ func loadCachedKernelModuleSpec (module string ) (* Spec , error ) {
84
+ globalCache .RLock ()
85
+ spec := globalCache .modules [module ]
86
+ globalCache .RUnlock ()
70
87
71
88
if spec != nil {
72
- return spec . Copy () , nil
89
+ return spec , nil
73
90
}
74
91
75
- base , err := LoadKernelSpec ()
92
+ base , err := loadCachedKernelSpec ()
76
93
if err != nil {
77
- return nil , fmt . Errorf ( "load kernel spec: %w" , err )
94
+ return nil , err
78
95
}
79
96
80
- kernelBTF .Lock ()
81
- defer kernelBTF .Unlock ()
82
-
83
- if spec = kernelBTF .modules [module ]; spec != nil {
84
- return spec .Copy (), nil
85
- }
97
+ // NB: This only allows a single module to be parsed at a time. Not sure
98
+ // it makes a difference.
99
+ globalCache .Lock ()
100
+ defer globalCache .Unlock ()
86
101
87
102
spec , err = loadKernelModuleSpec (module , base )
88
103
if err != nil {
89
- return nil , fmt . Errorf ( "load kernel module: %w" , err )
104
+ return nil , err
90
105
}
91
106
92
- kernelBTF .modules [module ] = spec
93
- return spec . Copy () , nil
107
+ globalCache .modules [module ] = spec
108
+ return spec , nil
94
109
}
95
110
96
- func loadKernelSpec () (_ * Spec , fallback bool , _ error ) {
111
+ func loadKernelSpec () (_ * Spec , _ error ) {
97
112
if platform .IsWindows {
98
- return nil , false , internal .ErrNotSupportedOnOS
113
+ return nil , internal .ErrNotSupportedOnOS
99
114
}
100
115
101
116
fh , err := os .Open ("/sys/kernel/btf/vmlinux" )
102
117
if err == nil {
103
118
defer fh .Close ()
104
119
105
120
spec , err := loadRawSpec (fh , internal .NativeEndian , nil )
106
- return spec , false , err
121
+ return spec , err
107
122
}
108
123
109
124
file , err := findVMLinux ()
110
125
if err != nil {
111
- return nil , false , err
126
+ return nil , err
112
127
}
113
128
defer file .Close ()
114
129
115
130
spec , err := LoadSpecFromReader (file )
116
- return spec , true , err
131
+ return spec , err
117
132
}
118
133
119
134
func loadKernelModuleSpec (module string , base * Spec ) (* Spec , error ) {
@@ -168,3 +183,83 @@ func findVMLinux() (*os.File, error) {
168
183
169
184
return nil , fmt .Errorf ("no BTF found for kernel version %s: %w" , release , internal .ErrNotSupported )
170
185
}
186
+
187
+ // Cache allows to amortise the cost of decoding BTF across multiple call-sites.
188
+ //
189
+ // It is not safe for concurrent use.
190
+ type Cache struct {
191
+ KernelTypes * Spec
192
+ KernelModules map [string ]* Spec
193
+ }
194
+
195
+ // NewCache creates a new Cache.
196
+ //
197
+ // Opportunistically reuses a global cache if possible.
198
+ func NewCache () * Cache {
199
+ globalCache .RLock ()
200
+ defer globalCache .RUnlock ()
201
+
202
+ // This copy is either a no-op or very cheap, since the spec won't contain
203
+ // any inflated types.
204
+ kernel := globalCache .kernel .Copy ()
205
+ if kernel == nil {
206
+ return & Cache {}
207
+ }
208
+
209
+ modules := make (map [string ]* Spec , len (globalCache .modules ))
210
+ for name , spec := range globalCache .modules {
211
+ decoder , _ := rebaseDecoder (spec .decoder , kernel .decoder )
212
+ // NB: Kernel module BTF can't contain ELF fixups because it is always
213
+ // read from sysfs.
214
+ modules [name ] = & Spec {decoder : decoder }
215
+ }
216
+
217
+ return & Cache {kernel , modules }
218
+ }
219
+
220
+ // Kernel is equivalent to [LoadKernelSpec], except that repeated calls do
221
+ // not copy the Spec.
222
+ func (c * Cache ) Kernel () (* Spec , error ) {
223
+ if c .KernelTypes != nil {
224
+ return c .KernelTypes , nil
225
+ }
226
+
227
+ var err error
228
+ c .KernelTypes , err = LoadKernelSpec ()
229
+ return c .KernelTypes , err
230
+ }
231
+
232
+ // Module is equivalent to [LoadKernelModuleSpec], except that repeated calls do
233
+ // not copy the spec.
234
+ //
235
+ // All modules also share the return value of [Kernel] as their base.
236
+ func (c * Cache ) Module (name string ) (* Spec , error ) {
237
+ if spec := c .KernelModules [name ]; spec != nil {
238
+ return spec , nil
239
+ }
240
+
241
+ if c .KernelModules == nil {
242
+ c .KernelModules = make (map [string ]* Spec )
243
+ }
244
+
245
+ base , err := c .Kernel ()
246
+ if err != nil {
247
+ return nil , err
248
+ }
249
+
250
+ spec , err := loadCachedKernelModuleSpec (name )
251
+ if err != nil {
252
+ return nil , err
253
+ }
254
+
255
+ // Important: base is shared between modules. This allows inflating common
256
+ // types only once.
257
+ decoder , err := rebaseDecoder (spec .decoder , base .decoder )
258
+ if err != nil {
259
+ return nil , err
260
+ }
261
+
262
+ spec = & Spec {decoder : decoder }
263
+ c .KernelModules [name ] = spec
264
+ return spec , err
265
+ }
0 commit comments