linux内核的数据结构:3 每CPU变量


http://blog.csdn.net/fengtaocat/article/details/7078472 


本文采用linux 3.04内核版本。

             多核情况下,CPU是同时并发运行的,但是多它们共同使用其他的硬件资源的,因此我们需要解决多个CPU之间的同步问题。每CPU变量(per-cpu-variable)是内核中一种重要的同步机制。顾名思义,每CPU变量就是为每个CPU构造一个变量的副本,这样多个CPU相互操作各自的副本,互不干涉。比如我们标识当前进程的变量current_task就被声明为每CPU变量。

每CPU变量的特点:

  1. 用于多个CPU之间的同步,如果是单核结构,每CPU变量没有任何用处。
  2. 每CPU变量不能用于多个CPU相互协作的场景。(每个CPU的副本都是独立的)
  3. 每CPU变量不能解决由中断或延迟函数导致的同步问题
  4. 访问每CPU变量的时候,一定要确保关闭进程抢占,否则一个进程被抢占后可能会更换CPU运行,这会导致每CPU变量的引用错误。
            我们可以用数组来实现每CPU变量吗?比如,我们要保护变量var,我们可以声明int var[NR_CPUS],CPU num就访问var[num]不就可以了吗?

            显然,每CPU变量的实现不会这么简单。理由:我们知道为了加快内存访问,处理器中设计了硬件高速缓存(也就是CPU的cache),每个处理器都会有一个硬件高速缓存。如果每CPU变量用数组来实现,那么任何一个CPU修改了其中的内容,都会导致其他CPU的高速缓存中对应的块失效。而频繁的失效会导致性能急剧的下降。

            每CPU变量分为静态和动态两种,静态的每CPU变量使用DEFINE_PER_CPU声明,在编译的时候分配空间;而动态的使用alloc_percpu和free_percpu来分配回收存储空间。下面我们来看看Linux中的具体实现:

每CPU变量的函数和宏

            每CPU变量的定义在include\linux\Percpu-defs.h以及include\asm-generic\Percpu.h中。这些文件中定义了单核和多核情况下的每CPU变量的操作,这是为了代码的统一设计的,实际上只有在多核情况下(定义了CONFIG_SMP)每CPU变量才有意义。常见的操作和含义如下:
  • DECLARE_PER_CPU(type, name)声明每CPU变量name,类型为type
  • DEFINE_PER_CPU(type, name)定义每CPU变量name,类型为type
  • alloc_percpu(type)动态为type类型的每CPU变量分配空间,并返回它的地址
  • free_percpu(pointer)释放为动态分配的每CPU变量的空间,pointer是起始地址
  • per_cpu(var, cpu)获取编号cpu的处理器上面的变量var的副本
  • get_cpu_var(var)获取本处理器上面的变量var的副本,该函数关闭进程抢占,主要由__get_cpu_var来完成具体的访问
  • get_cpu_ptr(var) 获取本处理器上面的变量var的副本的指针,该函数关闭进程抢占,主要由__get_cpu_var来完成具体的访问
  • put_cpu_var(var) & put_cpu_ptr(var)表示每CPU变量的访问结束,恢复进程抢占
  • __get_cpu_var(var) 获取本处理器上面的变量var的副本,该函数不关闭进程抢占

每CPU变量的实现原理

静态的每CPU变量

            通常情况下,静态声明的每CPU变量都会被编译在ELF文件中的以“.data.percpu”开头的段中(默认情况就是.data.percpu,也可以使用DEFINE_PER_CPU_SECTION(type, name, sec)来指定段的后缀)。具体的代码如下:
[cpp]  view plain copy
  1. #define DEFINE_PER_CPU(type, name)                  \  
  2.     DEFINE_PER_CPU_SECTION(type, name, "")  

[cpp]  view plain copy
  1. #define DEFINE_PER_CPU_SECTION(type, name, sec)             \  
  2.     __PCPU_ATTRS(sec) PER_CPU_DEF_ATTRIBUTES            \  
  3.     __typeof__(type) name  

[cpp]  view plain copy
  1. #define __PCPU_ATTRS(sec)                       \  
  2.     __percpu __attribute__((section(PER_CPU_BASE_SECTION sec))) \  
  3.     PER_CPU_ATTRIBUTES  

[cpp]  view plain copy
  1. #define PER_CPU_BASE_SECTION ".data..percpu"  

