per-CPU Parameters
Table of Contents
Overview
per-cpu变量,系统中的每一个处理器都会拥有该变量的独有副本.所以对它的访问几乎不需要锁.
内核是抢占式的,所以在访问per-CPU变量时,应使用特定的API来避免抢占,即避免它被切换到另一个CPU上被处理。
主要定义于 <linux/percpu.h>
.
静态创建
定义和声明每个CPU变量:
DEFINE_PER_CPU(type, name);
这语句为系统的每个CPU都创建了一个类型为type,名字为name的变量。如果需要在别处声明此变量,以防编译时的警告,可使用下面的宏:
#define DEFINE_PER_CPU(type, name) \ __attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name
从上面的代码我们可以看出,手工定义的所有per-cpu变量都是放在
.data.percpu
段的。注意上面的宏只是在SMP体系结构下才如此定义。如果不是SMP结构的计算机那么只是简单的把所有的per-cpu变量放到全局变量应该放到的地方。
在系统初始化期间, start_kernel()
函数中调用 setup_per_cpu_areas()
函数,用于为每个cpu的per-cpu变量副本分配空间,
void __init setup_per_cpu_areas(void) { unsigned long delta; unsigned int cpu; int rc; /* * Always reserve area for module percpu variables. That's * what the legacy allocator did. */ rc = pcpu_embed_first_chunk(PERCPU_MODULE_RESERVE, PERCPU_DYNAMIC_RESERVE, PAGE_SIZE, NULL, pcpu_dfl_fc_alloc, pcpu_dfl_fc_free); if (rc < 0) panic("Failed to initialize percpu areas."); delta = (unsigned long)pcpu_base_addr - (unsigned long)__per_cpu_start; for_each_possible_cpu(cpu) __per_cpu_offset[cpu] = delta + pcpu_unit_offsets[cpu]; }
上述函数,在分配好每个cpu的per-cpu变量副本所占用的物理空间的同时,也对 __per_cpu_offset[NR_CPUS]
数组进行了初始化用于以后找到指定CPU的这些
per-cpu变量副本。
DECLARE_PER_CPU(type, name);
操作每个CPU的变量和指针
get_cpu_var(name);
//返回当前处理器上的指定变量name的值, 同时将他禁止抢占;put_cpu_var(name);
//与getcpuvar(name)相对应,重新激活抢占;
/* * 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);
— 操作完成后,重新激活内核抢占。
#define get_cpu_ptr(var) ({ \ preempt_disable(); \ this_cpu_ptr(var); }) #define put_cpu_ptr(var) do { \ (void)(var); \ preempt_enable(); \ } while (0)
获得别的处理器上的name变量的值
per_cpu(name, cpu);
//返回别的处理器cpu上变量name的值;
per-CPU变量导出,供模块使用
EXPORT_PER_CPU_SYMBOL(per_cpu_var); EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var);
动态创建
- 给系统中每个处理器分配一个指定类型的对象:
alloc_percpu(type)
参数为type, 就是指定的需要分配的类型,通过类型,可以得出
__alloc_percpu()
的两个参数:
size = sizeof(type);
align = __alignof__(type);
__alignof__()
是gcc的一个功能,它会返回指定类型或lvalue所需的对齐字节数。
#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);
- 相应的释放所有处理器上指定的每个CPU数据:freepercpu();
/** * 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);
Misc
get_cpu()/put_cpu()
— 获得处理器编号
get_cpu()
在返回当前处理器编号之前,先回关闭内核抢占。put_cpu()
重新打开内核抢占。
#define get_cpu() ({ preempt_disable(); smp_processor_id(); }) #define put_cpu() preempt_enable()