CPU私有变量(per-CPU变量)
一、简介
2.6内核上一个新的特性就是per-CPU变量。顾名思义,就是每个处理器上有此变量的一个副本。
per-CPU的最大优点就是,对它的访问几乎不需要锁,因为每个CPU都在自己的副本上工作。
tasklet、timer_list等机制都使用了per-CPU技术。
当创建一个per-cpu变量时,系统中的每一个处理器都会拥有该变量的独有副本。由于每个处理器都是在自己的副本上工作,所以对per-cpu变量的访问几乎不需要加锁。
per-cpu变量只为来自不同处理器的并发访问提供保护,对来自异步函数(中断处理程序和可延迟函数)的访问,以及内核抢
并不提供保护,因此在这些情况下还需要另外的同步原语
二、API使用
注意,2.6内核是抢占式的。
所以在访问per-CPU变量时,应使用特定的API来避免抢占,即避免它被切换到另一个CPU上被处理。
per-CPU变量可以在编译时声明,也可以在系统运行时动态生成
实例一:
编译期间创建一个per-CPU变量:
DEFINE_PER_CPU(int,my_percpu); //声明一个变量
DEFINE_PER_CPU(int[3],my_percpu_array); //声明一个数组
访问per-CPU变量:
ptr = get_cpu_var(my_percpu); //
使用ptr
put_cpu_var(my_percpu); //
当然,也可以使用下列宏来访问特定CPU上的per-CPU变量
per_cpu(my_percpu, cpu_id); //
per-CPU变量导出,供模块使用:
EXPORT_PER_CPU_SYMBOL(per_cpu_var);
EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var);
实例二:
动态分配per-CPU变量:
void *alloc_percpu(type);
使用动态生成的per-CPU变量:
int cpu;
cpu = get_cpu();
ptr = per_cpu_ptr(my_percpu);
//使用ptr
put_cpu();
三、实现
使用上面的API为什么就能避免抢占问题呢,看看代码实现就知道了
#define get_cpu_var(var) (*({ \
extern int simple_identifier_##var(void); \
preempt_disable(); \
&__get_cpu_var(var); }))
#define put_cpu_var(var) preempt_enable()
#define get_cpu() ({ preempt_disable(); smp_processor_id(); })
#define put_cpu() preempt_enable()
关键就在于 preempt_disable 和 preempt_enable 两个调用,分别是禁止抢占和开启抢占
抢占相关的东东以后再看
per-cpu 变量的引入有效的解决了SMP系统中处理器对锁得竞争,每个cpu只需访问自己的本地变量。本文阐述了per-cpu变量在2.6内核上的实现和相关操作。
在系统编译阶段我们就手工的定义了一份所有的per-cpu变量,这些变量的定义是通过宏DEFINE_PER_CPU实现的:
11 #define DEFINE_PER_CPU(type, name) \
12 __attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name
从上面的代码我们可以看出,手工定义的所有per-cpu变量都是放在.data.percpu段的。注意上面的宏只是在SMP体系结构下才如此定义。如果不是SMP结构的计算机那么只是简单的把所有的per-cpu变量放到全局变量应该放到的地方。
单CPU的per-cpu变量定义:
27 #else /* ! SMP */
28
29 #define DEFINE_PER_CPU(type, name) \
30 __typeof__(type) per_cpu__##name
在了解了上述代码后,我们还必须弄清楚一点:单CPU的计算机中使用的per-cpu变量就是通过上述宏定义的放在全局数据区的per-cpu变 量。而在SMP体系结构中,我们使用却不是放在.data.percpu段的变量,设想一下如果使用这个变量,那么应该哪个CPU使用呢?事实上,SMP 下,每个cpu使用的都是在.data.percpu段中的这些per-cpu变量的副本,有几个cpu就创建几个这样的副本。
在系统初始化期 间,start_kernel()函数中调用setup_per_cpu_areas()函数,用于为每个cpu的per-cpu变量副本分配空间,注意 这时alloc内存分配器还没建立起来,该函数调用alloc_bootmem函数为初始化期间的这些变量副本分配物理空间。
332 static void __init setup_per_cpu_areas(void)
/* */
333 {
334 unsigned long size, i;
335 char *ptr;
336
337 /* Copy section for each CPU (we discard the original) */
338 size = ALIGN(__per_cpu_end - __per_cpu_start, SMP_CACHE_BYTES);
339 #ifdef CONFIG_MODULES
340 if (size < PERCPU_ENOUGH_ROOM)
341 size = PERCPU_ENOUGH_ROOM;
342 #endif
343
344 ptr = alloc_bootmem(size * NR_CPUS);
345
346 for (i = 0; i < NR_CPUS; i++, ptr += size) {
347 __per_cpu_offset[i] = ptr - __per_cpu_start;
348 memcpy(ptr, __per_cpu_start, __per_cpu_end - __per_cpu_start);
349 }
350 }
351 #endif /* !__GENERIC_PER_CPU */
上述函数,在分配好每个cpu的per-cpu变量副本所占用的物理空间的同时,也对__per_cpu_offset[NR_CPUS]数组进行了初始化用于以后找到指定CPU的这些per-cpu变量副本。
15 #define per_cpu(var, cpu) (*RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]))
16 #define __get_cpu_var(var) per_cpu(var, smp_processor_id())
这两个宏一个用于获得指定cpu的per-cpu变量,另一个用于获的本地cpu的per-cpu变量,可以自己分析一下。
多核中percpu的数据分配
linux2.6 为了方便创建和操作每个CPU数据,引进了新的操作接口,percpu(),该接口简化了创建了操作每个CPU的数据。
定义于<linux/percpu.h> 和<mm/slab.c> 中
1. 定义和声明每个CPU变量:
[cpp] view plaincopyprint?
DEFINE_PER_CPU(type, name);
这语句为系统的每个CPU都创建了一个类型为type,名字为name的变量。如果需要在别处声明此变量,以防编译时的警告,可使用下面的宏:
[cpp] view plaincopyprint?
DECLARE_PER_CPU(type, name);
2. 操作每个CPU的变量和指针:
get_cpu_var(name); //返回当前处理器上的指定变量name的值, 同时将他禁止抢占;
put_cpu_var(name); //与get_cpu_var(name)相对应,重新激活抢占;
[cpp] view plaincopyprint?
/*
* Must be an lvalue. Since @var must be a simple identifier,
* we force a syntax error here if it isn't.
*/
#define get_cpu_var(var) (*({ \
preempt_disable(); \
&__get_cpu_var(var); }))
/*
* The weird & is necessary because sparse considers (void)(var) to be
* a direct dereference of percpu variable (var).
*/
#define put_cpu_var(var) do { \
(void)&(var); \
preempt_enable(); \
} while (0)
通过指针来操作每个CPU的数据:
get_cpu_ptr(var); --- 返回一个void类型的指针,指向CPU ptr处的数据
put_cpu_ptr(var); --- 操作完成后,重新激活内核抢占。
[cpp] view plaincopyprint?
#define get_cpu_ptr(var) ({ \
preempt_disable(); \
this_cpu_ptr(var); })
#define put_cpu_ptr(var) do { \
(void)(var); \
preempt_enable(); \
} while (0)
3. 获得别的处理器上的name变量的值
per_cpu(name, cpu) ; //返回别的处理器cpu上变量name的值;
4. 给系统中每个处理器分配一个指定类型的对象: alloc_percpu();
[cpp] view plaincopyprint?
#define alloc_percpu(type) \
(typeof(type) __percpu *)__alloc_percpu(sizeof(type), __alignof__(type))
/**
* __alloc_percpu - allocate dynamic percpu area
* @size: size of area to allocate in bytes
* @align: alignment of area (max PAGE_SIZE)
*
* Allocate zero-filled percpu area of @size bytes aligned at @align.
* Might sleep. Might trigger writeouts.
*
* CONTEXT:
* Does GFP_KERNEL allocation.
*
* RETURNS:
* Percpu pointer to the allocated area on success, NULL on failure.
*/
void __percpu *__alloc_percpu(size_t size, size_t align)
{
return pcpu_alloc(size, align, false);
}
EXPORT_SYMBOL_GPL(__alloc_percpu);
参数为type, 就是指定的需要分配的类型,通过类型,可以得出__alloc_percpu()的两个参数:
size = sizeof(type);
align = __alignof__(type);
__alignof__()是gcc的一个功能,它会返回指定类型或lvalue所需的对齐字节数。
相应的释放所有处理器上指定的每个CPU数据:free_percpu();
[cpp] view plaincopyprint?
/**
* free_percpu - free percpu area
* @ptr: pointer to area to free
*
* Free percpu area @ptr.
*
* CONTEXT:
* Can be called from atomic context.
*/
void free_percpu(void __percpu *ptr)
{
void *addr;
struct pcpu_chunk *chunk;
unsigned long flags;
int off;
if (!ptr)
return;
addr = __pcpu_ptr_to_addr(ptr);
spin_lock_irqsave(&pcpu_lock, flags);
chunk = pcpu_chunk_addr_search(addr);
off = addr - chunk->base_addr;
pcpu_free_area(chunk, off);
/* if there are more than one fully free chunks, wake up grim reaper */
if (chunk->free_size == pcpu_unit_size) {
struct pcpu_chunk *pos;
list_for_each_entry(pos, &pcpu_slot[pcpu_nr_slots - 1], list)
if (pos != chunk) {
schedule_work(&pcpu_reclaim_work);
break;
}
}
spin_unlock_irqrestore(&pcpu_lock, flags);
}
EXPORT_SYMBOL_GPL(free_percpu);
5. get_cpu()/put_cpu() --- 获得处理器编号
get_cpu() 在返回当前处理器编号之前,先回关闭内核抢占。
put_cpu() 重新打开内核抢占。
函数定义如下:
[cpp] view plaincopyprint?
#define get_cpu() ({ preempt_disable(); smp_processor_id(); })
#define put_cpu() preempt_enable()
使用案例如下:
[cpp] view plaincopyprint?
int cpu;
cpu = get_cpu(); //禁止抢占内核,并将CPU设置为当前处理器。
//对每个处理器的数据进行操作,
put_cpu(); //使能内核抢占
一、简介
2.6内核上一个新的特性就是per-CPU变量。顾名思义,就是每个处理器上有此变量的一个副本。
per-CPU的最大优点就是,对它的访问几乎不需要锁,因为每个CPU都在自己的副本上工作。
tasklet、timer_list等机制都使用了per-CPU技术。
当创建一个per-cpu变量时,系统中的每一个处理器都会拥有该变量的独有副本。由于每个处理器都是在自己的副本上工作,所以对per-cpu变量的访问几乎不需要加锁。
per-cpu变量只为来自不同处理器的并发访问提供保护,对来自异步函数(中断处理程序和可延迟函数)的访问,以及内核抢
并不提供保护,因此在这些情况下还需要另外的同步原语
二、API使用
注意,2.6内核是抢占式的。
所以在访问per-CPU变量时,应使用特定的API来避免抢占,即避免它被切换到另一个CPU上被处理。
per-CPU变量可以在编译时声明,也可以在系统运行时动态生成
实例一:
编译期间创建一个per-CPU变量:
DEFINE_PER_CPU(int,my_percpu); //声明一个变量
DEFINE_PER_CPU(int[3],my_percpu_array); //声明一个数组
访问per-CPU变量:
ptr = get_cpu_var(my_percpu); //
使用ptr
put_cpu_var(my_percpu); //
当然,也可以使用下列宏来访问特定CPU上的per-CPU变量
per_cpu(my_percpu, cpu_id); //
per-CPU变量导出,供模块使用:
EXPORT_PER_CPU_SYMBOL(per_cpu_var);
EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var);
实例二:
动态分配per-CPU变量:
void *alloc_percpu(type);
使用动态生成的per-CPU变量:
int cpu;
cpu = get_cpu();
ptr = per_cpu_ptr(my_percpu);
//使用ptr
put_cpu();
三、实现
使用上面的API为什么就能避免抢占问题呢,看看代码实现就知道了
#define get_cpu_var(var) (*({ \
extern int simple_identifier_##var(void); \
preempt_disable(); \
&__get_cpu_var(var); }))
#define put_cpu_var(var) preempt_enable()
#define get_cpu() ({ preempt_disable(); smp_processor_id(); })
#define put_cpu() preempt_enable()
关键就在于 preempt_disable 和 preempt_enable 两个调用,分别是禁止抢占和开启抢占
抢占相关的东东以后再看
per-cpu 变量的引入有效的解决了SMP系统中处理器对锁得竞争,每个cpu只需访问自己的本地变量。本文阐述了per-cpu变量在2.6内核上的实现和相关操作。
在系统编译阶段我们就手工的定义了一份所有的per-cpu变量,这些变量的定义是通过宏DEFINE_PER_CPU实现的:
11 #define DEFINE_PER_CPU(type, name) \
12 __attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name
从上面的代码我们可以看出,手工定义的所有per-cpu变量都是放在.data.percpu段的。注意上面的宏只是在SMP体系结构下才如此定义。如果不是SMP结构的计算机那么只是简单的把所有的per-cpu变量放到全局变量应该放到的地方。
单CPU的per-cpu变量定义:
27 #else /* ! SMP */
28
29 #define DEFINE_PER_CPU(type, name) \
30 __typeof__(type) per_cpu__##name
在了解了上述代码后,我们还必须弄清楚一点:单CPU的计算机中使用的per-cpu变量就是通过上述宏定义的放在全局数据区的per-cpu变 量。而在SMP体系结构中,我们使用却不是放在.data.percpu段的变量,设想一下如果使用这个变量,那么应该哪个CPU使用呢?事实上,SMP 下,每个cpu使用的都是在.data.percpu段中的这些per-cpu变量的副本,有几个cpu就创建几个这样的副本。
在系统初始化期 间,start_kernel()函数中调用setup_per_cpu_areas()函数,用于为每个cpu的per-cpu变量副本分配空间,注意 这时alloc内存分配器还没建立起来,该函数调用alloc_bootmem函数为初始化期间的这些变量副本分配物理空间。
332 static void __init setup_per_cpu_areas(void)
/* */
333 {
334 unsigned long size, i;
335 char *ptr;
336
337 /* Copy section for each CPU (we discard the original) */
338 size = ALIGN(__per_cpu_end - __per_cpu_start, SMP_CACHE_BYTES);
339 #ifdef CONFIG_MODULES
340 if (size < PERCPU_ENOUGH_ROOM)
341 size = PERCPU_ENOUGH_ROOM;
342 #endif
343
344 ptr = alloc_bootmem(size * NR_CPUS);
345
346 for (i = 0; i < NR_CPUS; i++, ptr += size) {
347 __per_cpu_offset[i] = ptr - __per_cpu_start;
348 memcpy(ptr, __per_cpu_start, __per_cpu_end - __per_cpu_start);
349 }
350 }
351 #endif /* !__GENERIC_PER_CPU */
上述函数,在分配好每个cpu的per-cpu变量副本所占用的物理空间的同时,也对__per_cpu_offset[NR_CPUS]数组进行了初始化用于以后找到指定CPU的这些per-cpu变量副本。
15 #define per_cpu(var, cpu) (*RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]))
16 #define __get_cpu_var(var) per_cpu(var, smp_processor_id())
这两个宏一个用于获得指定cpu的per-cpu变量,另一个用于获的本地cpu的per-cpu变量,可以自己分析一下。
多核中percpu的数据分配
linux2.6 为了方便创建和操作每个CPU数据,引进了新的操作接口,percpu(),该接口简化了创建了操作每个CPU的数据。
定义于<linux/percpu.h> 和<mm/slab.c> 中
1. 定义和声明每个CPU变量:
[cpp] view plaincopyprint?
DEFINE_PER_CPU(type, name);
这语句为系统的每个CPU都创建了一个类型为type,名字为name的变量。如果需要在别处声明此变量,以防编译时的警告,可使用下面的宏:
[cpp] view plaincopyprint?
DECLARE_PER_CPU(type, name);
2. 操作每个CPU的变量和指针:
get_cpu_var(name); //返回当前处理器上的指定变量name的值, 同时将他禁止抢占;
put_cpu_var(name); //与get_cpu_var(name)相对应,重新激活抢占;
[cpp] view plaincopyprint?
/*
* Must be an lvalue. Since @var must be a simple identifier,
* we force a syntax error here if it isn't.
*/
#define get_cpu_var(var) (*({ \
preempt_disable(); \
&__get_cpu_var(var); }))
/*
* The weird & is necessary because sparse considers (void)(var) to be
* a direct dereference of percpu variable (var).
*/
#define put_cpu_var(var) do { \
(void)&(var); \
preempt_enable(); \
} while (0)
通过指针来操作每个CPU的数据:
get_cpu_ptr(var); --- 返回一个void类型的指针,指向CPU ptr处的数据
put_cpu_ptr(var); --- 操作完成后,重新激活内核抢占。
[cpp] view plaincopyprint?
#define get_cpu_ptr(var) ({ \
preempt_disable(); \
this_cpu_ptr(var); })
#define put_cpu_ptr(var) do { \
(void)(var); \
preempt_enable(); \
} while (0)
3. 获得别的处理器上的name变量的值
per_cpu(name, cpu) ; //返回别的处理器cpu上变量name的值;
4. 给系统中每个处理器分配一个指定类型的对象: alloc_percpu();
[cpp] view plaincopyprint?
#define alloc_percpu(type) \
(typeof(type) __percpu *)__alloc_percpu(sizeof(type), __alignof__(type))
/**
* __alloc_percpu - allocate dynamic percpu area
* @size: size of area to allocate in bytes
* @align: alignment of area (max PAGE_SIZE)
*
* Allocate zero-filled percpu area of @size bytes aligned at @align.
* Might sleep. Might trigger writeouts.
*
* CONTEXT:
* Does GFP_KERNEL allocation.
*
* RETURNS:
* Percpu pointer to the allocated area on success, NULL on failure.
*/
void __percpu *__alloc_percpu(size_t size, size_t align)
{
return pcpu_alloc(size, align, false);
}
EXPORT_SYMBOL_GPL(__alloc_percpu);
参数为type, 就是指定的需要分配的类型,通过类型,可以得出__alloc_percpu()的两个参数:
size = sizeof(type);
align = __alignof__(type);
__alignof__()是gcc的一个功能,它会返回指定类型或lvalue所需的对齐字节数。
相应的释放所有处理器上指定的每个CPU数据:free_percpu();
[cpp] view plaincopyprint?
/**
* free_percpu - free percpu area
* @ptr: pointer to area to free
*
* Free percpu area @ptr.
*
* CONTEXT:
* Can be called from atomic context.
*/
void free_percpu(void __percpu *ptr)
{
void *addr;
struct pcpu_chunk *chunk;
unsigned long flags;
int off;
if (!ptr)
return;
addr = __pcpu_ptr_to_addr(ptr);
spin_lock_irqsave(&pcpu_lock, flags);
chunk = pcpu_chunk_addr_search(addr);
off = addr - chunk->base_addr;
pcpu_free_area(chunk, off);
/* if there are more than one fully free chunks, wake up grim reaper */
if (chunk->free_size == pcpu_unit_size) {
struct pcpu_chunk *pos;
list_for_each_entry(pos, &pcpu_slot[pcpu_nr_slots - 1], list)
if (pos != chunk) {
schedule_work(&pcpu_reclaim_work);
break;
}
}
spin_unlock_irqrestore(&pcpu_lock, flags);
}
EXPORT_SYMBOL_GPL(free_percpu);
5. get_cpu()/put_cpu() --- 获得处理器编号
get_cpu() 在返回当前处理器编号之前,先回关闭内核抢占。
put_cpu() 重新打开内核抢占。
函数定义如下:
[cpp] view plaincopyprint?
#define get_cpu() ({ preempt_disable(); smp_processor_id(); })
#define put_cpu() preempt_enable()
使用案例如下:
[cpp] view plaincopyprint?
int cpu;
cpu = get_cpu(); //禁止抢占内核,并将CPU设置为当前处理器。
//对每个处理器的数据进行操作,
put_cpu(); //使能内核抢占