History

mm, sl[aou]b: guarantee natural alignment for kmalloc(power-of-two)

In most configurations, kmalloc() happens to return naturally aligned (i.e. aligned to the block size itself) blocks for power of two sizes. That means some kmalloc() users might unknowingly rely on that alignment, until stuff breaks when the kernel is built with e.g. CONFIG_SLUB_DEBUG or CONFIG_SLOB, and blocks stop being aligned. Then developers have to devise workaround such as own kmem caches with specified alignment [1], which is not always practical, as recently evidenced in [2]. The topic has been discussed at LSF/MM 2019 [3]. Adding a 'kmalloc_aligned()' variant would not help with code unknowingly relying on the implicit alignment. For slab implementations it would either require creating more kmalloc caches, or allocate a larger size and only give back part of it. That would be wasteful, especially with a generic alignment parameter (in contrast with a fixed alignment to size). Ideally we should provide to mm users what they need without difficult workarounds or own reimplementations, so let's make the kmalloc() alignment to size explicitly guaranteed for power-of-two sizes under all configurations. What this means for the three available allocators? * SLAB object layout happens to be mostly unchanged by the patch. The implicitly provided alignment could be compromised with CONFIG_DEBUG_SLAB due to redzoning, however SLAB disables redzoning for caches with alignment larger than unsigned long long. Practically on at least x86 this includes kmalloc caches as they use cache line alignment, which is larger than that. Still, this patch ensures alignment on all arches and cache sizes. * SLUB layout is also unchanged unless redzoning is enabled through CONFIG_SLUB_DEBUG and boot parameter for the particular kmalloc cache. With this patch, explicit alignment is guaranteed with redzoning as well. This will result in more memory being wasted, but that should be acceptable in a debugging scenario. * SLOB has no implicit alignment so this patch adds it explicitly for kmalloc(). The potential downside is increased fragmentation. While pathological allocation scenarios are certainly possible, in my testing, after booting a x86_64 kernel+userspace with virtme, around 16MB memory was consumed by slab pages both before and after the patch, with difference in the noise. [1] https://lore.kernel.org/linux-btrfs/c3157c8e8e0e7588312b40c853f65c02fe6c957a.1566399731.git.christophe.leroy@c-s.fr/ [2] https://lore.kernel.org/linux-fsdevel/20190225040904.5557-1-ming.lei@redhat.com/ [3] https://lwn.net/Articles/787740/ [akpm@linux-foundation.org: documentation fixlet, per Matthew] Link: http://lkml.kernel.org/r/20190826111627.7505-3-vbabka@suse.cz Signed-off-by: Vlastimil Babka <vbabka@suse.cz> Reviewed-by: Matthew Wilcox (Oracle) <willy@infradead.org> Acked-by: Michal Hocko <mhocko@suse.com> Acked-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com> Acked-by: Christoph Hellwig <hch@lst.de> Cc: David Sterba <dsterba@suse.cz> Cc: Christoph Lameter <cl@linux.com> Cc: Pekka Enberg <penberg@kernel.org> Cc: David Rientjes <rientjes@google.com> Cc: Ming Lei <ming.lei@redhat.com> Cc: Dave Chinner <david@fromorbit.com> Cc: "Darrick J . Wong" <darrick.wong@oracle.com> Cc: Christoph Hellwig <hch@lst.de> Cc: James Bottomley <James.Bottomley@HansenPartnership.com> Cc: Vlastimil Babka <vbabka@suse.cz> Cc: Joonsoo Kim <iamjoonsoo.kim@lge.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

Vlastimil Babka committed 7 years ago

Commit 59bb4791 parent 6a486c04 files · +49 −12Browse files

Unified · Split

4 files
  1. Documentation/core-api/memory-allocation.rst +4 −0
  2. include/linux/slab.h +4 −0
  3. mm/slab_common.c +10 −1
  4. mm/slob.c +31 −11
