about percpu



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 line
DECLARE_PER_CPU_ALIGNED(type, name)
DEFINE_PER_CPU_ALIGNED(type, name)
无论SMP或者UP,都是需要对齐到L1 cache line
DECLARE_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安排

 SMPUP
Build-in kernel".data..percpu" section".data" section
defined in module".data..percpu" section".data" section

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

 SMPUP
Build-in kernel".data..percpu..first" section".data" section
defined in module".data..percpu..first" section".data" section

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

 SMPUP
Build-in kernel".data..percpu..shared_aligned" section".data" section
defined in module".data..percpu" section".data" section

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

 SMPUP
Build-in kernel".data..percpu..shared_aligned" section".data..shared_aligned" section
defined in module".data..percpu" section".data..shared_aligned" section

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

 SMPUP
Build-in kernel".data..percpu..page_aligned" section".data..page_aligned" section
defined in module".data..percpu..page_aligned" section".data..page_aligned" section

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

 SMPUP
Build-in kernel".data..percpu..readmostly" section".data..readmostly" section
defined 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. //based on 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.     /* this function 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 accept if 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_lock and 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. 参见附件

### CPU Cache Write Process Architecture In modern computer systems, especially those with multi-core architectures, managing data writes efficiently between the CPU and memory involves complex interactions within the cache hierarchy. The process of writing data from the CPU into the cache follows specific architectural principles designed to maintain performance while ensuring coherency across multiple levels of caching. #### Writing Data to Cache When a processor core needs to write data, this operation can occur at different stages depending on whether the target address resides in one of its caches: - **Hit Case**: If the required location exists in any level of the local cache (L1, L2), it updates that entry directly. This update may also involve notifying other connected cores about changes through mechanisms like MESI protocol messages[^2]. - **Miss Case**: When no valid copy is found locally, several scenarios might unfold based on system design choices such as write-through versus write-back policies: - In a *write-through* policy, every change made by the CPU gets immediately propagated down all way to main RAM alongside being stored temporarily inside higher tiers' buffers if necessary. Such behavior ensures immediate visibility but comes at potential cost penalties due to increased traffic over interconnects linking various components together. - Alternatively, under a *write-back* strategy, modified entries remain cached until evicted either because they need replacement or explicitly flushed out during synchronization points; only then do these alterations reach permanent storage locations outside fast-accessible regions near execution units. To illustrate how this works programmatically without delving too deeply into hardware specifics, consider pseudo-code representing simplified logic behind handling store operations targeting potentially shared resources among concurrent actors operating independently yet cooperatively towards achieving common goals set forth within software applications running atop underlying platforms built around advanced microarchitectures incorporating sophisticated features aimed at optimizing overall efficiency metrics including power consumption rates relative to computational throughput capabilities offered per unit time interval measured against baseline benchmarks established prior development cycles commencing formal verification procedures before public release dates announced officially via press releases distributed widely amongst stakeholders involved throughout entire lifecycle management processes associated closely related activities spanning conception phase up till end-of-life discontinuation notices issued formally according official corporate governance guidelines adhered strictly thereto. ```c++ void cpuWriteData(uint64_t addr, uint8_t value){ // Check if address hits in L1 cache bool hit = checkCache(addr); if(hit){ // Update existing cache line updateCacheLine(addr,value); }else{ // Handle miss case handleCacheMiss(addr,value); // Depending on policy apply appropriate action if(writePolicy == WRITE_THROUGH){ writeToMainMemory(addr,value); } else{ // Assume WRITE_BACK here markDirtyBit(true); } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值