内核内存分配

GFP_KERNEL内存通常的分配方法,可能会引起休眠。

GFP_ATOMIC用于在中断处理例程或者其他运行在进程上下文中的代码分配内存,不会休眠。

上面这些标志可以和下面的符号位或起来使用。

__GFP_DMA该标志请求内存分配发生在可进行DMA的内存区段中:如果低地址段没有可用内存那么分配失败。如果没有什么特殊的标志,一般是搜索DMA和常规区段的内存;而如果设置了__GFP_HIGHMEM标志的话,那么内存分配会搜索高端内存区域。

后备高速缓存

驱动程序经常会反复分配很多同一大小的内存块。既然内核已经维护了一组拥有统一大小内存块的内存池,它同样为反复使用的块增加了某些特殊的内存池,通常称为lookaside cache。设备驱动程序通常不会涉及这种使用后备高速缓存的内存行为,除了USB和SCSI驱动程序。

内核高速缓存管理就是著名的slab分配器。因此定义在<linux/slab.h>中。

slab分配器分配的高速缓存是kmem_cache_t类型,可以通过kmem_cache_create创建:

 1 struct kmem_cache {
 2 /* 1) per-cpu data, touched during every alloc/free 每次alloc都会被创建*/
 3     struct array_cache *array[NR_CPUS];
 4 /* 2) Cache tunables. Protected by cache_chain_mutex cache微调器 被cache_chain_mutex cache保护*/
 5     unsigned int batchcount;
 6     unsigned int limit;
 7     unsigned int shared;
 8 
 9     unsigned int buffer_size;
10     u32 reciprocal_buffer_size;
11 /* 3) touched by every alloc & free from the backend */
12 
13     unsigned int flags;        /* constant flags */
14     unsigned int num;        /* # of objs per slab */
15 
16 /* 4) cache_grow/shrink */
17     /* order of pgs per slab (2^n) */
18     unsigned int gfporder;
19 
20     /* force GFP flags, e.g. GFP_DMA */
21     gfp_t gfpflags;
22 
23     size_t colour;            /* cache colouring range */
24     unsigned int colour_off;    /* colour offset */
25     struct kmem_cache *slabp_cache;
26     unsigned int slab_size;
27     unsigned int dflags;        /* dynamic flags */
28 
29     /* constructor func */
30     void (*ctor)(void *obj);
31 
32 /* 5) cache creation/removal */
33     const char *name;
34     struct list_head next;
35 
36 /* 6) statistics */
37 #ifdef CONFIG_DEBUG_SLAB
38     unsigned long num_active;
39     unsigned long num_allocations;
40     unsigned long high_mark;
41     unsigned long grown;
42     unsigned long reaped;
43     unsigned long errors;
44     unsigned long max_freeable;
45     unsigned long node_allocs;
46     unsigned long node_frees;
47     unsigned long node_overflow;
48     atomic_t allochit;
49     atomic_t allocmiss;
50     atomic_t freehit;
51     atomic_t freemiss;
52 
53     /*
54      * If debugging is enabled, then the allocator can add additional
55      * fields and/or padding to every object. buffer_size contains the total
56      * object size including these internal fields, the following two
57      * variables contain the offset to the user object and its size.
58      */
59     int obj_offset;
60     int obj_size;
61 #endif /* CONFIG_DEBUG_SLAB */
62 
63     /*
64      * We put nodelists[] at the end of kmem_cache, because we want to size
65      * this array to nr_node_ids slots instead of MAX_NUMNODES
66      * (see kmem_cache_init())
67      * We still use [MAX_NUMNODES] and not [1] or [0] because cache_cache
68      * is statically defined, so we reserve the max number of nodes.
69      */
70     struct kmem_list3 *nodelists[MAX_NUMNODES];
71     /*
72      * Do not add fields after nodelists[]
73      */
74 };

这个结构是kmem_alloc_create创造出来的内存,具体里面有几个对象则是由kmem_create_alloc创建的。下面先来大体看一下。

