about percpu

来源:互联网 发布:android final数组 编辑:程序博客网 时间:2024/05/20 13:40


http://www.wowotech.net/linux_kenrel/per-cpu.html

Linux内核同步机制之(二):Per-CPU变量

作者:linuxer 发布于:2014-10-16 11:17 分类:内核同步机制

一、源由:为何引入Per-CPU变量?

1、lock bus带来的性能问题

在ARM平台上,ARMv6之前,SWP和SWPB指令被用来支持对shared memory的访问:

SWP <Rt>, <Rt2>, [<Rn>]

Rn中保存了SWP指令要操作的内存地址,通过该指令可以将Rn指定的内存数据加载到Rt寄存器,同时将Rt2寄存器中的数值保存到Rn指定的内存中去。

我们在原子操作那篇文档中描述的read-modify-write的问题本质上是一个保持对内存read和write访问的原子性的问题。也就是说对内存的读和写的访问不能被打断。对该问题的解决可以通过硬件、软件或者软硬件结合的方法来进行。早期的ARM CPU给出的方案就是依赖硬件:SWP这个汇编指令执行了一次读内存操作、一次写内存操作,但是从程序员的角度看,SWP这条指令就是原子的,读写之间不会被任何的异步事件打断。具体底层的硬件是如何做的呢?这时候,硬件会提供一个lock signal,在进行memory操作的时候设定lock信号,告诉总线这是一个不可被中断的内存访问,直到完成了SWP需要进行的两次内存访问之后再clear lock信号。

lock memory bus对多核系统的性能造成严重的影响(系统中其他的processor对那条被lock的memory bus的访问就被hold住了),如何解决这个问题?最好的锁机制就是不使用锁,因此解决这个问题可以使用釜底抽薪的方法,那就是不在系统中的多个processor之间共享数据,给每一个CPU分配一个不就OK了吗。

当然,随着技术的发展,在ARMv6之后的ARM CPU已经不推荐使用SWP这样的指令,而是提供了LDREX和STREX这样的指令。这种方法是使用软硬件结合的方法来解决原子操作问题,看起来代码比较复杂,但是系统的性能可以得到提升。其实,从硬件角度看,LDREX和STREX这样的指令也是采用了lock-free的做法。OK,由于不再lock bus,看起来Per-CPU变量存在的基础被打破了。不过考虑cache的操作,实际上它还是有意义的。

2、cache的影响

在The Memory Hierarchy文档中,我们已经了解了关于memory一些基础的知识,一些基础的内容,这里就不再重复了。我们假设一个多核系统中的cache如下:

cache

每个CPU都有自己的L1 cache(包括data cache和instruction cache),所有的CPU共用一个L2 cache。L1、L2以及main memory的访问速度之间的差异都是非常大,最高的性能的情况下当然是L1 cache hit,这样就不需要访问下一阶memory来加载cache line。

我们首先看在多个CPU之间共享内存的情况。这种情况下,任何一个CPU如果修改了共享内存就会导致所有其他CPU的L1 cache上对应的cache line变成invalid(硬件完成)。虽然对性能造成影响,但是系统必须这么做,因为需要维持cache的同步。将一个共享memory变成Per-CPU memory本质上是一个耗费更多memory来解决performance的方法。当一个在多个CPU之间共享的变量变成每个CPU都有属于自己的一个私有的变量的时候,我们就不必考虑来自多个CPU上的并发,仅仅考虑本CPU上的并发就OK了。当然,还有一点要注意,那就是在访问Per-CPU变量的时候,不能调度,当然更准确的说法是该task不能调度到其他CPU上去。目前的内核的做法是在访问Per-CPU变量的时候disable preemptive,虽然没有能够完全避免使用锁的机制(disable preemptive也是一种锁的机制),但毫无疑问,这是一种代价比较小的锁。

二、接口

1、静态声明和定义Per-CPU变量的API如下表所示:

声明和定义Per-CPU变量的API描述DECLARE_PER_CPU(type, name)
DEFINE_PER_CPU(type, name)普通的、没有特殊要求的per cpu变量定义接口函数。没有对齐的要求DECLARE_PER_CPU_FIRST(type, name)
DEFINE_PER_CPU_FIRST(type, name)通过该API定义的per cpu变量位于整个per cpu相关section的最前面。DECLARE_PER_CPU_SHARED_ALIGNED(type, name)
DEFINE_PER_CPU_SHARED_ALIGNED(type, name)通过该API定义的per cpu变量在SMP的情况下会对齐到L1 cache line ,对于UP,不需要对齐到cachine lineDECLARE_PER_CPU_ALIGNED(type, name)
DEFINE_PER_CPU_ALIGNED(type, name)无论SMP或者UP,都是需要对齐到L1 cache lineDECLARE_PER_CPU_PAGE_ALIGNED(type, name)
DEFINE_PER_CPU_PAGE_ALIGNED(type, name)为定义page aligned per cpu变量而设定的API接口DECLARE_PER_CPU_READ_MOSTLY(type, name)
DEFINE_PER_CPU_READ_MOSTLY(type, name)通过该API定义的per cpu变量是read mostly的

  看到这样“丰富多彩”的Per-CPU变量的API,你是不是已经醉了。这些定义使用在不同的场合,主要的factor包括:

-该变量在section中的位置

-该变量的对齐方式

-该变量对SMP和UP的处理不同

-访问per cpu的形态

例如:如果你准备定义的per cpu变量是要求按照page对齐的,那么在定义该per cpu变量的时候需要使用DECLARE_PER_CPU_PAGE_ALIGNED。如果只要求在SMP的情况下对齐到cache line,那么使用DECLARE_PER_CPU_SHARED_ALIGNED来定义该per cpu变量。

2、访问静态声明和定义Per-CPU变量的API

静态定义的per cpu变量不能象普通变量那样进行访问,需要使用特定的接口函数,具体如下:

get_cpu_var(var)

put_cpu_var(var)

上面这两个接口函数已经内嵌了锁的机制(preempt disable),用户可以直接调用该接口进行本CPU上该变量副本的访问。如果用户确认当前的执行环境已经是preempt disable(或者是更厉害的锁,例如关闭了CPU中断),那么可以使用lock-free版本的Per-CPU变量的API:__get_cpu_var。

 

3、动态分配Per-CPU变量的API如下表所示:

动态分配和释放Per-CPU变量的API描述alloc_percpu(type)分配类型是type的per cpu变量,返回per cpu变量的地址(注意:不是各个CPU上的副本)void free_percpu(void __percpu *ptr)释放ptr指向的per cpu变量空间

 

4、访问动态分配Per-CPU变量的API如下表所示:

访问Per-CPU变量的API描述get_cpu_ptr这个接口是和访问静态Per-CPU变量的get_cpu_var接口是类似的,当然,这个接口是for 动态分配Per-CPU变量put_cpu_ptr同上per_cpu_ptr(ptr, cpu)根据per cpu变量的地址和cpu number,返回指定CPU number上该per cpu变量的地址

 

三、实现

1、静态Per-CPU变量定义

我们以DEFINE_PER_CPU的实现为例子,描述linux kernel中如何实现静态Per-CPU变量定义。具体代码如下:

#define DEFINE_PER_CPU(type, name)                    \
    DEFINE_PER_CPU_SECTION(type, name, "")

type就是变量的类型,name是per cpu变量符号。DEFINE_PER_CPU_SECTION宏可以把一个per cpu变量放到指定的section中,具体代码如下:

#define DEFINE_PER_CPU_SECTION(type, name, sec)                \
    __PCPU_ATTRS(sec) PER_CPU_DEF_ATTRIBUTES            \-----安排section
    __typeof__(type) name----------------------定义变量

在这里具体arch specific的percpu代码中(arch/arm/include/asm/percpu.h)可以定义PER_CPU_DEF_ATTRIBUTES,以便控制该per cpu变量的属性,当然,如果arch specific的percpu代码不定义,那么在general arch-independent的代码中(include/asm-generic/percpu.h)会定义为空。这里可以顺便提一下Per-CPU变量的软件层次:

(1)arch-independent interface。在include/linux/percpu.h文件中,定义了内核其他模块要使用per cpu机制使用的接口API以及相关数据结构的定义。内核其他模块需要使用per cpu变量接口的时候需要include该头文件

(2)arch-general interface。在include/asm-generic/percpu.h文件中。如果所有的arch相关的定义都是一样的,那么就把它抽取出来,放到asm-generic目录下。毫无疑问,这个文件定义的接口和数据结构是硬件相关的,只不过软件抽象各个arch-specific的内容,形成一个arch general layer。一般来说,我们不需要直接include该头文件,include/linux/percpu.h会include该头文件。

(3)arch-specific。这是和硬件相关的接口,在arch/arm/include/asm/percpu.h,定义了ARM平台中,具体和per cpu相关的接口代码。

