Skip to content

Commit 633bfc1

Browse files
committed
padrgn.helpers.linux.slab: add support for retrieving objects from partial slabs
The current implementation of for_each_allocated_object() is slow as it iterates through every physical page. This commit adds the ability to retrieve objects from the per-node partial lists and the per-cpu partial slab lists, greatly improving efficiency when searching for the source of vfs caches of dying cgroups or millions of negative dentries. Signed-off-by: Jian Wen <[email protected]>
1 parent c69e5b1 commit 633bfc1

File tree

3 files changed

+162
-0
lines changed

3 files changed

+162
-0
lines changed

drgn/helpers/linux/slab.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"get_slab_cache_aliases",
5252
"print_slab_caches",
5353
"slab_cache_for_each_allocated_object",
54+
"slab_cache_for_each_partial_slab_object",
5455
"slab_cache_is_merged",
5556
"slab_object_info",
5657
)
@@ -231,6 +232,39 @@ def for_each_allocated_object(self, type: Union[str, Type]) -> Iterator[Object]:
231232
if slab.slab_cache == self._slab_cache:
232233
yield from self._page_objects(page, slab, pointer_type)
233234

235+
def for_each_partial_slab_object(self, type: Union[str, Type]) -> Iterator[Object]:
236+
pointer_type = self._prog.pointer_type(self._prog.type(type))
237+
cpu_slab = self._slab_cache.cpu_slab.read_()
238+
239+
# per-cpu partial slabs
240+
if hasattr(cpu_slab, "slab"):
241+
slab_ctype = "struct slab *"
242+
else:
243+
slab_ctype = "struct page *"
244+
245+
for cpu in for_each_online_cpu(self._prog):
246+
this_cpu_slab = per_cpu_ptr(cpu_slab, cpu)
247+
slab = this_cpu_slab.partial
248+
if slab != NULL(self._prog, slab_ctype):
249+
yield from self._page_objects(
250+
cast("struct page *", slab), slab, pointer_type
251+
)
252+
253+
# per-node partial slabs
254+
if hasattr(cpu_slab, "slab"):
255+
struct = "struct slab"
256+
member = "slab_list"
257+
else:
258+
struct = "struct page"
259+
member = "lru"
260+
261+
for node in range(self._prog["nr_online_nodes"].value_()):
262+
n = self._slab_cache.node[node]
263+
for slab in list_for_each_entry(struct, n.partial.address_of_(), member):
264+
yield from self._page_objects(
265+
cast("struct page *", slab), slab, pointer_type
266+
)
267+
234268
def object_info(
235269
self, page: Object, slab: Object, addr: int
236270
) -> "Optional[SlabObjectInfo]":
@@ -450,6 +484,34 @@ def slab_cache_for_each_allocated_object(
450484
return _get_slab_cache_helper(slab_cache).for_each_allocated_object(type)
451485

452486

487+
def slab_cache_for_each_partial_slab_object(
488+
slab_cache: Object, type: Union[str, Type]
489+
) -> Iterator[Object]:
490+
"""
491+
Iterate over all allocated objects in a given slab cache's
492+
per-node partial slabs and per-cpu partial slabs.
493+
494+
Only the SLUB allocator is supported now.
495+
496+
>>> dentry_cache = find_slab_cache(prog, "dentry")
497+
>>> next(slab_cache_for_each_partial_slab_object(dentry_cache, "struct dentry")).d_name.name
498+
(const unsigned char *)0xffff93390051c038 = "cgroup"
499+
500+
>>> for s in slab_cache_for_each_partial_slab_object(dentry_cache, "struct dentry"):
501+
... print(s.d_name.name)
502+
...
503+
(const unsigned char *)0xffff93390051c038 = "cgroup"
504+
(const unsigned char *)0xffff93390051c0f8 = "cmdline"
505+
(const unsigned char *)0xffff93390051c1b8 = "8:85355"
506+
(const unsigned char *)0xffff93390051c278 = "cmdline"
507+
508+
:param slab_cache: ``struct kmem_cache *``
509+
:param type: Type of object in the slab cache.
510+
:return: Iterator of ``type *`` objects.
511+
"""
512+
return _get_slab_cache_helper(slab_cache).for_each_partial_slab_object(type)
513+
514+
453515
def _find_containing_slab(
454516
prog: Program, addr: int
455517
) -> Optional[Tuple[Object, Object, Object]]:

tests/linux_kernel/helpers/test_slab.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
for_each_slab_cache,
1313
get_slab_cache_aliases,
1414
slab_cache_for_each_allocated_object,
15+
slab_cache_for_each_partial_slab_object,
1516
slab_cache_is_merged,
1617
slab_object_info,
1718
)
@@ -160,6 +161,34 @@ def test_slab_cache_for_each_allocated_object(self):
160161
list(objects),
161162
)
162163