struct kmem_cache *kmem_cache_create(const char *name, size_t size,
    size_t align, unsigned long flags, void (*ctor)(void *))
{
    struct kmem_cache *c;

    c = slob_alloc(sizeof(struct kmem_cache),
        GFP_KERNEL, ARCH_KMALLOC_MINALIGN, -1);

    if (c) {
        c->name = name;
        c->size = size;
        if (flags & SLAB_DESTROY_BY_RCU) {
            /* leave room for rcu footer at the end of object */
            c->size += sizeof(struct slob_rcu);
        }
        c->flags = flags;
        c->ctor = ctor;
        /* ignore alignment unless it's forced */
        c->align = (flags & SLAB_HWCACHE_ALIGN) ? SLOB_ALIGN : 0;
        if (c->align < ARCH_SLAB_MINALIGN)
            c->align = ARCH_SLAB_MINALIGN;
        if (c->align < align)
            c->align = align;
    } else if (flags & SLAB_PANIC)
        panic("Cannot create slab cache %s
", name);

    kmemleak_alloc(c, sizeof(struct kmem_cache), 1, GFP_KERNEL);
    return c;
}

 以上函数创建出kmem_cache之后,然后将kmem_cache传给kmem_cache_alloc函数,这将会创建内存对象,释放内存对象调用kmem_cache_free(),释放高速缓存使用kmem_cache_destory()。

内存池

内存池中有些地方的内存是不允许分配失败的。为了确保这种情况下的分配成功,建立了一种叫mempool的技术。内存池其实就是某种形式的后备高速缓存它试图一直保存空闲的内存,以备不时之需。

内存池对象是mempool_t在<linux/mempool.h>中定义。

 1 typedef struct mempool_s {
 2     spinlock_t lock;
 3     int min_nr;        /* nr of elements at *elements 内存池应该始终保持的已分配对象的最小数目*/
 4     int curr_nr;        /* Current nr of elements at *elements */
 5     void **elements;
 6 
 7     void *pool_data;
 8     mempool_alloc_t *alloc;/*对象的实际分配和释放有这俩个函数指针来处理 */
 9     mempool_free_t *free;
10     wait_queue_head_t wait;
11 } mempool_t;

可以用mempool_create来建立内存池对象。

 1 /**
 2  * mempool_create - create a memory pool
 3  * @min_nr:    the minimum number of elements guaranteed to be
 4  *             allocated for this pool.
 5  * @alloc_fn:  user-defined element-allocation function.
 6  * @free_fn:   user-defined element-freeing function.
 7  * @pool_data: optional private data available to the user-defined functions.
 8  *
 9  * this function creates and allocates a guaranteed size, preallocated
10  * memory pool. The pool can be used from the mempool_alloc() and mempool_free()
11  * functions. This function might sleep. Both the alloc_fn() and the free_fn()
12  * functions might sleep - as long as the mempool_alloc() function is not called
13  * from IRQ contexts.
14  */
15 mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn,
16                 mempool_free_t *free_fn, void *pool_data)
17 {
18     return  mempool_create_node(min_nr,alloc_fn,free_fn, pool_data,-1);
19 }
20 EXPORT_SYMBOL(mempool_create);
21 
22 mempool_t *mempool_create_node(int min_nr, mempool_alloc_t *alloc_fn,
23             mempool_free_t *free_fn, void *pool_data, int node_id)
24 {
25     mempool_t *pool;
26     pool = kmalloc_node(sizeof(*pool), GFP_KERNEL | __GFP_ZERO, node_id);
27     if (!pool)
28         return NULL;
29     pool->elements = kmalloc_node(min_nr * sizeof(void *),
30                     GFP_KERNEL, node_id);
31     if (!pool->elements) {
32         kfree(pool);
33         return NULL;
34     }
35     spin_lock_init(&pool->lock);
36     pool->min_nr = min_nr;
37     pool->pool_data = pool_data;
38     init_waitqueue_head(&pool->wait);
39     pool->alloc = alloc_fn;
40     pool->free = free_fn;
41 
42     /*
43      * First pre-allocate the guaranteed number of buffers.
44      */
45     while (pool->curr_nr < pool->min_nr) {
46         void *element;
47 
48         element = pool->alloc(GFP_KERNEL, pool->pool_data);
49         if (unlikely(!element)) {
50             free_pool(pool);
51             return NULL;
52         }
53         add_element(pool, element);
54     }
55     return pool;
56 }

