Chinaunix首页 | 论坛 | 博客
  • 博客访问: 20915
  • 博文数量: 3
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2014-07-08 10:37
文章分类
文章存档

2015年(1)

2014年(2)

我的朋友

分类: LINUX

2014-11-17 16:57:12

作者 crosskernel@gmail.com 图书试读版-请勿转载


1.2 Percpu内存管理




随着处理器核心的增加,内核中系统中并发的线程也随之增加,这样对一些共享数据的同时访问机率也就增加,就避免不了spin_lock的使用,而且往往处理器核心越多造成的麻烦越大。Percpu内存对这种数据无能为力,但是内核中有些数据只是处理器局部可见,这种数据不会被别的处理器访问到,不需要加以spin_lock而直接访问。Percpu内存适用于这种数据,而实际上处理器局部数据的实际分配还是通过内核基本slab进行,而Percp内存则是用来存放指向处理器局部数据的指针。




1.2.1 处理器静态定义局部数据




首先在链接文件arch/arm/kernel/vmlinux.lds.S中包含了:
PERCPU_SECTION(32)




展开该宏:




#define PERCPU_SECTION(cacheline) \
. = ALIGN(PAGE_SIZE);\
.data..percpu  : AT(ADDR(.data..percpu) - LOAD_OFFSET) { \
VMLINUX_SYMBOL(__per_cpu_load) = .;\
PERCPU_INPUT(cacheline)\
}








#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) = .;




该宏的作用是声明了以 “.data..percpu..first”,“.data..percpu..readmostly”,“.data..percpu”等为名字的数据段,内核代码里如果有变量的属性指出放在该数据段的时候,链接时将其分配到对应的数据段。而且为了能够在内核里直接访问这段区域,定义两个变量__per_cpu_start,和__per_cpu_end。




而另一方面:
#define DEFINE_PER_CPU(type, name) \
DEFINE_PER_CPU_SECTION(type, name, "")




#define DEFINE_PER_CPU_SECTION(type, name, sec)  \
__PCPU_ATTRS(sec) PER_CPU_DEF_ATTRIBUTES\
__typeof__(type) name




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




#define PER_CPU_BASE_SECTION ".data..percpu"




根据以上宏定义展开之:可以得到
__attribute__((section(.data..percpu))) __typeof__(type) name




可见宏“DEFINE_PER_CPU(type, name)”的作用就是将类型为“type”的“name”变量放倒“.data..percpu”数据段。




同样方法可以推出:宏“DECLARE_PER_CPU_READ_MOSTLY(type, name)” 的作用就是将类型为“type”的“name”变量放倒“.data..percpu..readmostly”数据段。这种数据段是cacheline对齐,可以大大提高cache利用率,很适合频繁读取数据,不过在笔者分析的这个内核版本3.0.13中,内核里这种数据还没有大规模使用的。而且cacheline定义为32,对于不同架构可以使用时可以适当调整。




另外宏“DECLARE_PER_CPU_FIRST(type, name)”对应“.data..percpu..first”数据段。
宏“DECLARE_PER_CPU_PAGE_ALIGNED”对应“.data..percpu..first”数据段。…












然后在内核初始化过程中:




void __init setup_per_cpu_areas(void)
{
rc = pcpu_embed_first_chunk(PERCPU_MODULE_RESERVE,
   PERCPU_DYNAMIC_RESERVE, PAGE_SIZE, NULL,
   pcpu_dfl_fc_alloc, pcpu_dfl_fc_free);


delta = (unsigned long)pcpu_base_addr - (unsigned long)__per_cpu_start;
   /*设置每个cpu的__per_cpu_offset 指针*/
for_each_possible_cpu(cpu)
__per_cpu_offset[cpu] = delta + pcpu_unit_offsets[cpu];
}




再看对percpu数据的引用:




#define per_cpu(var, cpu) \
(*SHIFT_PERCPU_PTR(&(var), per_cpu_offset(cpu)))




#define SHIFT_PERCPU_PTR(__p, __offset) ({\
__verify_pcpu_ptr((__p));\
RELOC_HIDE((typeof(*(__p)) __kernel __force *)(__p), (__offset)); \
})




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