164+
@skip_unless_have_full_mm_support
165+
@skip_unless_have_test_kmod
166+
def test_slab_cache_node_partial_object(self):
167+
self.assertEqual(
168+
sum(
169+
o.value
170+
for o in slab_cache_for_each_partial_slab_object(
171+
self.prog["drgn_test_node_partial_kmem_cache"],
172+
"struct drgn_test_node_partial_slab_object",
173+
)
174+
),
175+
100,
176+
)
177+
178+
@skip_unless_have_full_mm_support
179+
@skip_unless_have_test_kmod
180+
def test_slab_cache_cpu_partial_object(self):
181+
self.assertEqual(
182+
sum(
183+
o.value
184+
for o in slab_cache_for_each_partial_slab_object(
185+
self.prog["drgn_test_cpu_partial_kmem_cache"],
186+
"struct drgn_test_cpu_partial_slab_object",
187+
)
188+
),
189+
300,
190+
)
191+
163192
@skip_unless_have_full_mm_support
164193
@skip_unless_have_test_kmod
165194
def test_slab_object_info(self):

tests/linux_kernel/kmod/drgn_test.c

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,24 @@ struct drgn_test_big_slab_object {
364364
struct drgn_test_small_slab_object *drgn_test_small_slab_objects[5];
365365
struct drgn_test_big_slab_object *drgn_test_big_slab_objects[5];
366366

367+
struct kmem_cache *drgn_test_node_partial_kmem_cache;
368+
369+
struct drgn_test_node_partial_slab_object {
370+
unsigned long value;
371+
};
372+
373+
struct drgn_test_node_partial_slab_object *drgn_test_node_partial_slab_object_p;
374+
375+
struct kmem_cache *drgn_test_cpu_partial_kmem_cache;
376+
377+
struct drgn_test_cpu_partial_slab_object {
378+
unsigned long padding[(PAGE_SIZE * 2) / sizeof(unsigned long) - 1
379+
- (sizeof(void *) / sizeof(unsigned long))];
380+
unsigned long value;
381+
};
382+
383+
struct drgn_test_cpu_partial_slab_object *drgn_test_cpu_partial_slab_objects[5];
384+
367385
static void drgn_test_slab_exit(void)
368386
{
369387
size_t i;
@@ -386,9 +404,27 @@ static void drgn_test_slab_exit(void)
386404
}
387405
kmem_cache_destroy(drgn_test_small_kmem_cache);
388406
}
407+
if (drgn_test_node_partial_kmem_cache) {
408+
if (drgn_test_node_partial_slab_object_p)
409+
kmem_cache_free(drgn_test_node_partial_kmem_cache,
410+
drgn_test_node_partial_slab_object_p);
411+
kmem_cache_destroy(drgn_test_node_partial_kmem_cache);
412+
}
413+
if (drgn_test_cpu_partial_kmem_cache) {
414+
for (i = 0; i < ARRAY_SIZE(drgn_test_cpu_partial_slab_objects); i++) {
415+
if (drgn_test_cpu_partial_slab_objects[i]) {
416+
kmem_cache_free(drgn_test_cpu_partial_kmem_cache,
417+
drgn_test_cpu_partial_slab_objects[i]);
418+
}
419+
}
420+
kmem_cache_destroy(drgn_test_cpu_partial_kmem_cache);
421+
}
389422
}
390423

391424
// Dummy constructor so test slab caches won't get merged.
425+
// Note that the free pointer is outside of the object of a slab with a destructor.
426+
// As a result, each object costs sizeof(void *) more bytes.
427+
// See https://github.com/torvalds/linux/blob/v6.4/mm/slub.c#L4393
392428
static void drgn_test_slab_ctor(void *arg)
393429
{
394430
}
@@ -426,6 +462,41 @@ static int drgn_test_slab_init(void)
426462
return -ENOMEM;
427463
drgn_test_big_slab_objects[i]->value = i;
428464
}
465+
466+
drgn_test_node_partial_kmem_cache =
467+
kmem_cache_create(
468+
"drgn_test_partial",
469+
sizeof(struct drgn_test_node_partial_slab_object),
470+
__alignof__(struct drgn_test_node_partial_slab_object),
471+
0, drgn_test_slab_ctor);
472+
if (!drgn_test_node_partial_kmem_cache)
473+
return -ENOMEM;
474+
drgn_test_node_partial_slab_object_p = kmem_cache_alloc(
475+
drgn_test_node_partial_kmem_cache, GFP_KERNEL);
476+
drgn_test_node_partial_slab_object_p->value = 100;
477+
478+
// Move the object to the per-node partial list
479+
kmem_cache_shrink(drgn_test_node_partial_kmem_cache);
480+
481+
drgn_test_cpu_partial_kmem_cache = kmem_cache_create(
482+
"drgn_test_cpu_partial_kmem_cache",
483+
sizeof(struct drgn_test_cpu_partial_slab_object),
484+
__alignof__(struct drgn_test_cpu_partial_slab_object),
485+
0, drgn_test_slab_ctor);
486+
if (!drgn_test_cpu_partial_kmem_cache)
487+
return -ENOMEM;
488+
489+
for (i = 0; i < ARRAY_SIZE(drgn_test_cpu_partial_slab_objects); i++) {
490+
drgn_test_cpu_partial_slab_objects[i] = kmem_cache_alloc(
491+
drgn_test_cpu_partial_kmem_cache, GFP_KERNEL);
492+
drgn_test_cpu_partial_slab_objects[i]->value = 100;
493+
}
494+
495+
// Free the first object to make a cpu partial slab
496+
kmem_cache_free(drgn_test_cpu_partial_kmem_cache,
497+
drgn_test_cpu_partial_slab_objects[0]);
498+
drgn_test_cpu_partial_slab_objects[0] = NULL;
499+
429500
return 0;
430501
}
431502

0 commit comments

Comments
 (0)