这和前面的步骤类似,首先cache=kmem_cache_create(...);创立一块内存。

然后通过pool=mempool_create(MY_POOL_MINIMUM,mempool_alloc_slab,mempool_free_slab,cache);创建内存池。

其中mempool_alloc_slab,mempool_free_slab是内核提供的俩个原型函数,和参数中的原型匹配,直接拿来用就可以。

用完后,可以通过

void* mempool_alloc(mempool_t *pool,int gfp_mask);

void mempool_free(void *element,mempool_t *pool);

当使用mempool_free去释放一个对象时,如果预先分配的对象数目小于要求的最低数目,那么这部分对象会被保存在内存池中,否则就会返回给系统。

可以利用mempool_resize来调整mempool的大小。

 1 /**
 2  * mempool_resize - resize an existing memory pool
 3  * @pool:       pointer to the memory pool which was allocated via
 4  *              mempool_create().
 5  * @new_min_nr: the new minimum number of elements guaranteed to be
 6  *              allocated for this pool.
 7  * @gfp_mask:   the usual allocation bitmask.
 8  *
 9  * This function shrinks/grows the pool. In the case of growing,
10  * it cannot be guaranteed that the pool will be grown to the new
11  * size immediately, but new mempool_free() calls will refill it.
12  *
13  * Note, the caller must guarantee that no mempool_destroy is called
14  * while this function is running. mempool_alloc() & mempool_free()
15  * might be called (eg. from IRQ contexts) while this function executes.
16  */
17 int mempool_resize(mempool_t *pool, int new_min_nr, gfp_t gfp_mask)
18 {
19     void *element;
20     void **new_elements;
21     unsigned long flags;
22 
23     BUG_ON(new_min_nr <= 0);
24 
25     spin_lock_irqsave(&pool->lock, flags);
26     if (new_min_nr <= pool->min_nr) {
27         while (new_min_nr < pool->curr_nr) {
28             element = remove_element(pool);
29             spin_unlock_irqrestore(&pool->lock, flags);
30             pool->free(element, pool->pool_data);
31             spin_lock_irqsave(&pool->lock, flags);
32         }
33         pool->min_nr = new_min_nr;
34         goto out_unlock;
35     }
36     spin_unlock_irqrestore(&pool->lock, flags);
37 
38     /* Grow the pool */
39     new_elements = kmalloc(new_min_nr * sizeof(*new_elements), gfp_mask);
40     if (!new_elements)
41         return -ENOMEM;
42 
43     spin_lock_irqsave(&pool->lock, flags);
44     if (unlikely(new_min_nr <= pool->min_nr)) {
45         /* Raced, other resize will do our work */
46         spin_unlock_irqrestore(&pool->lock, flags);
47         kfree(new_elements);
48         goto out;
49     }
50     memcpy(new_elements, pool->elements,
51             pool->curr_nr * sizeof(*new_elements));
52     kfree(pool->elements);
53     pool->elements = new_elements;
54     pool->min_nr = new_min_nr;
55 
56     while (pool->curr_nr < pool->min_nr) {
57         spin_unlock_irqrestore(&pool->lock, flags);
58         element = pool->alloc(gfp_mask, pool->pool_data);
59         if (!element)
60             goto out;
61         spin_lock_irqsave(&pool->lock, flags);
62         if (pool->curr_nr < pool->min_nr) {
63             add_element(pool, element);
64         } else {
65             spin_unlock_irqrestore(&pool->lock, flags);
66             pool->free(element, pool->pool_data);    /* Raced */
67             goto out;
68         }
69     }
70 out_unlock:
71     spin_unlock_irqrestore(&pool->lock, flags);
72 out:
73     return 0;
74 }