/*选用一个简单易看得RELOC_HIDE 宏定义*/
# define RELOC_HIDE(ptr, off) \
  ({ unsigned long __ptr; \
     __ptr = (unsigned long) (ptr); \
(typeof(ptr)) (__ptr + (off)); })




可见是以当前cpu局部数据“__per_cpu_offset[x]”为基地址的相对寻址。








1.2.2 percpu内存管理的建立




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




对于处理器的分组信息,内核使用“struct pcpu_group_info”结构表示:
struct pcpu_group_info {
    /* 该组的处理器数目,对于双核CA9处理器,该值为2 */
int nr_units;
    /*组内处理器数目×处理器percpu虚拟内存递进基本单位*/
unsigned long  base_offset; 
   /*组内处理器对应数组,双核CA9架构下该数组长度为2*/
unsigned int  *cpu_map;  
};




整体的percpu内存管理信息被收集在“struct pcpu_alloc_info”结构中:
struct pcpu_alloc_info {
    //静态定义的percpu变量占用内存区域长度
size_t  static_size;
   /*预留区域,在percpu内存分配指定为预留区域分配时,将使用该区域*/
size_t  reserved_size;
size_t  dyn_size;
    /*每颗处理器的percpu虚拟内存递进基本单位*/
size_t  unit_size;

    /*该架构下的处理器分组数目,CA9双核架构下该值为1*/
int nr_groups;
/*该架构下的处理器分组信息,CA9双核架构下该数组长度为1*/
struct pcpu_group_infogroups[];
};




接下来构建静态定义的percpu变量创建percpu区域




/*该函数被“void __init setup_per_cpu_areas(void)”调用,对于CA9架构其参数“cpu_distance_fn”为“NULL”,参数“alloc_fn”为“pcpu_dfl_fc_alloc”*/
int __init pcpu_embed_first_chunk(size_t reserved_size, size_t dyn_size,
 size_t atom_size,
 pcpu_fc_cpu_distance_fn_t cpu_distance_fn,
 pcpu_fc_alloc_fn_t alloc_fn,
 pcpu_fc_free_fn_t free_fn)
{
void *base = (void *)ULONG_MAX;
void **areas = NULL;
struct pcpu_alloc_info *ai;
size_t size_sum, areas_size, max_distance;
int group, i, rc;
    /*收集整理该架构下的percpu信息,结果放在“struct pcpu_alloc_info”结构中*/
ai = pcpu_build_alloc_info(reserved_size, dyn_size, atom_size,
  cpu_distance_fn);

    //静态定义变量占用空间+reserved空间+动态分配空间
    size_sum = ai->static_size + ai->reserved_size + ai->dyn_size;

/*针对每个group操作 */
for (group = 0; group < ai->nr_groups; group++) {
struct pcpu_group_info *gi = &ai->groups[group];
unsigned int cpu = NR_CPUS;
void *ptr;









/* 为该group分配percpu内存区域。长度为处理器数目X每颗处理器的percpu递进单位。函数“pcpu_dfl_fc_alloc”是从bootmem里取得内存,得到的是物理内存 */
ptr = alloc_fn(cpu, gi->nr_units * ai->unit_size, atom_size);

areas[group] = ptr;




base = min(ptr, base);
       //为每颗处理器建立其percpu区域
for (i = 0; i < gi->nr_units; i++, ptr += ai->unit_size) {
if (gi->cpu_map[i] == NR_CPUS) {
/*检查组内处理器器,对于没有用到处理器释放其percpu区域 */
free_fn(ptr, ai->unit_size);
continue;
}
/*将定态定义的percpu变量拷贝到每颗处理器percpu区域*/
memcpy(ptr, __per_cpu_load, ai->static_size);
           /*为每颗处理器释放掉多余的空间,多余的空间是指ai->unit_size 减去静态定义变量占用空间+reserved空间+动态分配空间*/
free_fn(ptr + size_sum, ai->unit_size - size_sum);
}
}




/* 处理器架构相关的计算,对于CA9双核架构,ai->nr_groups 为1,且“ai->groups[group].base_offset”和“max_distance” 这里的计算结果都为“0”*/
max_distance = 0;
for (group = 0; group < ai->nr_groups; group++) {
ai->groups[group].base_offset = areas[group] - base;
max_distance = max_t(size_t, max_distance,
    ai->groups[group].base_offset);
}
   
max_distance += ai->unit_size;

    //建立可动态分配的percpu内存区域
rc = pcpu_setup_first_chunk(ai, base);

}








