linux内核之Per-CPU变量

前言

通过Per-cpu变量除了可以分配内存,还有一个最大的好处就是不需要考虑同步。最好的同步技术就是把不需要同步的内核放在首位,因为每种显示的同步原语都有不容忽视的开销。

本质:Per-cpu变量主要是数据结构的数组,每个cpu对应数组中的一个元素。

位置:Per-cpu数组在主存中被排列,以使每个数据结构放在硬件高速缓存中的不同行(参考第二章硬件高速缓存)。这样,对每cpu数组的并发访问不会造成高速缓存行的窃用和实效(这种操作会带来昂贵的系统性能开销)。

应用条件

  1. 一个cpu不应该访问其它cpu对应的数组元素,另外,它可以随意读或者修改它自己的元素而不担心出现竞争条件。这样也意味者它只能在特殊的情况下使用,即确保在系统中cpu上的数据在逻辑上是独立的。
  2. 每cpu变量为来自不同cpu的并发访问提供保护,但对于来自异步函数(比如:本地中断和软中断)的访问不提供保护,在这种情况下需要提供额外的同步原语。
  3. 在单处理或多处理器系统中,内核抢占都会是每cpu变量产生竞争条件。总的原则是,内核控制路径应该在禁止内核抢占的情况下方法每cpu变量。

Eg:1.本地cpu上的任务切换。 2.一个内核控制路径获得了它的每cpu变量副本的地址,然后因为抢占内核控制路径被转移到另外一个cpu上(一个内核控制因为发生内核抢占而被转移到其他cpu上执行),但仍然引用原来cpu元素的地址。
优点:每个处理器访问自己的副本,几乎无需加锁(需要禁止抢占),可以放入自己的cache中,极大地提高了访问与更新效率。常用于计数器,tasklet、timer_list等机制都使用了per-CPU技术。

 实现原理

"include/asm-generic/percpu.h"

#define DEFINE_PER_CPU(type, name) \

__attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name

在GCC中__typeof__相当于typeof(检查数据类型),用__typeof__为了兼容性。

系统如何为每个CPU保留这些私有数据的?

在start_kernel函数中调用执行函数setup_per_cpu_areas( ),setup_per_cpu_areas( )的功能就是将.data.percpu中的数据拷贝到每个CPU的数据段中,每个CPU一份。其中CPU n 对应的专有数据区的首地址为__per_cpu_offset[n]。在系统启动的时候会生成以__per_cpu_start标识开头和__per_cpu_end标识结尾的数据段,在执行完函数setup_per_cpu_areas( )后,将.data.percpu中的数据拷贝到每个CPU数据段后,这个数据段就会释放(free)掉。
.data.percpu定义如下(在arch/ia64/kernel/vmlinux.lds.S):
 

{
   __per_cpu_start = .;
   *(.data.percpu)
   *(.data.percpu.shared_aligned)
   __per_cpu_end = .;
}

如何存取每个CPU的这些私有数据呢?

#define per_cpu(var, cpu) (*RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]))
#define __get_cpu_var(var) per_cpu(var, smp_processor_id())

相关函数和宏

"include/asm-generic/percpu.h"

DEFINE_PER_CPU(type,name)  //静态分配一个每cpu数组,参数为类型和名字

per_cpu(name, cpu)         //为cpu选择一个每cpu数组元素 

_ _get_cpu_var(name)       //选择每cpu数组中名字为name的本地cpu元素

"include\linux\percpu.h"

get_cpu_var(name)         //先禁止抢占,然后选择每cpu数组中名字为name的本地cpu元素

put_cpu_var(name)         //启用抢占,不使用name

alloc_percpu(type)        //动态分配type类型数据结构的每cpu数组,并返回它的地址;就是在普通内存中分配的,详见代码

free_percpu(pointer)      //释放动态分配的每cpu数组

per_cpu_ptr(pointer,cpu)  //返回每cpu数组中与参数cpu对应的cpu元素地址,pointer为返回的地址。

例子

#include <linux/module.h>
#include <linux/percpu.h>

/* 需要掌握的内容:
 * 1.怎么为每个cpu分配对应的变量,静态分配和动态分配,
 * 需要指定变量类型和名称,也可以赋初始值。
 * 2.怎么改变变量值,通过或per_cpu或者per_cpu_ptr.
 * 3.怎么获取变量值,也可以通过per_cpu或者per_cpu_ptr.
 */

/* 方式一:静态的分配每cpu数组 
 * 实际上是根据系统的cpu个数来分配数组,这个数组包含有cpu个数个元素,
 * 每个cpu在这个数组中都拥有一个对应的元素。
 */
static DEFINE_PER_CPU(int, val) = 8;
/* 动态分配的指针 */
static int __percpu *aval;

static int __init my_init(void)
{
	int cpu,i=1;

	pr_info("module init\n");
	
	/* 获得本地cpu的per-cpu变量值 */
	pr_info("local cpu:%d static per-cpu:%d\n", cpu, get_cpu_var(val));
	put_cpu_var(val);

	/* 打印每个cpu的per-cpu变量值 */
	for_each_possible_cpu(cpu){
		pr_info("each cpu:%d static per-cpu:%d\n", cpu, per_cpu(val,cpu));
	}

	/* 遍历每个可能的cpu */
	for_each_possible_cpu(cpu){
		/* 选择相应的cpu的per-cpu变量并赋值 */
		per_cpu(val, cpu) = cpu;	
		/* 获取相应的cpu的per-cpu变量并显示 */
		pr_info("cpu:%d assignment per-cpu:%d\n", cpu, per_cpu(val,cpu));
		/* 使能内核抢占,因为上面的函数禁止内核抢占了 */
	}

	/* 方式二:动态分配per-cpu变量 */
	aval = alloc_percpu(int);
	/* 遍历每个可能的cpu */
	for_each_possible_cpu(cpu){
		/* 通过指针为动态分配的per-cpu变量赋值 */
		*per_cpu_ptr(aval, cpu) = cpu;
		/* 获取相应的cpu的per-cpu变量并显示 */
		pr_info("cpu:%d alloc per-cpu:%d\n", cpu, *per_cpu_ptr(aval, cpu));
	}

	return 0;
}

static void __exit my_exit(void)
{
	pr_info("module exit\n");
	
	free_percpu(aval);
}

module_init(my_init);
module_exit(my_exit);

MODULE_AUTHOR("andy.chen");
MODULE_LICENSE("GPL v2");

结果如下:

  

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值