如果不需要内存池了就用mempool_destroy()来将内存返回给系统。

 1 /**
 2  * mempool_destroy - deallocate a memory pool
 3  * @pool:      pointer to the memory pool which was allocated via
 4  *             mempool_create().
 5  *
 6  * this function only sleeps if the free_fn() function sleeps. The caller
 7  * has to guarantee that all elements have been returned to the pool (ie:
 8  * freed) prior to calling mempool_destroy().
 9  */
10 void mempool_destroy(mempool_t *pool)
11 {
12     /* Check for outstanding elements */
13     BUG_ON(pool->curr_nr != pool->min_nr);
14     free_pool(pool);
15 }

销毁mempool之前一定要将所有已经分配对象返回到内存池中否则会oops。

mempool会浪费内存,如果驱动能存在某种形式响应分配的失败,而不会对系统一致性造成破坏,则应该使用这种方式。

get_free_page和相关函数

如果模块需要分配大块的北村,使用面向页的分配技术会更好一些。整页分配还有一些优点后面介绍。

分配页面可以使用函数get_zeroed_page()

1 unsigned long get_zeroed_page(gfp_t gfp_mask)
2 {
3     return __get_free_pages(gfp_mask | __GFP_ZERO, 0);
4 }
 1 /*
 2  * Common helper functions.
 3  */
 4 unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
 5 {
 6     struct page *page;
 7 
 8     /*
 9      * __get_free_pages() returns a 32-bit address, which cannot represent
10      * a highmem page
11      */
12     VM_BUG_ON((gfp_mask & __GFP_HIGHMEM) != 0);
13 
14     page = alloc_pages(gfp_mask, order);
15     if (!page)
16         return 0;
17     return (unsigned long) page_address(page);
18 }

这个函数发返回指向页面的指针,并且将页面清零。

__get_free_page()这个函数类似上面的get_zeroed_page但是不清空页面。

1 #define __get_free_page(gfp_mask) 
2         __get_free_pages((gfp_mask), 0)

最后一个就是__get_free_pages()用来分配若干物理连续的页面,并且返回指向该内存区域的第一个字节的指针,但不清0页面。

/*
 * Common helper functions.
 */
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
{
    struct page *page;

    /*
     * __get_free_pages() returns a 32-bit address, which cannot represent
     * a highmem page
     */
    VM_BUG_ON((gfp_mask & __GFP_HIGHMEM) != 0);

    page = alloc_pages(gfp_mask, order);
    if (!page)
        return 0;
    return (unsigned long) page_address(page);
}

参数flags的作用和kmalloc中的一样;通常使用GFP_KERNEL 或者GFP_ATOMIC,也许还会加上__GFP_DMA(申请可用于ISA直接内存访问操作的内存)或者__GFP_HIGHMEM标志(使用高端内存),order是要申请或者释放的页面的2次数的数量,0表示一个页面,order为3表示8个页面。如果指数太大,有没有连续的页面可以分配就会返回失败。get_order函数使用一个整数参数可以根据宿主平台的大小(必须是2的幂)返回order值。可以是10或者11对应1024或者2048,依赖于体系结构。但是对于刚启动的系统而言,以阶数为10进行分配二成功的机会很小。

可以通过/proc/buddyinfo可以得知系统上每个内存区段上每个阶数下可以获得的数据块数目。

当程序不再需要的页面,可以通过下列函数之一来释放。第一个函数是宏,展开后是对第二个函数的调用:

void free_page();

void free_pages();

1 void __free_pages(struct page *page, unsigned int order)
2 {
3     if (put_page_testzero(page)) {
4         if (order == 0)
5             free_hot_cold_page(page, 0);
6         else
7             __free_pages_ok(page, order);
8     }
9 }

如果试图释放和先前分配的数目不相等的页面,内存映射关系就会被破坏掉,随后系统就会出错。