//建立可动态分配的percpu内存区域
int __init pcpu_setup_first_chunk(const struct pcpu_alloc_info *ai,
 void *base_addr)
{
static char cpus_buf[4096] __initdata;
static int smap[PERCPU_DYNAMIC_EARLY_SLOTS] __initdata;
static int dmap[PERCPU_DYNAMIC_EARLY_SLOTS] __initdata;
….


for (cpu = 0; cpu < nr_cpu_ids; cpu++)
unit_map[cpu] = UINT_MAX;
pcpu_first_unit_cpu = NR_CPUS;




   /*针对每一group的每一颗处理器,对于对于双核CA9,“ai->nr_groups”值为“0”“gi->nr_units”值为2*/
for (group = 0, unit = 0; group < ai->nr_groups; group++, unit += i) {
const struct pcpu_group_info *gi = &ai->groups[group];
        
        //该组处理器的percpu偏移量,对于双核CA9,该值为“0”
group_offsets[group] = gi->base_offset;
       //该组处理器占用的虚拟地址空间
group_sizes[group] = gi->nr_units * ai->unit_size;




       //针对组内的每颗处理器
for (i = 0; i < gi->nr_units; i++) {
cpu = gi->cpu_map[i];
if (cpu == NR_CPUS)
continue;





            unit_map[cpu] = unit + i;
           //计算每颗处理器的percpu虚拟空间偏移量
unit_off[cpu] = gi->base_offset + i * ai->unit_size;

}
}
pcpu_nr_units = unit;





    //记录下全局参数,留在“pcpu_alloc”时使用
pcpu_nr_groups = ai->nr_groups;
    …
pcpu_unit_offsets = unit_off;




/* determine basic parameters */
pcpu_unit_pages = ai->unit_size >> PAGE_SHIFT;
pcpu_unit_size = pcpu_unit_pages << PAGE_SHIFT;
pcpu_atom_size = ai->atom_size;
pcpu_chunk_struct_size = sizeof(struct pcpu_chunk) +
BITS_TO_LONGS(pcpu_unit_pages) * sizeof(unsigned long);




/*
构建“pcpu_slot”数组,不同size的chunck挂在不同“pcpu_slot”项目中
*/
pcpu_nr_slots = __pcpu_size_to_slot(pcpu_unit_size) + 2;
pcpu_slot = alloc_bootmem(pcpu_nr_slots * sizeof(pcpu_slot[0]));
   //初始化“pcpu_slot”数组链头
for (i = 0; i < pcpu_nr_slots; i++)
INIT_LIST_HEAD(&pcpu_slot[i]);




/*
构建静态chunck即“pcpu_reserved_chunk”,该区域的物理内存以及虚拟地址都在“int __init pcpu_embed_first_chunk(…)”里分配了。
*/
schunk = alloc_bootmem(pcpu_chunk_struct_size);

schunk->immutable = true;
    //物理内存已经分配这里标志之
bitmap_fill(schunk->populated, pcpu_unit_pages);

if (ai->reserved_size) {
       //reserved的空间,在指定reserved分配时使用
schunk->free_size = ai->reserved_size;
pcpu_reserved_chunk = schunk;
        //定义的静态变量的空间也算进来
pcpu_reserved_chunk_limit = ai->static_size + ai->reserved_size;
} else {
schunk->free_size = dyn_size;
dyn_size = 0;  /* dynamic area covered */
}
schunk->contig_hint = schunk->free_size;




schunk->map[schunk->map_used++] = -ai->static_size;








if (schunk->free_size)
schunk->map[schunk->map_used++] = schunk->free_size;




/* 动态分配空间,这里构建第一个chunck,该chunk是第一次步进时静态变量空间和reserved空间使用后剩下的*/
if (dyn_size) {
dchunk = alloc_bootmem(pcpu_chunk_struct_size);
INIT_LIST_HEAD(&dchunk->list);

        //记录下来分配的物理页
bitmap_fill(dchunk->populated, pcpu_unit_pages);




dchunk->contig_hint = dchunk->free_size = dyn_size;
        /*map指针更新将静态变量空间和reserved空间甩在后面*/
dchunk->map[dchunk->map_used++] = -pcpu_reserved_chunk_limit;
dchunk->map[dchunk->map_used++] = dchunk->free_size;
}




/* 把第一个chunk链接进对应的slot链表,reserverd 的空间有自己单独的chunk:pcpu_reserved_chunk */
pcpu_first_chunk = dchunk ?: schunk;
pcpu_chunk_relocate(pcpu_first_chunk, -1);




/* we're done */
pcpu_base_addr = base_addr;
return 0;
}