Documentation/core-api/memory-allocation.rst+4−0
@@ -98,6 +98,10 @@ limited. The actual limit depends on the hardware and the kernel · 97 unmodified lines
98configuration, but it is a good practice to use `kmalloc` for objects98configuration, but it is a good practice to use `kmalloc` for objects
99smaller than page size.99smaller than page size.
100 100
101The address of a chunk allocated with `kmalloc` is aligned to at least
102ARCH_KMALLOC_MINALIGN bytes. For sizes which are a power of two, the
103alignment is also guaranteed to be at least the respective size.
104
101For large allocations you can use :c:func:`vmalloc` and105For large allocations you can use :c:func:`vmalloc` and
102:c:func:`vzalloc`, or directly request pages from the page106:c:func:`vzalloc`, or directly request pages from the page
103allocator. The memory allocated by `vmalloc` and related functions is107allocator. The memory allocated by `vmalloc` and related functions is
include/linux/slab.h+4−0
@@ -493,6 +493,10 @@ static __always_inline void *kmalloc_large(size_t size, gfp_t flags) · 492 unmodified lines
493 * kmalloc is the normal method of allocating memory493 * kmalloc is the normal method of allocating memory
494 * for objects smaller than page size in the kernel.494 * for objects smaller than page size in the kernel.
495 *495 *
496 * The allocated object address is aligned to at least ARCH_KMALLOC_MINALIGN
497 * bytes. For @size of power of two bytes, the alignment is also guaranteed
498 * to be at least to the size.
499 *
496 * The @flags argument may be one of the GFP flags defined at500 * The @flags argument may be one of the GFP flags defined at
497 * include/linux/gfp.h and described at501 * include/linux/gfp.h and described at
498 * :ref:`Documentation/core-api/mm-api.rst <mm-api-gfp-flags>`502 * :ref:`Documentation/core-api/mm-api.rst <mm-api-gfp-flags>`
mm/slab_common.c+10−1
@@ -1030,10 +1030,19 @@ void __init create_boot_cache(struct kmem_cache *s, const char *name, · 1029 unmodified lines
1030 unsigned int useroffset, unsigned int usersize)1030 unsigned int useroffset, unsigned int usersize)
1031{1031{
1032 int err;1032 int err;
1033 unsigned int align = ARCH_KMALLOC_MINALIGN;
1033 1034
1034 s->name = name;1035 s->name = name;
1035 s->size = s->object_size = size;1036 s->size = s->object_size = size;
1036 s->align = calculate_alignment(flags, ARCH_KMALLOC_MINALIGN, size);1037
1038 /*
1039 * For power of two sizes, guarantee natural alignment for kmalloc
1040 * caches, regardless of SL*B debugging options.
1041 */
1042 if (is_power_of_2(size))
1043 align = max(align, size);
1044 s->align = calculate_alignment(flags, align, size);
1045
1037 s->useroffset = useroffset;1046 s->useroffset = useroffset;
1038 s->usersize = usersize;1047 s->usersize = usersize;
1039 1048
mm/slob.c+31−11
@@ -224,6 +224,7 @@ static void slob_free_pages(void *b, int order) · 223 unmodified lines
224 * @sp: Page to look in.224 * @sp: Page to look in.
225 * @size: Size of the allocation.225 * @size: Size of the allocation.
226 * @align: Allocation alignment.226 * @align: Allocation alignment.
227 * @align_offset: Offset in the allocated block that will be aligned.
227 * @page_removed_from_list: Return parameter.228 * @page_removed_from_list: Return parameter.
228 *229 *
229 * Tries to find a chunk of memory at least @size bytes big within @page.230 * Tries to find a chunk of memory at least @size bytes big within @page.
@@ -234,7 +235,7 @@ static void slob_free_pages(void *b, int order) · 4 unmodified lines
234 * true (set to false otherwise).235 * true (set to false otherwise).
235 */236 */
236static void *slob_page_alloc(struct page *sp, size_t size, int align,237static void *slob_page_alloc(struct page *sp, size_t size, int align,
237 bool *page_removed_from_list)238 int align_offset, bool *page_removed_from_list)
238{239{
239 slob_t *prev, *cur, *aligned = NULL;240 slob_t *prev, *cur, *aligned = NULL;
240 int delta = 0, units = SLOB_UNITS(size);241 int delta = 0, units = SLOB_UNITS(size);
@@ -243,8 +244,17 @@ static void *slob_page_alloc(struct page *sp, size_t size, int align, · 2 unmodified lines
243 for (prev = NULL, cur = sp->freelist; ; prev = cur, cur = slob_next(cur)) {244 for (prev = NULL, cur = sp->freelist; ; prev = cur, cur = slob_next(cur)) {
244 slobidx_t avail = slob_units(cur);245 slobidx_t avail = slob_units(cur);
245 246
247 /*
248 * 'aligned' will hold the address of the slob block so that the
249 * address 'aligned'+'align_offset' is aligned according to the
250 * 'align' parameter. This is for kmalloc() which prepends the
251 * allocated block with its size, so that the block itself is
252 * aligned when needed.
253 */
246 if (align) {254 if (align) {
247 aligned = (slob_t *)ALIGN((unsigned long)cur, align);255 aligned = (slob_t *)
256 (ALIGN((unsigned long)cur + align_offset, align)
257 - align_offset);
248 delta = aligned - cur;258 delta = aligned - cur;
249 }259 }
250 if (avail >= units + delta) { /* room enough? */260 if (avail >= units + delta) { /* room enough? */
@@ -288,7 +298,8 @@ static void *slob_page_alloc(struct page *sp, size_t size, int align, · 37 unmodified lines
288/*298/*
289 * slob_alloc: entry point into the slob allocator.299 * slob_alloc: entry point into the slob allocator.
290 */300 */
291static void *slob_alloc(size_t size, gfp_t gfp, int align, int node)301static void *slob_alloc(size_t size, gfp_t gfp, int align, int node,
302 int align_offset)
292{303{
293 struct page *sp;304 struct page *sp;
294 struct list_head *slob_list;305 struct list_head *slob_list;
@@ -319,7 +330,7 @@ static void *slob_alloc(size_t size, gfp_t gfp, int align, int node) · 24 unmodified lines
319 if (sp->units < SLOB_UNITS(size))330 if (sp->units < SLOB_UNITS(size))
320 continue;331 continue;
321 332
322 b = slob_page_alloc(sp, size, align, &page_removed_from_list);333 b = slob_page_alloc(sp, size, align, align_offset, &page_removed_from_list);
323 if (!b)334 if (!b)
324 continue;335 continue;
325 336
@@ -356,7 +367,7 @@ static void *slob_alloc(size_t size, gfp_t gfp, int align, int node) · 30 unmodified lines
356 INIT_LIST_HEAD(&sp->slab_list);367 INIT_LIST_HEAD(&sp->slab_list);
357 set_slob(b, SLOB_UNITS(PAGE_SIZE), b + SLOB_UNITS(PAGE_SIZE));368 set_slob(b, SLOB_UNITS(PAGE_SIZE), b + SLOB_UNITS(PAGE_SIZE));
358 set_slob_page_free(sp, slob_list);369 set_slob_page_free(sp, slob_list);
359 b = slob_page_alloc(sp, size, align, &_unused);370 b = slob_page_alloc(sp, size, align, align_offset, &_unused);
360 BUG_ON(!b);371 BUG_ON(!b);
361 spin_unlock_irqrestore(&slob_lock, flags);372 spin_unlock_irqrestore(&slob_lock, flags);
362 }373 }
@@ -458,7 +469,7 @@ static __always_inline void * · 95 unmodified lines
458__do_kmalloc_node(size_t size, gfp_t gfp, int node, unsigned long caller)469__do_kmalloc_node(size_t size, gfp_t gfp, int node, unsigned long caller)
459{470{
460 unsigned int *m;471 unsigned int *m;
461 int align = max_t(size_t, ARCH_KMALLOC_MINALIGN, ARCH_SLAB_MINALIGN);472 int minalign = max_t(size_t, ARCH_KMALLOC_MINALIGN, ARCH_SLAB_MINALIGN);
462 void *ret;473 void *ret;
463 474
464 gfp &= gfp_allowed_mask;475 gfp &= gfp_allowed_mask;
@@ -466,19 +477,28 @@ __do_kmalloc_node(size_t size, gfp_t gfp, int node, unsigned long caller) · 1 unmodified line
466 fs_reclaim_acquire(gfp);477 fs_reclaim_acquire(gfp);
467 fs_reclaim_release(gfp);478 fs_reclaim_release(gfp);
468 479
469 if (size < PAGE_SIZE - align) {480 if (size < PAGE_SIZE - minalign) {
481 int align = minalign;
482
483 /*
484 * For power of two sizes, guarantee natural alignment for
485 * kmalloc()'d objects.
486 */
487 if (is_power_of_2(size))
488 align = max(minalign, (int) size);
489
470 if (!size)490 if (!size)
471 return ZERO_SIZE_PTR;491 return ZERO_SIZE_PTR;
472 492
473 m = slob_alloc(size + align, gfp, align, node);493 m = slob_alloc(size + minalign, gfp, align, node, minalign);
474 494
475 if (!m)495 if (!m)
476 return NULL;496 return NULL;
477 *m = size;497 *m = size;
478 ret = (void *)m + align;498 ret = (void *)m + minalign;
479 499
480 trace_kmalloc_node(caller, ret,500 trace_kmalloc_node(caller, ret,
481 size, size + align, gfp, node);501 size, size + minalign, gfp, node);
482 } else {502 } else {
483 unsigned int order = get_order(size);503 unsigned int order = get_order(size);
484 504
@@ -579,7 +599,7 @@ static void *slob_alloc_node(struct kmem_cache *c, gfp_t flags, int node) · 94 unmodified lines
579 fs_reclaim_release(flags);599 fs_reclaim_release(flags);
580 600
581 if (c->size < PAGE_SIZE) {601 if (c->size < PAGE_SIZE) {
582 b = slob_alloc(c->size, flags, c->align, node);602 b = slob_alloc(c->size, flags, c->align, node, 0);
583 trace_kmem_cache_alloc_node(_RET_IP_, b, c->object_size,603 trace_kmem_cache_alloc_node(_RET_IP_, b, c->object_size,
584 SLOB_UNITS(c->size) * SLOB_UNIT,604 SLOB_UNITS(c->size) * SLOB_UNIT,
585 flags, node);605 flags, node);

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t