和malloc一样,get_free_pages可以在任何时候调用。某些情况下函数分配内存会失败,特别是使用了GFP_ATOMIC的时候。因此,调用了这些函数的程序在分配出错的时候都应该提供相应的处理。尽管kmalloc(GFP_KERNEL)在没有空闲内存时有时会失败,但是内核总会尽一切可能满足这个内存分配请求。因此,如果分配太多内存,系统的相应很容易慢下来。例如,如果往某一个scull写入大量数据,计算机可能死掉;当系统为满足kmalloc分配请求而试图换出尽可能多的内存页时,就会变得汗漫。所有的资源都被这个贪婪的设备所吞噬,计算机很快死机;甚至无法生成新的进程来解决这个问题。同时因为模块属于特权代码,这会带来DOS安全漏洞。

alloc_pages接口

struct page是内核用来描述某一内存页的数据结构。内核有许多地方用到这个page结构,尤其是在使用高端内存的(高端内存在内核空间没有对应不变的地址)地方。

linux页分配器的核心代码是alloc_pages_node的函数:

1 #define alloc_pages(gfp_mask, order) 
2         alloc_pages_node(numa_node_id(), gfp_mask, order)

核心函数alloc_pages_node要求传入三个参数。nid是NUMA节点的ID号,NUMA计算机是多处理器系统,其中内存对特定处理器组(节点)来说是本地的,访问本地内存要比访问非本地的内存要快。在这类问题上,在正确的节点分配正确的内存非常重要,nid表示要在对应的NUMA节点分配内存,flags通常就是GFP_分配标志,而order是要分配的内存大小
。该函数的返回值是指向第一个page结构(可能返回多个页)的指针,它描述了已分配的内存;或者在失败是返回NULL。

alloc_pages通过在当前NUMA节点上分配内存而简化了alloc_pages_node函数,将numa_node_id的返回值作为nid参数调用了核心函数alloc_pages_node。另外,alloc_page函数显然忽略了order参数而只分配单个页面。

 1   #ifdef CONFIG_NUMA
 2 extern struct page *alloc_pages_current(gfp_t gfp_mask, unsigned order);
 3   
 4   static inline struct page *
 5   alloc_pages(gfp_t gfp_mask, unsigned int order)
 6   {
 7       return alloc_pages_current(gfp_mask, order);
 8   }
 9   extern struct page *alloc_pages_vma(gfp_t gfp_mask, int order,
10              struct vm_area_struct *vma, unsigned long addr,
11              int node);
12  #else
13  #define alloc_pages(gfp_mask, order) 
14          alloc_pages_node(numa_node_id(), gfp_mask, order)
15  #define alloc_pages_vma(gfp_mask, order, vma, addr, node)    
16      alloc_pages(gfp_mask, order)
17 #endif

为了释放上述分配的页面,我们应该使用一下函数:

void __free_page(struct page *page);

void __free_pages(struct page *page,unsigned int order);

void free_hot_page(struct page *pages);

void free_cold_page(struct page *pages);

如果读者知道某一个页面中的内容是否驻留在处理器高速缓存中,则应该使用free_hot_page(用于驻留在高速缓存中的页)或者free_cold_page和内和通信。这个信息可以帮助内存分配器优化内存的使用。

vmalloc及其辅助函数

下面要介绍的内存分配函数是vmalloc,分配虚拟地址空间的连续区域。尽管这段区域在物理上可能是不连续的(要访问其中的每个页面都必须独立地调用函数alloc_page),内核确认为它们在地址上是连续的。vmalloc在发生错误时返回0NULL,成功时返回一个指针,改制真指向一个线性的、大小最少为size的线性内存区域。

我们在这里描述vmalloc的原因是,它是内存分配机制的基础。但是我们要注意在大多数情况下不鼓励使用vmalloc。通过vmalloc获得的内存使用起来效率不高,而且在某些体系架构上,用于vmalloc的地址空间总量相对较小。如果可能,应该直接和单个的页面打交道而不是使用vmalloc。

该函数的原型以及相关函数ioremap ioremap不是严格的分配函数,如下所示:

#include <linux/vmalloc.h>

void *vmalloc(unsigned long size);