我们回到正题,看看__PCPU_ATTRS的定义:

#define __PCPU_ATTRS(sec)                        \
    __percpu __attribute__((section(PER_CPU_BASE_SECTION sec)))    \
    PER_CPU_ATTRIBUTES

PER_CPU_BASE_SECTION 定义了基础的section name symbol,定义如下:

#ifndef PER_CPU_BASE_SECTION
#ifdef CONFIG_SMP
#define PER_CPU_BASE_SECTION ".data..percpu"
#else
#define PER_CPU_BASE_SECTION ".data"
#endif
#endif

虽然有各种各样的静态Per-CPU变量定义方法,但是都是类似的,只不过是放在不同的section中,属性不同而已,这里就不看其他的实现了,直接给出section的安排:

(1)普通per cpu变量的section安排

 SMPUPBuild-in kernel".data..percpu" section".data" sectiondefined in module".data..percpu" section".data" section

(2)first per cpu变量的section安排

 SMPUPBuild-in kernel".data..percpu..first" section".data" sectiondefined in module".data..percpu..first" section".data" section

(3)SMP shared aligned per cpu变量的section安排

 SMPUPBuild-in kernel".data..percpu..shared_aligned" section".data" sectiondefined in module".data..percpu" section".data" section

(4)aligned per cpu变量的section安排

 SMPUPBuild-in kernel".data..percpu..shared_aligned" section".data..shared_aligned" sectiondefined in module".data..percpu" section".data..shared_aligned" section

(5)page aligned per cpu变量的section安排

 SMPUPBuild-in kernel".data..percpu..page_aligned" section".data..page_aligned" sectiondefined in module".data..percpu..page_aligned" section".data..page_aligned" section

(6)read mostly per cpu变量的section安排

 SMPUPBuild-in kernel".data..percpu..readmostly" section".data..readmostly" sectiondefined in module".data..percpu..readmostly" section".data..readmostly" section

 

了解了静态定义Per-CPU变量的实现,但是为何要引入这么多的section呢?对于kernel中的普通变量,经过了编译和链接后,会被放置到.data或者.bss段,系统在初始化的时候会准备好一切(例如clear bss),由于per cpu变量的特殊性,内核将这些变量放置到了其他的section,位于kernel address space中__per_cpu_start和__per_cpu_end之间,我们称之Per-CPU变量的原始变量(我也想不出什么好词了)。

只有Per-CPU变量的原始变量还是不够的,必须为每一个CPU建立一个副本,怎么建?直接静态定义一个NR_CPUS的数组?NR_CPUS定义了系统支持的最大的processor的个数,并不是实际中系统processor的数目,这样的定义非常浪费内存。此外,静态定义的数据在内存中连续,对于UMA系统而言是OK的,对于NUMA系统,每个CPU上的Per-CPU变量的副本应该位于它访问最快的那段memory上,也就是说Per-CPU变量的各个CPU副本可能是散布在整个内存地址空间的,而这些空间之间是有空洞的。本质上,副本per cpu内存的分配归属于内存管理子系统,因此,分配per cpu变量副本的内存本文不会详述,大致的思路如下:

percpu

内存管理子系统会根据当前的内存配置为每一个CPU分配一大块memory,对于UMA,这个memory也是位于main memory,对于NUMA,有可能是分配最靠近该CPU的memory(也就是说该cpu访问这段内存最快),但无论如何,这些都是内存管理子系统需要考虑的。无论静态还是动态per cpu变量的分配,其机制都是一样的,只不过,对于静态per cpu变量,需要在系统初始化的时候,对应per cpu section,预先动态分配一个同样size的per cpu chunk。在vmlinux.lds.h文件中,定义了percpu section的排列情况:

#define PERCPU_INPUT(cacheline)                        \
    VMLINUX_SYMBOL(__per_cpu_start) = .;                \
    *(.data..percpu..first)                        \
    . = ALIGN(PAGE_SIZE);                        \
    *(.data..percpu..page_aligned)                    \
    . = ALIGN(cacheline);                        \
    *(.data..percpu..readmostly)                    \
    . = ALIGN(cacheline);                        \
    *(.data..percpu)                        \
    *(.data..percpu..shared_aligned)                \
    VMLINUX_SYMBOL(__per_cpu_end) = .;

对于build in内核的那些per cpu变量,必然位于__per_cpu_start和__per_cpu_end之间的per cpu section。在系统初始化的时候(setup_per_cpu_areas),分配per cpu memory chunk,并将per cpu section copy到每一个chunk中。

 

2、访问静态定义的per cpu变量

代码如下:

#define get_cpu_var(var) (*({                \
    preempt_disable();                \
    &__get_cpu_var(var); }))

再看到get_cpu_var和__get_cpu_var这两个符号,相信广大人民群众已经相当的熟悉,一个持有锁的版本,一个lock-free的版本。为防止当前task由于抢占而调度到其他的CPU上,在访问per cpu memory的时候都需要使用preempt_disable这样的锁的机制。我们来看__get_cpu_var:

#define __get_cpu_var(var) (*this_cpu_ptr(&(var)))

#define this_cpu_ptr(ptr) __this_cpu_ptr(ptr)

对于ARM平台,我们没有定义__this_cpu_ptr,因此采用asm-general版本的:

#define __this_cpu_ptr(ptr) SHIFT_PERCPU_PTR(ptr, __my_cpu_offset)

SHIFT_PERCPU_PTR这个宏定义从字面上就可以看出它是可以从原始的per cpu变量的地址,通过简单的变换(SHIFT)转成实际的per cpu变量副本的地址。实际上,per cpu内存管理模块可以保证原始的per cpu变量的地址和各个CPU上的per cpu变量副本的地址有简单的线性关系(就是一个固定的offset)。__my_cpu_offset这个宏定义就是和offset相关的,如果arch specific没有定义,那么可以采用asm general版本的,如下:

#define __my_cpu_offset per_cpu_offset(raw_smp_processor_id())

raw_smp_processor_id可以获取本CPU的ID,如果没有arch specific没有定义__per_cpu_offset这个宏,那么offset保存在__per_cpu_offset的数组中(下面只是数组声明,具体定义在mm/percpu.c文件中),如下:

#ifndef __per_cpu_offset
extern unsigned long __per_cpu_offset[NR_CPUS];

#define per_cpu_offset(x) (__per_cpu_offset[x])
#endif

对于ARMV6K和ARMv7版本,offset保存在TPIDRPRW寄存器中,这样是为了提升系统性能。

 

3、动态分配per cpu变量

这部分内容留给内存管理子系统吧。


原创文章,转发请注明出处。蜗窝科技

http://www.wowotech.net/linux_kenrel/per-cpu.html

'

linux percpu机制解析2014-05-14 10:54:53

分类: LINUX