1.2.3 percpu动态分配内存空间




关于percpu动态分配内存空间有以下基本概念




? 每颗处理器的自己percpu动态分配内存空间都有不同的虚拟地址空间。否则同一线程在不同处理器上运行时修改页表页目录项的开销太大。
? Chunk记录每颗处理器一次步进得到虚拟地址空间,对于一颗处理器来说一次步进长度是“ai->unit_size”,percpu内存的虚拟地址以chunk为基础
? Chunk中每一次配出的内存用其map[]指向
? Chunk根据其free的内存长度挂到Slot数组的对应链表上
? 每次步进仅得到虚拟地址空间,在完成一次分配时才得到对应的物理内存
? 本书仅考虑percpu-vm的情况




/*动态分配函数 */
static void __percpu *pcpu_alloc(size_t size, size_t align, bool reserved)
{
static int warn_limit = 10;
struct pcpu_chunk *chunk;
const char *err;
int slot, off, new_alloc;
unsigned long flags;




mutex_lock(&pcpu_alloc_mutex);
spin_lock_irqsave(&pcpu_lock, flags);




/* 指定reserved分配,从pcpu_reserved_chunk进行 ,较简单不讨论*/
if (reserved && pcpu_reserved_chunk) {

}




restart:
/* 根据需要分配内存块的大小索引slot数组找到对应链表 */
for (slot = pcpu_size_to_slot(size); slot < pcpu_nr_slots; slot++) {
list_for_each_entry(chunk, &pcpu_slot[slot], list) {
           //在该链表中进一步寻找符合尺寸要求的chunk
if (size > chunk->contig_hint)
continue;
           
/*chunck用数组“int *map”记录每次分配的内存块,若该数组用完(该chunk仍然还有自由空间),则需要增长该“int *map”数组。*/
new_alloc = pcpu_need_to_extend(chunk);
if (new_alloc) {
spin_unlock_irqrestore(&pcpu_lock, flags);
              //扩展“int *map”数组。
if (pcpu_extend_area_map(chunk,
new_alloc) < 0) {

}
spin_lock_irqsave(&pcpu_lock, flags);
goto restart;
}
            /*在该chunk里分配虚拟内存空间:分割最后一段自由空间,然后重新将该chunk挂到slot数组对应链表中*/
off = pcpu_alloc_area(chunk, size, align);
           //off大于0表示分配成功
if (off >= 0)
goto area_found;
}
}




spin_unlock_irqrestore(&pcpu_lock, flags);
    /*创建一个新的chunk,这里进行的是虚拟地址空间的分配 */
chunk = pcpu_create_chunk();

spin_lock_irqsave(&pcpu_lock, flags);
    //把一个全新的chunk挂到slot数组对应链表中
pcpu_chunk_relocate(chunk, -1);
goto restart;




area_found:
spin_unlock_irqrestore(&pcpu_lock, flags);




/* 一次percpu内存分配成功,这里要检查该段区域对应物理页是否已经分配,否者将为该区域分配对应的物理页并作填充L1 L2页表项*/
if (pcpu_populate_chunk(chunk, off, size)) {

}




mutex_unlock(&pcpu_alloc_mutex);




/* return address relative to base address */
return __addr_to_pcpu_ptr(chunk->base_addr + off);





}

阅读(1667) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~