void vfree(void * addr);

void *ioremap(unsigned long offset,unsigned long size);

void iounmap(void * addr);

由kmalloc和__get_free_pages返回的内存地址也是虚拟地址,实际值仍然要由MMU处理才能转换为物理内存地址。某些体系结构定义了保留的虚拟地址范围,用于寻址物理内存。遇到这种情况时,linux内核会利用这种,内核和__get_free_pages地址均位于这种内存范围。其中的区别对设备驱动程序透明的,对于不直接涉及内存管理子系统的其他内核代码来说也是透明的。vmalloc在如何使用硬件上没有区别,区别在于内核如何执行分配任务上。

kmalloc和__get_free_pages使用的虚拟地址范围和物理内存是一一对应的,可能会有基于常量的PAGE_OFFSET的一个便宜。这两个函数不需要为该地址段修改页表。但另一方面,vmalloc和ioremap使用的地址范围完全是虚拟的,每次分配都需要通过对页表的适当设置来建立虚拟内存区域。

可以通过比较内存分配函数返回的指针来发现这种区别。在某一些平台上x86,vmalloc返回的地址仅仅比kmalloc返回的地址高一些;而在其他平台上(MIPS和IA-64),它们就完全属于不同的地址范围了。vmalloc可以获得的地址在VMALLOC_START到VMALLOC_END的范围中。这两个符号<asm/pgtable.h>中定义。

用VMALLOC分配的地址是不能在微处理器之外使用的,因为它们只在处理器的内存管理单元上才有意义。当驱动程序需要真正的物理地址的时候(像外设用以驱动系统总线的DMA),就不能使用vmalloc了。使用vmalloc的正确场合是在分配一大块连续的、只在软件中存在的、用于缓冲的内存区域的时候。注意vmalloc的开销要比__get_free_pages大,因为他不但获取内存,还要建立页表。因此,用vmalloc函数分配仅仅一页的内存空间是不值得的。

使用vmalloc函数的一个例子函数是create_module系统调用,他利用vmalloc函数来获取装载模块所需的内存空间。在调用insmod重定位模块代码后,接着会调用copy_from_user函数把模块代码和数据复制到分配的空间内。这样模块看起来像是在连续的内存空间内。但是通过检查/proc/ksyms文件就能发现模块导出的内核符号和内核本身的符号分布在不同的内存范围上。

用vmalloc分配得到的内存空间要用vfree来释放,这就像要用kfree来释放kmalloc函数分配得到的内存空间一样。

和vmalloc一样,ioremap也建立新的页表,但是和vmalloc不同的是,ioremap并不实际分配内存。ioremap的返回值是一个特殊虚拟地址,可以用来访问指定的物理内存区域,这个虚拟地址最后调用iounmap来释放掉。

ioremap更多用于映射物理PCI缓冲区地址到虚拟的内核空间。例如,可以用来访问PCI视频设备的帧缓冲区;该缓冲区通常被映射到高物理地址,超出了系统初始化时建立的页表地址范围。

为了可移植性,不应该吧ioremap返回的地址当做指向内存的指针而直接访问。相反,应该使用readb或者其他io函数。这是因为,在alpha平台上,由于pci规范和alpha处理器在数据传输上方式上的差异,不能直接把PCI内存映射到处理器的地址空间。

ioremap和vmalloc函数都是面向页的(它们都会修改页表),因此重新定位或分配的内存空间实际上都会上调到最近一个页边界。ioremap通过把重新映射的地址向下下调到页边界,并返回第一个重新映射页面中的偏移量的方法模拟了不对齐的映射。

vmalloc函数有个小缺点就是不能在原子的上下文中使用,因为内部实现调用了kmallocGFP_KERNEL来获取页表的存储空间,因而可能休眠。但这并不是什么问题--如果__get_free_page函数都还不能满足中断处理例程的需求的话,那就应该修改软件的设计了。

per-CPU变量