点击(此处)折叠或打开

  1. //basedon Linux V3.14 source code
  2. 一、概述
  3. 每cpu变量是最简单也是最重要的同步技术。每cpu变量主要是数据结构数组,系统的每个cpu对应数组的一个元素。一个cpu不应该访问与其它cpu对应的数组元素,另外,它可以随意读或修改它自己的元素而不用担心出现竞争条件,因为它是唯一有资格这么做的cpu。这也意味着每cpu变量基本上只能在特殊情况下使用,也就是当它确定在系统的cpu上的数据在逻辑上是独立的时候。

  4. 每个处理器访问自己的副本,无需加锁,可以放入自己的cache中,极大地提高了访问与更新效率。常用于计数器。

  5. 二、相关结构体:
  6. 1.整体的percpu内存管理信息被收集在struct pcpu_alloc_info结构中
  7. struct pcpu_alloc_info {
  8.     size_t static_size;    //静态定义的percpu变量占用内存区域长度
  9.     size_t reserved_size;    //预留区域,在percpu内存分配指定为预留区域分配时,将使用该区域
  10.     size_t dyn_size;        //动态分配的percpu变量占用内存区域长度
  11.     //每个cpu的percpu空间所占得内存空间为一个unit, 每个unit的大小记为unit_size
  12.     size_t unit_size;        //每颗处理器的percpu虚拟内存递进基本单位
  13.     size_t atom_size;        //PAGE_SIZE
  14.     size_t alloc_size;        //要分配的percpu内存空间
  15.     size_t __ai_size;        //整个pcpu_alloc_info结构体的大小
  16.     int nr_groups;        //该架构下的处理器分组数目
  17.     struct pcpu_group_info groups[];        //该架构下的处理器分组信息
  18. };

  19. 2.对于处理器的分组信息,内核使用struct pcpu_group_info结构表示
  20. struct pcpu_group_info {
  21.     int nr_units;//该组的处理器数目
  22.     //组的percpu内存地址起始地址,即组内处理器数目×处理器percpu虚拟内存递进基本单位
  23.     unsigned long base_offset;
  24.     unsigned int *cpu_map; //组内cpu对应数组,保存cpu id号
  25. };

  26. 3.内核使用pcpu_chunk结构管理percpu内存
  27. struct pcpu_chunk {
  28.     //用来把chunk链接起来形成链表。每一个链表又都放到pcpu_slot数组中,根据chunk中空闲空间的大小决定放到数组的哪个元素中。
  29.     struct list_head list;
  30.     int free_size;//chunk中的空闲大小
  31.     int contig_hint;//该chunk中最大的可用空间的map项的size
  32.     void *base_addr;//percpu内存开始基地值
  33.     int map_used;//该chunk中使用了多少个map项
  34.     int map_alloc;//记录map数组的项数,为PERCPU_DYNAMIC_EARLY_SLOTS=128
  35.     //若map项>0,表示该map中记录的size是可以用来分配percpu空间的。
  36.     //若map项<0,表示该map项中的size已经被分配使用。
  37.     int *map; //map数组,记录该chunk的空间使用情况
  38.     void *data;//chunk data
  39.     bool immutable; /* no [de]population allowed */
  40.     unsigned long populated[];/* populated bitmap*/
  41. };

  42. 三、per-cpu初始化
  43. 在系统初始化期间,start_kernel()函数中调用setup_per_cpu_areas()函数,用于为每个cpu的per-cpu变量副本分配空间,注意这时alloc内存分配器还没建立起来,该函数调用alloc_bootmem函数为初始化期间的这些变量副本分配物理空间。

  44. 在建立percpu内存管理机制之前要整理出该架构下的处理器信息,包括处理器如何分组、每组对应的处理器位图、静态定义的percpu变量占用内存区域、每颗处理器percpu虚拟内存递进基本单位等信息。

  45. 1.setup_per_cpu_areas()函数,用于为每个cpu的per-cpu变量副本分配空间
  46. void __init setup_per_cpu_areas(void)
  47. {
  48.     unsigned long delta;
  49.     unsigned int cpu;
  50.     int rc;
  51.     
  52.     //为percpu建立第一个chunk
  53.     rc = pcpu_embed_first_chunk(PERCPU_MODULE_RESERVE,
  54.                                 PERCPU_DYNAMIC_RESERVE, PAGE_SIZE,NULL,
  55.                                 pcpu_dfl_fc_alloc, pcpu_dfl_fc_free);
  56.     if (rc < 0)
  57.         panic("Failed to initialize percpu areas.");
  58.     
  59.     //内核为percpu分配了一大段空间,在整个percpu空间中根据cpu个数将percpu的空间分为不同的unit。
  60.     //而pcpu_base_addr表示整个系统中percpu的起始内存地址.
  61.     //__per_cpu_start表示静态分配的percpu起始地址。即节区".data..percpu"中起始地址。
  62.     //函数首先算出副本空间首地址(pcpu_base_addr)".data..percpu"section首地址(__per_cpu_start)之间的偏移量delta
  63.     delta = (unsigned long)pcpu_base_addr - (unsigned long)__per_cpu_start;

  64.     //遍历系统中的cpu,设置每个cpu的__per_cpu_offset指针
  65.     //pcpu_unit_offsets[cpu]保存对应cpu所在副本空间相对于pcpu_base_addr的偏移量
  66.     //加上delta,这样就可以得到每个cpu的per-cpu变量副本的偏移量, 放在__per_cpu_offset数组中.
  67.     for_each_possible_cpu(cpu)
  68.         __per_cpu_offset[cpu]= delta + pcpu_unit_offsets[cpu];
  69. }

  70. 1.1 为percpu建立第一个chunk
  71. int __init pcpu_embed_first_chunk(size_t reserved_size, size_t dyn_size,
  72.                                     size_t atom_size,
  73.                                     pcpu_fc_cpu_distance_fn_t cpu_distance_fn,
  74.                                     pcpu_fc_alloc_fn_t alloc_fn,
  75.                                     pcpu_fc_free_fn_t free_fn)
  76. {
  77.     void *base = (void *)ULONG_MAX;
  78.     void **areas= NULL;
  79.     struct pcpu_alloc_info *ai;
  80.     size_t size_sum, areas_size, max_distance;
  81.     int group, i, rc;
  82.     
  83.     //收集整理该架构下的percpu信息,结果放在struct pcpu_alloc_info结构中
  84.     ai = pcpu_build_alloc_info(reserved_size, dyn_size, atom_size,cpu_distance_fn);
  85.     if (IS_ERR(ai))
  86.         return PTR_ERR(ai);
  87.     
  88.     //计算每个cpu占用的percpu内存空间大小,包括静态定义变量占用空间+reserved空间+动态分配空间
  89.     size_sum = ai->static_size+ ai->reserved_size+ ai->dyn_size;
  90.     
  91.     //areas用来保存每个group的percpu内存起始地址,为其分配空间,做临时存储使用,用完释放掉
  92.     areas_size = PFN_ALIGN(ai->nr_groups* sizeof(void*));    
  93.     areas = memblock_virt_alloc_nopanic(areas_size, 0);
  94.     if (!areas){
  95.         rc = -ENOMEM;
  96.         goto out_free;
  97.     }
  98.     
  99.     //针对该系统下的每个group操作,为每个group分配percpu内存区域,前边只是计算出percpu信息,并没有分配percpu的内存空间。
  100.     for (group = 0; group< ai->nr_groups; group++){
  101.         struct pcpu_group_info *gi = &ai->groups[group];//取出该group下的组信息
  102.         unsigned int cpu = NR_CPUS;
  103.         void *ptr;
  104.     
  105.         //检查cpu_map数组
  106.         for (i = 0; i< gi->nr_units&& cpu== NR_CPUS; i++)
  107.             cpu = gi->cpu_map[i];
  108.         BUG_ON(cpu == NR_CPUS);
  109.  
  110.         //为该group分配percpu内存区域。长度为该group里的cpu数目X每颗处理器的percpu递进单位。
  111.         //函数pcpu_dfl_fc_alloc是从bootmem里取得内存,得到的是物理内存,返回物理地址的内存虚拟地址ptr
  112.         ptr = alloc_fn(cpu, gi->nr_units* ai->unit_size, atom_size);
  113.         if (!ptr){
  114.             rc = -ENOMEM;
  115.             goto out_free_areas;
  116.         }
  117.         /* kmemleak tracks the percpu allocations separately*/
  118.         kmemleak_free(ptr);
  119.         //将分配到的改组percpu内存虚拟起始地址保存在areas数组中
  120.         areas[group]= ptr;
  121.          
  122.         //比较每个group的percpu内存地址,保存最小的内存地址,即percpu内存的起始地址
  123.         //为后边计算group的percpu内存地址的偏移量
  124.         base = min(ptr, base);
  125.     }
  126.     
  127.     //为每个group中的每个cpu建立其percpu区域
  128.     for (group = 0; group< ai->nr_groups; group++){
  129.         //取出该group下的组信息
  130.         struct pcpu_group_info *gi = &ai->groups[group];
  131.         void *ptr = areas[group];//得到该group的percpu内存起始地址
  132.          
  133.         //遍历该组中的cpu,并得到每个cpu对应的percpu内存地址
  134.         for (i = 0; i< gi->nr_units; i++, ptr += ai->unit_size){
  135.             if (gi->cpu_map[i]== NR_CPUS){
  136.                 free_fn(ptr, ai->unit_size);//释放掉未使用的unit
  137.                 continue;
  138.             }

  139.             //将静态定义的percpu变量拷贝到每个cpu的percpu内存起始地址
  140.             memcpy(ptr, __per_cpu_load, ai->static_size);
  141.             //为每个cpu释放掉多余的空间,多余的空间是指ai->unit_size减去静态定义变量占用空间+reserved空间+动态分配空间
  142.             free_fn(ptr + size_sum, ai->unit_size- size_sum);
  143.         }
  144.     }
  145.  
  146.     //计算group的percpu内存地址的偏移量
  147.     max_distance = 0;
  148.     for (group = 0; group< ai->nr_groups; group++){
  149.         ai->groups[group].base_offset= areas[group]- base;
  150.         max_distance = max_t(size_t, max_distance,ai->groups[group].base_offset);
  151.     }

  152.     //检查最大偏移量是否超过vmalloc空间的75%
  153.     max_distance += ai->unit_size;    
  154.     if (max_distance > VMALLOC_TOTAL * 3 / 4){
  155.         pr_warning("PERCPU: max_distance=0x%zx too large for vmalloc "
  156.                     "space 0x%lx\n", max_distance,VMALLOC_TOTAL);
  157.     }
  158.     
  159.     pr_info("PERCPU: Embedded %zu pages/cpu @%p s%zu r%zu d%zu u%zu\n",
  160.             PFN_DOWN(size_sum), base, ai->static_size, ai->reserved_size,
  161.             ai->dyn_size, ai->unit_size);

  162.     //为percpu建立第一个chunk
  163.     rc = pcpu_setup_first_chunk(ai, base);
  164.     goto out_free;
  165.     
  166. out_free_areas:
  167.     for (group = 0; group< ai->nr_groups; group++)
  168.         if (areas[group])
  169.             free_fn(areas[group],ai->groups[group].nr_units* ai->unit_size);
  170. out_free:
  171.     pcpu_free_alloc_info(ai);
  172.     if (areas)
  173.         memblock_free_early(__pa(areas), areas_size);
  174.     return rc;
  175. }

  176. 1.1.1 收集整理该架构下的percpu信息
  177. static struct pcpu_alloc_info * __init pcpu_build_alloc_info(size_t reserved_size, size_t dyn_size,
  178.                                     size_t atom_size,pcpu_fc_cpu_distance_fn_t cpu_distance_fn)
  179. {
  180.     static int group_map[NR_CPUS] __initdata;
  181.     static int group_cnt[NR_CPUS] __initdata;
  182.     const size_t static_size = __per_cpu_end - __per_cpu_start;
  183.     int nr_groups = 1, nr_units = 0;
  184.     size_t size_sum, min_unit_size, alloc_size;
  185.     int upa, max_upa, uninitialized_var(best_upa);/* units_per_alloc*/
  186.     int last_allocs, group, unit;
  187.     unsigned int cpu, tcpu;
  188.     struct pcpu_alloc_info *ai;
  189.     unsigned int *cpu_map;
  190.     
  191.     /* thisfunction may be called multiple times */
  192.     memset(group_map, 0, sizeof(group_map));
  193.     memset(group_cnt, 0, sizeof(group_cnt));
  194.     
  195.     //计算每个cpu所占有的percpu空间大小,包括静态空间+保留空间+动态空间
  196.     size_sum = PFN_ALIGN(static_size+ reserved_size +
  197.                         max_t(size_t, dyn_size, PERCPU_DYNAMIC_EARLY_SIZE));
  198.     //重新计算动态分配的percpu空间大小
  199.     dyn_size = size_sum - static_size - reserved_size;
  200.  
  201.     //计算每个unit的大小,即每个group中的每个cpu占用的percpu内存大小为一个unit
  202.     min_unit_size = max_t(size_t, size_sum, PCPU_MIN_UNIT_SIZE);
  203.     //atom_size为PAGE_SIZE,即4K.将min_unit_size按4K向上舍入,例如min_unit_size=5k,则alloc_size为两个页面大小即8K,若min_unit_size=9k,则alloc_size为三个页面大小即12K
  204.     alloc_size = roundup(min_unit_size, atom_size);
  205.     upa = alloc_size / min_unit_size;
  206.     while (alloc_size % upa || ((alloc_size / upa) & ~PAGE_MASK))
  207.         upa--;
  208.     max_upa = upa;
  209.     
  210.     //为cpu分组,将接近的cpu分到一组中,因为没有定义cpu_distance_fn函数体,所以所有的cpu分到一个组中。
  211.     //可以得到所有的cpu都是group=0,group_cnt[0]即是该组中的cpu个数
  212.     for_each_possible_cpu(cpu){
  213.         group = 0;
  214. next_group:
  215.         for_each_possible_cpu(tcpu){
  216.             if (cpu == tcpu)
  217.                 break;
  218.             //cpu_distance_fn=NULL
  219.             if (group_map[tcpu]== group&& cpu_distance_fn&&
  220.                 (cpu_distance_fn(cpu, tcpu)> LOCAL_DISTANCE ||
  221.                 cpu_distance_fn(tcpu, cpu)> LOCAL_DISTANCE)){
  222.                     group++;
  223.                     nr_groups = max(nr_groups, group+ 1);
  224.                     goto next_group;
  225.                 }
  226.         }
  227.         group_map[cpu]= group;
  228.         group_cnt[group]++;
  229.     }
  230.      
  231.     /*
  232.     * Expand unit size until address space usage goes over 75%
  233.     * and then as much as possible without using more address
  234.     * space.
  235.     */
  236.     last_allocs = INT_MAX;
  237.     for (upa = max_upa; upa; upa--){
  238.         int allocs = 0, wasted = 0;
  239.     
  240.         if (alloc_size % upa || ((alloc_size / upa) & ~PAGE_MASK))
  241.             continue;
  242.     
  243.         for (group = 0; group< nr_groups; group++){
  244.             int this_allocs = DIV_ROUND_UP(group_cnt[group], upa);
  245.             allocs += this_allocs;
  246.             wasted += this_allocs* upa - group_cnt[group];
  247.         }
  248.     
  249.         /*
  250.             * Don't acceptif wastage is over 1/3. The
  251.             * greater-than comparison ensures upa==1 always
  252.             * passes the following check.
  253.         */
  254.         if (wasted > num_possible_cpus()/ 3)
  255.             continue;
  256.     
  257.         /*and then don't consume more memory*/
  258.         if (allocs > last_allocs)
  259.             break;
  260.         last_allocs = allocs;
  261.         best_upa = upa;
  262.     }
  263.     upa = best_upa;
  264.     
  265.     //计算每个group中的cpu个数
  266.     for (group = 0; group< nr_groups; group++)
  267.         nr_units += roundup(group_cnt[group], upa);
  268.     
  269.     //分配pcpu_alloc_info结构空间,并初始化
  270.     ai = pcpu_alloc_alloc_info(nr_groups, nr_units);
  271.     if (!ai)
  272.         return ERR_PTR(-ENOMEM);
  273.     
  274.     //为每个group的cpu_map指针赋值为group[0],group[0]中的cpu_map中的值初始化为NR_CPUS
  275.     cpu_map = ai->groups[0].cpu_map;
  276.     for (group = 0; group< nr_groups; group++){
  277.         ai->groups[group].cpu_map= cpu_map;
  278.         cpu_map += roundup(group_cnt[group], upa);
  279.     }
  280.     
  281.     ai->static_size= static_size;//静态percpu变量空间
  282.     ai->reserved_size= reserved_size;//保留percpu变量空间
  283.     ai->dyn_size= dyn_size;//动态分配的percpu变量空间
  284.     ai->unit_size= alloc_size / upa; //每个cpu占用的percpu变量空间
  285.     ai->atom_size= atom_size;//PAGE_SIZE
  286.     ai->alloc_size= alloc_size;//实际分配的空间
  287.     
  288.     for (group = 0, unit= 0; group_cnt[group]; group++){
  289.         struct pcpu_group_info *gi = &ai->groups[group];
  290.         //设置组内的相对于0地址偏移量,后边会设置真正的对于percpu起始地址的偏移量
  291.         gi->base_offset= unit * ai->unit_size;
  292.          //设置cpu_map数组,数组保存该组中的cpu id号。以及设置组中的cpu个数gi->nr_units
  293.         //gi->nr_units=0,cpu=0
  294.         //gi->nr_units=1,cpu=1
  295.         //gi->nr_units=2,cpu=2
  296.         //gi->nr_units=3,cpu=3
  297.         for_each_possible_cpu(cpu)
  298.             if (group_map[cpu]== group)
  299.                 gi->cpu_map[gi->nr_units++]= cpu;
  300.         gi->nr_units= roundup(gi->nr_units, upa);
  301.         unit += gi->nr_units;
  302.     }
  303.     BUG_ON(unit != nr_units);
  304.         
  305.     return ai;
  306. }

  307. 1.1.1.1 分配pcpu_alloc_info结构,并初始化
  308. struct pcpu_alloc_info * __init pcpu_alloc_alloc_info(int nr_groups,int nr_units)
  309. {
  310.     struct pcpu_alloc_info *ai;
  311.     size_t base_size, ai_size;
  312.     void *ptr;
  313.     int unit;
  314.     
  315.     //根据group数以及,group[0]中cpu个数确定pcpu_alloc_info结构体大小ai_size
  316.     base_size = ALIGN(sizeof(*ai)+ nr_groups * sizeof(ai->groups[0]),
  317.                 __alignof__(ai->groups[0].cpu_map[0]));

  318.     ai_size = base_size + nr_units * sizeof(ai->groups[0].cpu_map[0]);
  319.     
  320.     //分配空间
  321.     ptr = memblock_virt_alloc_nopanic(PFN_ALIGN(ai_size), 0);
  322.     if (!ptr)
  323.         return NULL;
  324.     ai = ptr;
  325.     ptr += base_size;//指针指向group的cpu_map数组地址处
  326.     
  327.     ai->groups[0].cpu_map= ptr;
  328.     
  329.     //初始化group[0]的cpu_map数组值为NR_CPUS
  330.     for (unit = 0; unit< nr_units; unit++)
  331.         ai->groups[0].cpu_map[unit]= NR_CPUS;
  332.     
  333.     ai->nr_groups= nr_groups;//group个数
  334.     ai->__ai_size= PFN_ALIGN(ai_size);//整个pcpu_alloc_info结构体的大小
  335.     
  336.     return ai;
  337. }

  338. 1.1.2 为percpu建立第一个chunk
  339. int __init pcpu_setup_first_chunk(const struct pcpu_alloc_info*ai,void*base_addr)
  340. {
  341.     static char cpus_buf[4096] __initdata;
  342.     static int smap[PERCPU_DYNAMIC_EARLY_SLOTS] __initdata;
  343.     static int dmap[PERCPU_DYNAMIC_EARLY_SLOTS] __initdata;
  344.     size_t dyn_size = ai->dyn_size;
  345.     size_t size_sum = ai->static_size+ ai->reserved_size+ dyn_size;
  346.     struct pcpu_chunk *schunk,*dchunk = NULL;
  347.     unsigned long *group_offsets;
  348.     size_t *group_sizes;
  349.     unsigned long *unit_off;
  350.     unsigned int cpu;
  351.     int *unit_map;
  352.     int group, unit, i;
  353.     
  354.     cpumask_scnprintf(cpus_buf, sizeof(cpus_buf), cpu_possible_mask);
  355.     
  356. #define PCPU_SETUP_BUG_ON(cond)do {\
  357.         if (unlikely(cond)){ \
  358.             pr_emerg("PERCPU: failed to initialize, %s", #cond);\
  359.             pr_emerg("PERCPU: cpu_possible_mask=%s\n", cpus_buf);\
  360.             pcpu_dump_alloc_info(KERN_EMERG, ai);\
  361.             BUG();\
  362.         } \
  363.     } while (0)
  364.     
  365.     //健康检查
  366.     PCPU_SETUP_BUG_ON(ai->nr_groups<= 0);
  367. #ifdef CONFIG_SMP
  368.     PCPU_SETUP_BUG_ON(!ai->static_size);
  369.     PCPU_SETUP_BUG_ON((unsigned long)__per_cpu_start& ~PAGE_MASK);
  370. #endif
  371.     PCPU_SETUP_BUG_ON(!base_addr);
  372.     PCPU_SETUP_BUG_ON((unsigned long)base_addr& ~PAGE_MASK);
  373.     PCPU_SETUP_BUG_ON(ai->unit_size< size_sum);
  374.     PCPU_SETUP_BUG_ON(ai->unit_size& ~PAGE_MASK);
  375.     PCPU_SETUP_BUG_ON(ai->unit_size< PCPU_MIN_UNIT_SIZE);
  376.     PCPU_SETUP_BUG_ON(ai->dyn_size< PERCPU_DYNAMIC_EARLY_SIZE);
  377.     PCPU_SETUP_BUG_ON(pcpu_verify_alloc_info(ai)< 0);
  378.     
  379.     //为group相关percpu信息保存数组分配空间
  380.     group_offsets = memblock_virt_alloc(ai->nr_groups*sizeof(group_offsets[0]), 0);
  381.     group_sizes = memblock_virt_alloc(ai->nr_groups*sizeof(group_sizes[0]), 0);
  382.     //为每个cpu相关percpu信息保存数组分配空间
  383.     unit_map = memblock_virt_alloc(nr_cpu_ids* sizeof(unit_map[0]), 0);
  384.     unit_off = memblock_virt_alloc(nr_cpu_ids* sizeof(unit_off[0]), 0);
  385.     
  386.     //对unit_map、pcpu_low_unit_cpu和pcpu_high_unit_cpu变量初始化
  387.     for (cpu = 0; cpu< nr_cpu_ids; cpu++)
  388.         unit_map[cpu]= UINT_MAX;    
  389.     pcpu_low_unit_cpu = NR_CPUS;
  390.     pcpu_high_unit_cpu = NR_CPUS;
  391.     
  392.     //遍历每一group的每一个cpu
  393.     for (group = 0, unit= 0; group< ai->nr_groups; group++, unit += i){
  394.         const struct pcpu_group_info *gi = &ai->groups[group];
  395.         //取得该组处理器的percpu内存空间的偏移量
  396.         group_offsets[group]= gi->base_offset;
  397.         //取得该组处理器的percpu内存空间占用的虚拟地址空间大小,即包含改组中每个cpu所占的percpu空间
  398.         group_sizes[group]= gi->nr_units* ai->unit_size;
  399.         //遍历该group中的cpu
  400.         for (i = 0; i< gi->nr_units; i++){
  401.             cpu = gi->cpu_map[i];//得到该group中的cpu id号
  402.             if (cpu == NR_CPUS)
  403.                 continue;
  404.     
  405.             PCPU_SETUP_BUG_ON(cpu > nr_cpu_ids);
  406.             PCPU_SETUP_BUG_ON(!cpu_possible(cpu));
  407.             PCPU_SETUP_BUG_ON(unit_map[cpu]!= UINT_MAX);
  408.              
  409.             //计算每个cpu的跨group的编号,保存在unit_map数组中
  410.             unit_map[cpu]= unit + i;
  411.             //计算每个cpu的在整个系统percpu内存空间中的偏移量,保存到数组unit_off中
  412.             unit_off[cpu]= gi->base_offset+ i * ai->unit_size;
  413.     
  414.             /* determine low/high unit_cpu*/
  415.             if (pcpu_low_unit_cpu == NR_CPUS|| unit_off[cpu]< unit_off[pcpu_low_unit_cpu])
  416.                 pcpu_low_unit_cpu = cpu;
  417.             if (pcpu_high_unit_cpu == NR_CPUS|| unit_off[cpu]> unit_off[pcpu_high_unit_cpu])
  418.                 pcpu_high_unit_cpu = cpu;
  419.         }
  420.     }
  421.     //pcpu_nr_units变量保存系统中有多少个cpu的percpu内存空间
  422.     pcpu_nr_units = unit;
  423.     
  424.     for_each_possible_cpu(cpu)
  425.         PCPU_SETUP_BUG_ON(unit_map[cpu]== UINT_MAX);
  426.     
  427. #undef PCPU_SETUP_BUG_ON
  428.     pcpu_dump_alloc_info(KERN_DEBUG, ai);

  429.     //记录下全局参数,留在pcpu_alloc时使用
  430.     pcpu_nr_groups = ai->nr_groups;//系统中group数量
  431.     pcpu_group_offsets = group_offsets;//记录每个group的percpu内存偏移量数组
  432.     pcpu_group_sizes = group_sizes;//记录每个group的percpu内存空间大小数组
  433.     pcpu_unit_map = unit_map;//整个系统中cpu(跨group)的编号数组
  434.     pcpu_unit_offsets = unit_off;//每个cpu的percpu内存空间偏移量
  435.     pcpu_unit_pages = ai->unit_size>> PAGE_SHIFT;//每个cpu的percpu内存虚拟空间所占的页面数量
  436.     pcpu_unit_size = pcpu_unit_pages << PAGE_SHIFT;//每个cpu的percpu内存虚拟空间大小
  437.     pcpu_atom_size = ai->atom_size;//PAGE_SIZE

  438.     //计算pcpu_chunk结构的大小,加上populated域的大小
  439.     pcpu_chunk_struct_size = sizeof(struct pcpu_chunk)+
  440.                             BITS_TO_LONGS(pcpu_unit_pages)* sizeof(unsigned long);
  441.     
  442.     //计算pcpu_nr_slots,即pcpu_slot数组的组项数量
  443.     pcpu_nr_slots = __pcpu_size_to_slot(pcpu_unit_size)+ 2;
  444.     //为pcpu_slot数组分配空间,不同size的chunck挂在不同“pcpu_slot”项目中
  445.     pcpu_slot = memblock_virt_alloc(pcpu_nr_slots* sizeof(pcpu_slot[0]), 0);
  446.     for (i = 0; i< pcpu_nr_slots; i++)
  447.         INIT_LIST_HEAD(&pcpu_slot[i]);
  448.     
  449.     //构建静态chunck,即pcpu_reserved_chunk
  450.     schunk = memblock_virt_alloc(pcpu_chunk_struct_size, 0);
  451.     INIT_LIST_HEAD(&schunk->list);
  452.     schunk->base_addr= base_addr;//整个系统中percpu内存的起始地址
  453.     schunk->map= smap;//初始化为一个静态数组
  454.     schunk->map_alloc= ARRAY_SIZE(smap);//PERCPU_DYNAMIC_EARLY_SLOTS=128
  455.     schunk->immutable= true;
  456.     //物理内存已经分配这里标志之
  457.     //若pcpu_unit_pages=8即每个cpu占用的percpu空间为8页的空间,则populated域被设置为0xff
  458.     bitmap_fill(schunk->populated, pcpu_unit_pages);
  459.     
  460.     if (ai->reserved_size){
  461.         //如果存在percpu保留空间,在指定reserved分配时作为空闲空间使用
  462.         schunk->free_size= ai->reserved_size;            
  463.         pcpu_reserved_chunk = schunk;
  464.         //静态chunk的大小限制包括,定义的静态变量的空间+保留的空间
  465.         pcpu_reserved_chunk_limit = ai->static_size+ ai->reserved_size;
  466.     } else {
  467.         //若不存在保留空间,则将动态分配空间作为空闲空间使用
  468.         schunk->free_size= dyn_size;
  469.         dyn_size = 0;//覆盖掉动态分配空间
  470.     }

  471.     //记录静态chunk中空闲可使用的percpu空间大小
  472.     schunk->contig_hint= schunk->free_size;
  473.     //map数组保存空间的使用情况,负数为已使用的空间,正数表示为以后可以分配的空间
  474.     //map_used记录chunk中存在几个map项
  475.     schunk->map[schunk->map_used++]= -ai->static_size;
  476.     if (schunk->free_size)
  477.         schunk->map[schunk->map_used++]= schunk->free_size;
  478.     
  479.     //构建动态chunk分配空间
  480.     if (dyn_size) {
  481.         dchunk = memblock_virt_alloc(pcpu_chunk_struct_size, 0);
  482.         INIT_LIST_HEAD(&dchunk->list);
  483.         dchunk->base_addr= base_addr;//整个系统中percpu内存的起始地址
  484.         dchunk->map= dmap;//初始化为一个静态数组
  485.         dchunk->map_alloc= ARRAY_SIZE(dmap);//PERCPU_DYNAMIC_EARLY_SLOTS=128
  486.         dchunk->immutable= true;
  487.         //记录下来分配的物理页
  488.         bitmap_fill(dchunk->populated, pcpu_unit_pages);
  489.          //设置动态chunk中的空闲可分配空间大小
  490.         dchunk->contig_hint= dchunk->free_size= dyn_size;
  491.         //map数组保存空间的使用情况,负数为已使用的空间(静态变量空间和reserved空间),正数表示为以后可以分配的空间
  492.         dchunk->map[dchunk->map_used++]= -pcpu_reserved_chunk_limit;
  493.         dchunk->map[dchunk->map_used++]= dchunk->free_size;
  494.     }
  495.     
  496.     //把第一个chunk链接进对应的slot链表,reserverd的空间有自己单独的chunk:pcpu_reserved_chunk
  497.     pcpu_first_chunk = dchunk ?: schunk;
  498.     pcpu_chunk_relocate(pcpu_first_chunk,-1);
  499.  
  500.     //pcpu_base_addr记录整个系统中percpu内存的起始地址
  501.     pcpu_base_addr = base_addr;
  502.     return 0;
  503. }

  504. //fls找到size中最高的置1的位,返回该位号
  505. //例:fls(0)= 0, fls(1)= 1, fls(0x80000000)= 32.
  506. //若size=32768=0x8000,则fls(32768)=16
  507. //若highbit=0-4,则slot个数均为1
  508. #define PCPU_SLOT_BASE_SHIFT    5
  509. static int __pcpu_size_to_slot(int size)
  510. {
  511.     int highbit = fls(size);
  512.     return max(highbit - PCPU_SLOT_BASE_SHIFT + 2, 1);
  513. }

  514. static void pcpu_chunk_relocate(struct pcpu_chunk*chunk,int oslot)
  515. {
  516.     //返回该chunk对应的要挂入的slot数组的下标
  517.     int nslot = pcpu_chunk_slot(chunk);
  518.         
  519.     //静态chunk不需挂入pcpu_slot数组中
  520.     if (chunk != pcpu_reserved_chunk&& oslot!= nslot){
  521.         if (oslot < nslot)
  522.             list_move(&chunk->list,&pcpu_slot[nslot]);
  523.         else
  524.             list_move_tail(&chunk->list,&pcpu_slot[nslot]);
  525.     }
  526. }

  527. static int pcpu_chunk_slot(const struct pcpu_chunk*chunk)
  528. {
  529.     //该chunk中的空闲空间小于sizeof(int),或者最大的空闲空间块小于sizeof(int),返回0
  530.     if (chunk->free_size< sizeof(int)|| chunk->contig_hint< sizeof(int))
  531.         return 0;
  532.  
  533.     return pcpu_size_to_slot(chunk->free_size);
  534. }

  535. static int pcpu_size_to_slot(int size)
  536. {
  537.     //若size等于每个cpu占用的percpu内存空间大小,返回最后一项pcpu_slot数组下标
  538.     if (size == pcpu_unit_size)
  539.         return pcpu_nr_slots - 1;
  540.     
  541.     //否则根据size返回在pcpu_slot数组中的下标
  542.     return __pcpu_size_to_slot(size);
  543. }


  544. 四、每CPU变量提供的函数和宏
  545. 1.编译期间分配percpu,即分配静态percpu,函数原型:
  546. DEFINE_PER_CPU(type, name)

  547. #define DEFINE_PER_CPU(type, name) DEFINE_PER_CPU_SECTION(type, name, "")

  548. #define DEFINE_PER_CPU_SECTION(type, name, sec)\
  549.         __PCPU_ATTRS(sec) PER_CPU_DEF_ATTRIBUTES\
  550.         __typeof__(type) name

  551. #define __PCPU_ATTRS(sec)\
  552.         __percpu __attribute__((section(PER_CPU_BASE_SECTION sec)))\
  553.         PER_CPU_ATTRIBUTES

  554. #define PER_CPU_BASE_SECTION ".data..percpu"
  555. #define PER_CPU_ATTRIBUTES
  556. #define PER_CPU_DEF_ATTRIBUTES

  557. 根据以上宏定义展开之,可以得到
  558. __attribute__((section(.data..percpu))) __typeof__(type) name
  559. 可见宏“DEFINE_PER_CPU(type, name)”的作用就是将类型为“type”的“name”变量放到“.data..percpu”数据段。
  560. 而在/include/asm-generic/vmlinux.lds.h中定义:
  561. 链接器会把所有静态定义的per-cpu变量统一放到".data..percpu" section中, 链接器生成__per_cpu_start和__per_cpu_end两个变量来表示该section的起始和结束地址, 为了配合链接器的行为, linux内核源码中针对以上链接脚本声明了外部变量 extern char __per_cpu_load[], __per_cpu_start[], __per_cpu_end[];
  562. #define PERCPU_INPUT(cacheline)                                        \
  563.     VMLINUX_SYMBOL(__per_cpu_start)= .;\
  564.     *(.data..percpu..first)\
  565.     . = ALIGN(PAGE_SIZE);\
  566.     *(.data..percpu..page_aligned)\
  567.     . = ALIGN(cacheline);\
  568.     *(.data..percpu..readmostly)\
  569.     . = ALIGN(cacheline);\
  570.     *(.data..percpu)\
  571.     *(.data..percpu..shared_aligned)\
  572.     VMLINUX_SYMBOL(__per_cpu_end)= .;

  573. #define PERCPU_VADDR(cacheline, vaddr, phdr)\
  574.         VMLINUX_SYMBOL(__per_cpu_load)= .;\
  575.         .data..percpu vaddr: AT(VMLINUX_SYMBOL(__per_cpu_load)\
  576.                                 - LOAD_OFFSET){ \
  577.                 PERCPU_INPUT(cacheline)\
  578.         } phdr \
  579.         . = VMLINUX_SYMBOL(__per_cpu_load)+ SIZEOF(.data..percpu);

  580. 我们知道在系统对percpu初始化的时候,会将静态定义的percpu变量(内核映射".data.percpu"section中的变量数据)拷贝到每个cpu的percpu内存空间中,静态定义的percpu变量的起始地址为__per_cpu_load,即
  581. memcpy(ptr, __per_cpu_load, ai->static_size);

  582. 2. 访问percpu变量
  583. (1) per_cpu(var, cpu)获取编号cpu的处理器上面的变量var的副本
  584. (2) get_cpu_var(var)获取本处理器上面的变量var的副本,该函数关闭进程抢占,主要由__get_cpu_var来完成具体的访问
  585. (3) get_cpu_ptr(var) 获取本处理器上面的变量var的副本的指针,该函数关闭进程抢占,主要由__get_cpu_var来完成具体的访问
  586. (4) put_cpu_var(var)& put_cpu_ptr(var)表示每CPU变量的访问结束,恢复进程抢占
  587. (5) __get_cpu_var(var) 获取本处理器上面的变量var的副本,该函数不关闭进程抢占

  588. 注意:关闭内核抢占可确保在对per-cpu变量操作的临界区中, 当前进程不会被换出处理器, 在put_cpu_var中恢复内核调度器的可抢占性.

  589. //详细代码解析:
  590. (1) per_cpu
  591. #define per_cpu(var, cpu)(*SHIFT_PERCPU_PTR(&(var), per_cpu_offset(cpu)))

  592. #define per_cpu_offset(x)(__per_cpu_offset[x])

  593. #define SHIFT_PERCPU_PTR(__p, __offset)({\
  594.         __verify_pcpu_ptr((__p));\
  595.         RELOC_HIDE((typeof(*(__p)) __kernel __force *)(__p),(__offset));\
  596.         })

  597. #define RELOC_HIDE(ptr, off)             \
  598.         ({ unsigned long __ptr;                                            \
  599.         __ptr = (unsigned long) (ptr);                                    \
  600.         (typeof(ptr))(__ptr +(off));})

  601. per_cpu(var, cpu)通过以上的宏展开,就是返回*(__per_cpu_offset[cpu]+&(var))的值。__per_cpu_offset数组记录每个cpu的percpu内存空间距离内核静态percpu内存区起始地址(".data..percpu"段的起始地址__per_cpu_start)的偏移量,加上var在内核中的内存地址(因为是静态percpu变量,所以地址肯定在".data..percpu"段中),就得到var在该cpu下的percpu内存区的地址,取地址下的值即可得到该var变量的值。

  602. (2) get_cpu_var/__get_cpu_var
  603. #define get_cpu_var(var)(*({\
  604.         preempt_disable();\ //关闭进程抢占
  605.         &__get_cpu_var(var);}))

  606. #define __get_cpu_var(var)(*this_cpu_ptr(&(var)))
  607. #define this_cpu_ptr(ptr) __this_cpu_ptr(ptr)
  608. #define __this_cpu_ptr(ptr) SHIFT_PERCPU_PTR(ptr, __my_cpu_offset)
  609. #define my_cpu_offset __my_cpu_offset
  610. #define __my_cpu_offset per_cpu_offset(raw_smp_processor_id())
  611. #define per_cpu_offset(x)(__per_cpu_offset[x])
  612. 通过一系列宏调用,最终函数还是通过*(__per_cpu_offset[raw_smp_processor_id()]+&(var))来获得本地处理器上的var变量的值。

  613. (3) get_cpu_ptr
  614. #define get_cpu_ptr(var)({\
  615.         preempt_disable();\
  616.         this_cpu_ptr(var);})
  617. 获取本处理器上面的变量var的副本的指针,该函数关闭进程抢占.

  618. (4)put_cpu_ptr/put_cpu_var,恢复进程抢占
  619. #define put_cpu_var(var)do {\
  620.         (void)&(var);\
  621.         preempt_enable();\
  622.         } while (0)
  623.          
  624. #define put_cpu_ptr(var)do {\
  625.         (void)(var);\
  626.         preempt_enable();\
  627.         } while (0)

  628. 3.动态分配percpu空间:void * alloc_percpu(type)
  629. #define alloc_percpu(type)\
  630.         (typeof(type) __percpu*)__alloc_percpu(sizeof(type), __alignof__(type))

  631. void __percpu *__alloc_percpu(size_t size, size_t align)
  632. {
  633.     return pcpu_alloc(size, align,false);
  634. }

  635. 3.1 动态分配percpu
  636. static void __percpu *pcpu_alloc(size_t size, size_t align, bool reserved)
  637. {
  638.     static int warn_limit = 10;
  639.     struct pcpu_chunk *chunk;
  640.     const char *err;
  641.     int slot, off, new_alloc;
  642.     unsigned long flags;
  643.     void __percpu *ptr;
  644.         
  645.     if (unlikely(!size|| size> PCPU_MIN_UNIT_SIZE || align > PAGE_SIZE)){
  646.         WARN(true,"illegal size (%zu) or align (%zu) for "
  647.             "percpu allocation\n", size, align);
  648.         return NULL;
  649.     }
  650.     
  651.     mutex_lock(&pcpu_alloc_mutex);
  652.     spin_lock_irqsave(&pcpu_lock, flags);
  653.         
  654.     //若指定reserved分配,则从pcpu_reserved_chunk进行
  655.     if (reserved && pcpu_reserved_chunk){
  656.         chunk = pcpu_reserved_chunk;//找到静态percpu的chunk
  657.         
  658.         //检查要分配的空间size是否超出该chunk的所具有的最大的空闲size
  659.         if (size > chunk->contig_hint){
  660.             err = "alloc from reserved chunk failed";
  661.             goto fail_unlock;
  662.         }
  663.     
  664.         //检查是否要扩展chunk的的map数组,map数组默认设置为128项
  665.         while ((new_alloc = pcpu_need_to_extend(chunk))){
  666.             spin_unlock_irqrestore(&pcpu_lock, flags);
  667.             //对map数组进行扩展
  668.             if (pcpu_extend_area_map(chunk, new_alloc)< 0){
  669.                 err = "failed to extend area map of reserved chunk";
  670.                 goto fail_unlock_mutex;
  671.             }
  672.             spin_lock_irqsave(&pcpu_lock, flags);
  673.         }
  674.     
  675.         //从该chunk分配出size大小的空间,返回该size空间在chunk中的偏移量off
  676.         //然后重新将该chunk挂到slot数组对应链表中
  677.         off = pcpu_alloc_area(chunk, size, align);
  678.         if (off >= 0)
  679.             goto area_found;
  680.     
  681.         err = "alloc from reserved chunk failed";
  682.         goto fail_unlock;
  683.     }
  684.         
  685. restart:
  686.     //根据需要分配内存块的大小索引slot数组找到对应链表
  687.     for (slot = pcpu_size_to_slot(size); slot< pcpu_nr_slots; slot++){
  688.         list_for_each_entry(chunk,&pcpu_slot[slot], list){
  689.             if (size > chunk->contig_hint)//在该链表中进一步寻找符合尺寸要求的chunk
  690.                 continue;
  691.              //chunck用数组map记录每次分配的内存块,若该数组项数用完(默认为128项)
  692.             //但是若该chunk仍然还有空闲空间可分配,则需要增长该map数组项数来记录可分配的空间
  693.             new_alloc = pcpu_need_to_extend(chunk);
  694.             if (new_alloc) {
  695.                 spin_unlock_irqrestore(&pcpu_lock, flags);
  696.                 //扩展map数组
  697.                 if (pcpu_extend_area_map(chunk,new_alloc)< 0){
  698.                     err = "failed to extend area map";
  699.                     goto fail_unlock_mutex;
  700.                 }
  701.                 spin_lock_irqsave(&pcpu_lock, flags);

  702.                 goto restart;
  703.             }
  704.              //从该chunk分配出size大小的空间,返回该size空间在chunk中的偏移量off
  705.             //然后重新将该chunk挂到slot数组对应链表中
  706.             off = pcpu_alloc_area(chunk, size, align);
  707.             if (off >= 0)
  708.                 goto area_found;
  709.         }
  710.     }
  711.     
  712.     //到这里表示没有找到合适的chunk,需要重新创建一个新的chunk
  713.     spin_unlock_irqrestore(&pcpu_lock, flags);
  714.     //创建一个新的chunk,这里进行的是虚拟地址空间的分配
  715.     chunk = pcpu_create_chunk();
  716.     if (!chunk){
  717.         err = "failed to allocate new chunk";
  718.         goto fail_unlock_mutex;
  719.     }
  720.     
  721.     spin_lock_irqsave(&pcpu_lock, flags);
  722.     //把一个全新的chunk挂到slot数组对应链表中
  723.     pcpu_chunk_relocate(chunk,-1);
  724.     goto restart;
  725.     
  726. area_found:
  727.     spin_unlock_irqrestore(&pcpu_lock, flags);
  728.     
  729.     //这里要检查该段区域对应物理页是否已经分配
  730.     if (pcpu_populate_chunk(chunk, off, size)){
  731.         spin_lock_irqsave(&pcpu_lock, flags);
  732.         pcpu_free_area(chunk, off);
  733.         err = "failed to populate";
  734.         goto fail_unlock;
  735.     }
  736.     
  737.     mutex_unlock(&pcpu_alloc_mutex);
  738.     
  739.     /*
  740.     #define __addr_to_pcpu_ptr(addr)     \
  741.         (void __percpu *)((unsigned long)(addr)- \
  742.         (unsigned long)pcpu_base_addr+                     \
  743.         (unsigned long)__per_cpu_start)
  744.     */
  745.     //chunk->base_addr+ off表示分配该size空间的起始percpu内存地址
  746.     //最终返回的地址即__per_cpu_start+off,即得到该动态分配percpu变量在内核镜像中的一个虚拟内存地址。
  747.     //实际上该动态分配percpu变量并不在此地址上,只是为了以后通过per_cpu(var, cpu)引用该变量时,
  748.     //与静态percpu变量一致,因为静态percpu变量在内核镜像中是有分配内存虚拟地址的(.data..percpu段中)
  749.     //使用per_cpu(var, cpu)时,该动态分配percpu变量的内核镜像中的虚拟地址(假的地址,为了跟静态percpu变量一致),加上本cpu所在percpu空间与.data..percpu段的偏移量,
  750.     //即得到该动态分配percpu变量在本cpu副本中的内存地址
  751.     ptr = __addr_to_pcpu_ptr(chunk->base_addr+ off);
  752.     kmemleak_alloc_percpu(ptr, size);
  753.     return ptr;
  754.     
  755. fail_unlock:
  756.     spin_unlock_irqrestore(&pcpu_lock, flags);
  757. fail_unlock_mutex:
  758.     mutex_unlock(&pcpu_alloc_mutex);
  759.     if (warn_limit) {
  760.         pr_warning("PERCPU: allocation failed, size=%zu align=%zu, ""%s\n", size, align, err);
  761.         dump_stack();
  762.         if (!--warn_limit)
  763.             pr_info("PERCPU: limit reached, disable warning\n");
  764.     }
  765.     return NULL;
  766. }

  767. 3.1.1 检查chunk的map数组是否需要扩展
  768. //#define PCPU_DFL_MAP_ALLOC    16
  769. static int pcpu_need_to_extend(struct pcpu_chunk*chunk)
  770. {
  771.     int new_alloc;
  772.     
  773.     //map_alloc默认设置为128,只有map_used记录超过126时才会进行map数组扩展
  774.     if (chunk->map_alloc>= chunk->map_used+ 2)
  775.         return 0;
  776.         
  777.     new_alloc = PCPU_DFL_MAP_ALLOC;//16

  778.     //计算该chunk的map数组新的大小,并返回
  779.     while (new_alloc < chunk->map_used+ 2)
  780.         new_alloc *= 2;
  781.         
  782.     return new_alloc;
  783. }

  784. 3.1.2 对map数组的大小进行扩展
  785. static int pcpu_extend_area_map(struct pcpu_chunk*chunk,int new_alloc)
  786. {
  787.     int *old = NULL, *new = NULL;
  788.     size_t old_size = 0, new_size= new_alloc * sizeof(new[0]);
  789.     unsigned long flags;
  790.     
  791.     //为新的map数组大小分配内存空间    
  792.     new = pcpu_mem_zalloc(new_size);
  793.     if (!new)
  794.         return -ENOMEM;
  795.         
  796.     /* acquire pcpu_lockand switch to new area map */
  797.     spin_lock_irqsave(&pcpu_lock, flags);
  798.         
  799.     if (new_alloc <= chunk->map_alloc)
  800.         goto out_unlock;
  801.         
  802.     old_size = chunk->map_alloc* sizeof(chunk->map[0]);
  803.     old = chunk->map;
  804.     
  805.     //复制老的map数组信息到new
  806.     memcpy(new, old, old_size);
  807.     
  808.     //重新设置map数组,完成map数组的扩展
  809.     chunk->map_alloc= new_alloc;
  810.     chunk->map= new;
  811.     new = NULL;
  812.     
  813. out_unlock:
  814.     spin_unlock_irqrestore(&pcpu_lock, flags);
  815.     
  816.     pcpu_mem_free(old, old_size);
  817.     pcpu_mem_free(new, new_size);
  818.         
  819.     return 0;
  820. }

  821. 3.1.3 从chunk的map数组中分配size大小空间,返回该size的偏移值
  822. static int pcpu_alloc_area(struct pcpu_chunk*chunk,int size,int align)
  823. {
  824.     int oslot = pcpu_chunk_slot(chunk);
  825.     int max_contig = 0;
  826.     int i, off;
  827.     
  828.     //遍历该chunk的map中记录的空间,map中负数为已经使用的空间,正数为可以分配使用的空间
  829.     for (i = 0, off= 0; i< chunk->map_used; off+= abs(chunk->map[i++])){
  830.         //is_last为1表示已经扫描了chunk中所有记录的空间,并且是最后一个map组项
  831.         bool is_last = i + 1 == chunk->map_used;
  832.         int head, tail;
  833.         
  834.         //对map项中记录的percpu空间大小进行对齐,可能会产生的一个偏移量head
  835.         head = ALIGN(off, align)- off;
  836.         BUG_ON(i == 0 && head != 0);
  837.          
  838.          //map中记录的负数表示已经使用的percpu空间,继续下一个
  839.         if (chunk->map[i]< 0)
  840.             continue;
  841.             
  842.         //若map中的空间大小小于要分配的空间大小,继续下一个
  843.         if (chunk->map[i]< head + size){
  844.             //更新该chunk中可使用的空间大小
  845.             max_contig = max(chunk->map[i], max_contig);
  846.             continue;
  847.         }
  848.     
  849.         //如果head不为0,并且head很小(小于sizeof(int)),或者前一个map的可用空间大于0(但是chunk->map[i- 1]< head+size)
  850.         //如果前一个map项>0,则将head合并到前一个map中
  851.         //如果前一个map项<0,则将head合并到前一个map,并且是负数,不可用空间,当前chunk空闲size减去这head大小的空间
  852.         if (head &&(head < sizeof(int)|| chunk->map[i- 1]> 0)){
  853.             if (chunk->map[i- 1]> 0)
  854.                 chunk->map[i- 1]+= head;
  855.             else {
  856.                 chunk->map[i- 1]-= head;
  857.                 chunk->free_size-= head;
  858.             }
  859.             //当前map减去已经与前一个map合并的head大小的空间
  860.             chunk->map[i]-= head;
  861.             off += head;//偏移要加上head
  862.             head = 0;//合并之后,head清零
  863.         }
  864.     
  865.         //计算要分配空间的尾部
  866.         tail = chunk->map[i]- head - size;
  867.         if (tail < sizeof(int))
  868.             tail = 0;
  869.     
  870.         //如果head不为0,或者tail不为0,则要将当前map分割
  871.         if (head || tail){
  872.             pcpu_split_block(chunk, i, head, tail);
  873.             //如果head不为0,tail不为0,经过split之后,map[i]记录head,map[i+1]记录要分配的size,map[i+2]记录tail。
  874.             if (head) {
  875.                 i++;//移到记录要分配size空间的map项
  876.                 off += head;//偏移要加上head,表示从head之后开始
  877.                 //i-1表示head所在的那个map项,与max_contig比较大小,为下边更新chunk的最大空闲空间
  878.                 max_contig = max(chunk->map[i- 1], max_contig);
  879.             }
  880.             //i+1表示tail所在的那个map项,比较与max_contig的大小,为下边更新chunk的最大空闲空间
  881.             if (tail)
  882.                 max_contig = max(chunk->map[i+ 1], max_contig);
  883.         }
  884.     
  885.         //更新chunk的最大空闲空间
  886.         if (is_last)
  887.             chunk->contig_hint= max_contig;/* fully scanned*/
  888.         else
  889.             chunk->contig_hint= max(chunk->contig_hint,max_contig);
  890.     
  891.         chunk->free_size-= chunk->map[i];//chunk中的空闲空间大小递减
  892.         chunk->map[i]= -chunk->map[i];//变成负数表示该map中的size大小已分配
  893.         
  894.         //重新计算chunk在slot中的位置
  895.         pcpu_chunk_relocate(chunk, oslot);
  896.         return off;
  897.     }
  898.     
  899.     chunk->contig_hint= max_contig;/* fully scanned*/
  900.     pcpu_chunk_relocate(chunk, oslot);
  901.     
  902.     /* tell the upper layer that this chunk has no matching area*/
  903.     return -1;
  904. }

  905. 3.1.4 将map数组进行分割
  906. static void pcpu_split_block(struct pcpu_chunk*chunk,int i,int head,int tail)
  907. {
  908.     //若head、tail都不为0,则要添加两个map,有一个不为0则添加一个map
  909.     int nr_extra = !!head+ !!tail;
  910.     
  911.     BUG_ON(chunk->map_alloc< chunk->map_used+ nr_extra);
  912.     
  913.     //首先将该当前要分割的map后边的数据拷贝
  914.     memmove(&chunk->map[i+ nr_extra],&chunk->map[i],sizeof(chunk->map[0])* (chunk->map_used- i));
  915.     chunk->map_used+= nr_extra;//map数组的使用个数更新
  916.     
  917.     //如果head不为0,则i+1的map项保存chunk->map[i]- head的大小,当前的map保存head的大小
  918.     if (head) {
  919.         chunk->map[i+ 1]= chunk->map[i]- head;
  920.         chunk->map[i++]= head;
  921.     }

  922.     //如果tail不为0,将记录(chunk->map[i]- head)大小的map项减去tail,即得到要分配size空间
  923.     //最后一个map保存剩余的tail大小
  924.     if (tail) {
  925.         chunk->map[i++]-= tail;//得到size空间大小的map项
  926.         chunk->map[i]= tail;
  927.     }
  928. }

  929. 五、结构图
  930. 参见附件

0 0
原创粉丝点击