[cpp]  view plain copy
  1. __attribute__((section(PER_CPU_BASE_SECTION sec)  
            备注:每CPU变量的声明和普通变量的声明一样,主要的区别是使用了__attribute__((section(PER_CPU_BASE_SECTION sec)))来指定该变量被放置的段中,普通变量默认会被放置data段或者bss段中。
            看到这里有一个问题:如果我们只是声明了一个变量,那么如果有多个副本的呢?奥妙在于内核加载的过程。
            一般情况下,ELF文件中的每一个段在内存中只会有一个副本,而.data.percpu段再加载后,又被复制了NR_CPUS次,一个每CPU变量的多个副本在内存中是不会相邻。示意图如下:

            具体的代码参加start_kernel中调用的setup_per_cpu_areas函数。代码如下:
[cpp]  view plain copy
  1. void __init setup_per_cpu_areas(void)  
  2. {  
  3.     unsigned long delta;  
  4.     unsigned int cpu;  
  5.     int rc;  
  6.   
  7.     /* 
  8.      * Always reserve area for module percpu variables.  That's 
  9.      * what the legacy allocator did. 
  10.      */  
  11.     rc = pcpu_embed_first_chunk(PERCPU_MODULE_RESERVE,  
  12.                     PERCPU_DYNAMIC_RESERVE, PAGE_SIZE, NULL,  
  13.                     pcpu_dfl_fc_alloc, pcpu_dfl_fc_free);  
  14.     if (rc < 0)  
  15.         panic("Failed to initialize percpu areas.");  
  16.   
  17.     delta = (unsigned long)pcpu_base_addr - (unsigned long)__per_cpu_start;  
  18.     for_each_possible_cpu(cpu)  
  19.         __per_cpu_offset[cpu] = delta + pcpu_unit_offsets[cpu];  
  20. }  

            备注:分配内存以及复制.data.percup内容的工作由pcpu_embed_first_chunk来完成,这里就不展开了。__per_cpu_offset数组中记录了每个CPU的percpu区域的开始地址。我们访问每CPU变量就要依靠__per_cpu_offset中的地址。

动态每CPU变量

            了解了静态的每CPU变量的实现机制后,就很容易想到动态的每CPU变量的实现方法了。实际上,在setup_per_cpu_areas的时候,我们会为每个CPU都多申请一部分空间留作动态分配每CPU变量之用(一个场景就是内核模块中的每CPU变量)。相对于静态的每CPU变量,我们需要额外管理内存的分配和回收。

每CPU变量的访问

我们以per_cpu为例,来看一下每CPU变量的访问是如何实现的。代码如下:
[cpp]  view plain copy
  1. #define per_cpu(var, cpu) \  
  2.     (*SHIFT_PERCPU_PTR(&(var), per_cpu_offset(cpu)))  
其中per_cpu_offset是获取编号为cpu的处理器上的每CPU区域的地址,实际上就是数组 __per_cpu_offset中对应的项。具体实现如下:
[cpp]  view plain copy
  1. #define per_cpu_offset(x) (__per_cpu_offset[x])  
[cpp]  view plain copy
  1. <pre name="code" class="cpp" style="margin-top: 4px; margin-right: 0px; margin-bottom: 4px; margin-left: 0px; background-color: rgb(240, 240, 240); ">#define SHIFT_PERCPU_PTR(__p, __offset)   ({              \  
  2.     __verify_pcpu_ptr((__p));                   \  
  3.     RELOC_HIDE((typeof(*(__p)) __kernel __force *)(__p), (__offset)); \  
  4. })</pre><pre name="code" class="cpp" style="margin-top: 4px; margin-right: 0px; margin-bottom: 4px; margin-left: 0px; background-color: rgb(240, 240, 240); "><pre name="code" class="cpp" style="margin-top: 4px; margin-right: 0px; margin-bottom: 4px; margin-left: 0px; background-color: rgb(240, 240, 240); ">#define __verify_pcpu_ptr(ptr)  do {                    \  
  5.     const void __percpu *__vpp_verify = (typeof(ptr))NULL;      \  
  6.     (void)__vpp_verify;                     \  
  7. while (0)</pre>  
  8. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  9. <pre name="code" class="cpp" style="margin-top: 4px; margin-right: 0px; margin-bottom: 4px; margin-left: 0px; background-color: rgb(240, 240, 240); "># define RELOC_HIDE(ptr, off)                 \  
  10.   ({ unsigned long __ptr;                   \  
  11.      __ptr = (unsigned long) (ptr);             \  
  12.     (typeof(ptr)) (__ptr + (off)); })</pre><br>  
  13. <span style="white-space:pre"></span><span style="white-space:pre"></span>            备注:__verify_pcpu是为了验证var是否是一个每CPU变量(如果不是,会再编译的时候报错)。实际上的存取简化后相当于*(var的地址(即相对偏移)+__per_cpu_offset)。<br>  
  14. <span style="white-space:pre"></span>  
  15. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  16. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  17. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  18. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  19. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  20. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  21. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  22. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  23. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  24. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  25. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  26. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  27. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  28. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  29. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  30. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  31. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  32. <pre style="margin-top:4px; margin-right:0px; margin-bottom:4px; margin-left:0px; background-color:rgb(240,240,240)"></pre>  
  33. <pre></pre>  
  34. <pre></pre>  
  35. </pre>  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值