per-cpu变量是2.6内核的一个有趣的特性。每当建立一个percpu变量时,系统中的每个处理器都会拥有该变量的特有副本。这看起来有点奇怪,但是又有点。对于percpu变量的访问几乎不需要锁定,因为每一个处理器在其自己的副本上工作。percpu变量海可以保存在对应的处理器高速缓存中,这样,就可以在频繁的更新时获得更好的性能。关于percpu变量的使用例子可以见网络子系统。内和维护着大量计数器,这些计数器跟踪已收到的各类数据包,而这些计数器可能每秒更新上千次。网络子系统的开发者将这些统计用的计数器放在额了percpu变量中,这样,他们就不必处理缓存和锁的问题而更新可在不用锁的情况下快速完成。在用户空间偶尔请求这些计数器的值时,只需要将每个处理器的版本想家并返回合计值即可。

用于percpu变量的声明在<linux/percpu.h>中。要在编译期间创建一个percpu变量,可使用以下宏:

DEFINE_PER_CPU(type,name);

如果该变量name是一个数组,需要在type中包含数组的维度。这样,具有三个整数percpu数组变量可通过下面的语句建立:

DEFINE_PER_CPU(int[3],my_percpu_array);

对于percpu变量的操作几乎不用任何锁定就可以完成。但要记得2.6内核是抢占式的:也就是说,当处理器在修改某一个percpu变量的临界区中间,可能会抢占,因此应该避免这种情况的发生。我们还应该避免进程正在访问一个percpu变量时被切换到另一个处理器上运行。为此我们应该显示地调用get_cpu_var宏访问某给定变量的当前处理器副本,结束后调用put_cpu_var。对于get_cpu_var宏的调用将返回当前处理器变量版本的lvalue值,并禁止抢占。因为返回的是lvalue,因此可直接赋值或者操作。例如,网络代码对一个计数器的递增使用了一下两条语句:

get_cpu_var(sockets_in_use)++;

put_cpu_var(sockets_in_use);

我们可以使用per_cpu(variable,int cpu_id);

如果我们的代码设计多个处理器的percpu变量,这时则需要采用某种锁定机制来确保访问安全。

动态分配percpu也是可能的。则应使用下面的函数分配变量;

void * alloc_percpu(type);

void *__alloc_percpu(size_t size,size_t align);

大多数情况下可以使用alloc_percpu完成分配工作;但是如果需要特定的对齐,则应该调用__alloc_percpu函数。不管使用哪一个函数都可用free_percpu将percpu变量返回给系统。对动态分配的percpu变量的访问通过per_cpu_ptr完成。

per_cpu_ptr(void *per_cpu_var,int cpu_id);

这个宏返回指相对应cpu_id的per_cpu_var版本的指针。如果打算读取该变量的其他cpu版本,则可以引用该指针并进行相关操作。但是,如果打算读取该变量的其他cpu版本,则可以引用该指针并进行相关操作。但是,如果正在操作当前处理器的版本,则应该首先保证自己不会被切换到其他处理器上运行。如果对percpu变量的整个访问发生在拥有某个自旋锁的情况下,则不会发生问题。但是,在使用该变量的时候通常需要使用get_cpu来阻塞抢占。这样,使用动态percpu变量的代码类似下面所示

int cpu;

cpu = get_cpu();

ptr = per_cpu_ptr(per_cpu_var,cpu);

/*使用ptr*/

put_cpu();

如果使用编译期间的per-CPU变量,则get_cpu_var和put_cpu_var宏将处理这些细节,但动态的percpu变量更需要明确的保护。

percpu变量可以导出给模块但是必须使用上述宏的特殊版本。

EXPORT_PER_CPU_SYMBOL(per_cpu_var);

EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var);

要在模块中访问这么一个变量,则应将其声明如下:

DECLARE_PER_CPU(type,name);

这将告诉编译器要使用一个外部引用。

可参考linux/percpu_counter.h已经封装好的实现。在某些体系架构上,percpu变量可用的地址空间受限制,因此,如果要创建percpu变量,则应保持这些变量比较小。

原文地址:https://www.cnblogs.com/flintlovesam/p/5094008.html