深入浅出内存管理--kmalloc支持的最大内存分配

首先我们来看下kmalloc的实现,本文基于kernel 4.0版本:

static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
    if (__builtin_constant_p(size)) {  -----------(1)
        if (size > KMALLOC_MAX_CACHE_SIZE)
            return kmalloc_large(size, flags); --------------(2)
#ifndef CONFIG_SLOB
        if (!(flags & GFP_DMA)) {
            int index = kmalloc_index(size);

            if (!index)
                return ZERO_SIZE_PTR;

            return kmem_cache_alloc_trace(kmalloc_caches[index], -----------------(3)
                    flags, size);
        }
#endif
    }
    return __kmalloc(size, flags);------------------(4)
}

(1) __builtin_constant_p表示传入的是否为一个实数,gcc编译器会做这个判断,如果是一个确定的实数而非变量,那么它返回true,主要用于编译优化的处理。
(2) 如果是实数,那么会判断size是否大于KMALLOC_MAX_CACHE_SIZE,此值表示的是系统创建的slab cache的最大值,系统为kmalloc预先创建了很多大小不同的kmem cache,用于内存分配。这里的含义就是如果内存申请超过此值,那么直接使用 kmalloc_large进行大内存分配,实际上最终会调用页分配器去分配内存,而不是使用slab分配器。
(3) kmalloc_large分配大内存,后面会讲到,实际上也就是调用页分配器去分配内存。
(4) 最后如果是一个变量,那么会调用__kmalloc来进行分配。

和此函数相关的宏定义有如下几个:

#define MAX_ORDER 11
#define PAGE_SHIFT      12
 #define KMALLOC_SHIFT_HIGH  ((MAX_ORDER + PAGE_SHIFT - 1) <= 25 ? \
                 (MAX_ORDER + PAGE_SHIFT - 1) : 25)
 #define KMALLOC_SHIFT_MAX   KMALLOC_SHIFT_HIGH

/* Maximum allocatable size */
#define KMALLOC_MAX_SIZE    (1UL << KMALLOC_SHIFT_MAX)
/* Maximum size for which we actually use a slab cache */
#define KMALLOC_MAX_CACHE_SIZE  (1UL << KMALLOC_SHIFT_HIGH)
/* Maximum order allocatable via the slab allocagtor */
#define KMALLOC_MAX_ORDER   (KMALLOC_SHIFT_MAX - PAGE_SHIFT)

这里可能每个平台定义不同,以我的arm32为例,经过换算可知,KMALLOC_MAX_SIZE是4M。

KMALLOC_MAX_SIZE  = (1<<22) = 4M
KMALLOC_MAX_CACHE_SIZE = (1<<22) = 4M
KMALLOC_SHIFT_HIGH = 22
KMALLOC_SHIFT_MAX  = 22

__kmalloc路径

static __always_inline void *__do_kmalloc(size_t size, gfp_t flags,
                      unsigned long caller)
{
    struct kmem_cache *cachep;
    void *ret;

    cachep = kmalloc_slab(size, flags);
    if (unlikely(ZERO_OR_NULL_PTR(cachep)))
        return cachep;
    ret = slab_alloc(cachep, flags, caller);
       
    trace_kmalloc(caller, ret,
              size, cachep->size, flags);
           
    return ret;
}
    
void *__kmalloc(size_t size, gfp_t flags)
{      
    return __do_kmalloc(size, flags, _RET_IP_);
}  

当代码跑到__kmalloc之后,我们继续跟进,发现会先查找kmalloc_slab,然后在对应的slab kmem cache中去申请内存来使用,采用slab_alloc来申请内存。那么内存大小的限制在如下代码中得以体现:

struct kmem_cache *kmalloc_slab(size_t size, gfp_t flags)
{   
    int index;

    if (unlikely(size > KMALLOC_MAX_SIZE)) {
        WARN_ON_ONCE(!(flags & __GFP_NOWARN));
        return NULL;
    }

    if (size <= 192) {
        if (!size)
            return ZERO_SIZE_PTR;

        index = size_index[size_index_elem(size)];
    } else
        index = fls(size - 1);

#ifdef CONFIG_ZONE_DMA
    if (unlikely((flags & GFP_DMA)))
        return kmalloc_dma_caches[index];
    
#endif
    return kmalloc_caches[index];
}   

在这里判断了size大小是否超过KMALLOC_MAX_SIZE,也就是前面定义的4M,如果超过了就返回NULL,最终kmalloc申请失败。

kmalloc_large

想象一下,如果我们调用了一次内存分配函数,代码跑到了kmalloc_large,并且此size超过了伙伴系统能够支持的最大申请大小,比如order>11个page大小的内存,那么系统会在哪里判断返回呢?带着这个问题,我们看一下它的实现过程:

static __always_inline void *kmalloc_large(size_t size, gfp_t flags)
{
    unsigned int order = get_order(size);
    return kmalloc_order_trace(size, flags, order);
}

get_order返回值如下:

 0 -> 2^0 * PAGE_SIZE and below
 1 -> 2^1 * PAGE_SIZE to 2^0 * PAGE_SIZE + 1
 2 -> 2^2 * PAGE_SIZE to 2^1 * PAGE_SIZE + 1
 3 -> 2^3 * PAGE_SIZE to 2^2 * PAGE_SIZE + 1
 4 -> 2^4 * PAGE_SIZE to 2^3 * PAGE_SIZE + 1

紧接着,函数调用到kmalloc_order:

void *kmalloc_order(size_t size, gfp_t flags, unsigned int order)
{
    void *ret;
    struct page *page;

    flags |= __GFP_COMP;
    page = alloc_kmem_pages(flags, order);
    ret = page ? page_address(page) : NULL;
    kmemleak_alloc(ret, size, 1, flags);
    kasan_kmalloc_large(ret, size);
    return ret;
}

进一步调用到alloc_kmem_pages:

struct page *alloc_kmem_pages(gfp_t gfp_mask, unsigned int order)
{
    struct page *page;
    struct mem_cgroup *memcg = NULL;

    if (!memcg_kmem_newpage_charge(gfp_mask, &memcg, order))
        return NULL;
    page = alloc_pages(gfp_mask, order);
    memcg_kmem_commit_charge(page, memcg, order);
    return page;
}

最终调用到了alloc_pages,看过我前面文章的应该很熟悉了,这个就是页分配器的接口了,最终是会利用伙伴系统算法进行页的分配。看下伙伴系统核心:

static inline
struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
                        int migratetype)
{
    unsigned int current_order;
    struct free_area *area;
    struct page *page;

    /* Find a page of the appropriate size in the preferred list */
    for (current_order = order; current_order < MAX_ORDER; ++current_order) {
        area = &(zone->free_area[current_order]);
        if (list_empty(&area->free_list[migratetype]))
            continue;

        page = list_entry(area->free_list[migratetype].next,
                            struct page, lru);
        list_del(&page->lru);
        rmv_page_order(page);
        area->nr_free--;
        expand(zone, page, order, current_order, area, migratetype);
        set_freepage_migratetype(page, migratetype);
        return page;
    }

    return NULL;
}

终于在这里有个current_order < MAX_ORDER的限制,这里也就是限制伙伴系统能够分配的内存大小最大不超过2^(MAX_ORDER-1)个page,经过计算可知:

2 ^ 10 * 4K = 4M

因此在我的平台上最大使用kmalloc申请不能超过4M大小。

你可能感兴趣的:(内核笔记,深入浅出内存管理)