Linuxカーネル同期原語のper-cpu変数
同じデータへの同時アクセスを回避することを同期と呼びます(通常、割り込み、対称マルチプロセッサ、カーネルプリエンプトなどによって引き起こされます).
——題記
カーネルソース:linux-2.6.38.8.tar.bz2
ターゲットプラットフォーム:ARMアーキテクチャ
per-cpu変数を作成すると、システム内の各プロセッサは変数の独自のコピーを持っています.各プロセッサは独自のレプリカで動作するため、per-cpu変数へのアクセスにロックはほとんど必要ありません.
per-cpu変数は、異なるプロセッサからの同時アクセスのみを保護し、非同期関数(割り込みプロセッサおよび遅延可能関数)からのアクセス、およびカーネルプリエンプトは保護しないため、これらの場合には追加の同期原語が必要である.
1、静的作成
(1)、定義
DEFINE_の使用PER_CPUマクロは、per-cpu変数を静的に作成する.
ただし、_PCPU_ATTRSマクロの定義は次のとおりです.
PER_CPU_DEF_ATTRIBUTESマクロの定義は空です.
CONFIGでSMPが構成されていない場合、DEFINE_PER_CPUマクロ展開後の様子は以下の通りです.
__typeof__typeofと等価で、typeを取得するためのデータ型(typeofの使用方法は、博文「GNU Cのtypeofの例を解く」を参照).
__attribute__(noderef,address_space(3))プロパティはsparseプログラムに使用されます.
__attribute__((section(".data"))属性は、定義変数を実行可能プログラムに格納ことを表す.dataセグメント.
文全体の核心的な意味は、データ型がtypeで、名前がnameの変数を定義することです.
(2)、使用
1)、get_を呼び出すcpu_var関数は、現在のプロセッサ上のper-cpu変数を取得し、カーネルプリエンプトを禁止します.
a、シングルプロセッサバージョンの定義
__get_cpu_varのコアコードは以下に等しい.
b、マルチプロセッサバージョンの定義
ただし、_my_cpu_offsetの定義は次のとおりです.
2)、put_を呼び出すcpu_var関数はカーネルプリエンプトを再アクティブにします.
3)、per_を呼び出すcpu関数は、指定したプロセッサ上のper-cpu変数を取得します.
注意、ここのper_cpu関数はカーネルプリエンプトを禁止したり、他の形式のロック保護を提供したりしていません.
2、動的作成
(1)、分配と釈放
呼び出しalloc_percpuマクロはtypeデータ型のper-cpu変数を動的に作成し、そのアドレスを返します.
呼び出しfree_percpu関数を使用して、per-cpu変数を解放します.
(2)、使用
——題記
カーネルソース:linux-2.6.38.8.tar.bz2
ターゲットプラットフォーム:ARMアーキテクチャ
per-cpu変数を作成すると、システム内の各プロセッサは変数の独自のコピーを持っています.各プロセッサは独自のレプリカで動作するため、per-cpu変数へのアクセスにロックはほとんど必要ありません.
per-cpu変数は、異なるプロセッサからの同時アクセスのみを保護し、非同期関数(割り込みプロセッサおよび遅延可能関数)からのアクセス、およびカーネルプリエンプトは保護しないため、これらの場合には追加の同期原語が必要である.
1、静的作成
(1)、定義
DEFINE_の使用PER_CPUマクロは、per-cpu変数を静的に作成する.
/* linux-2.6.38.8/include/linux/percpu-defs.h */
#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
ただし、_PCPU_ATTRSマクロの定義は次のとおりです.
/* linux-2.6.38.8/include/linux/percpu-defs.h */
#define __PCPU_ATTRS(sec) \
__percpu __attribute__((section(PER_CPU_BASE_SECTION sec))) \
PER_CPU_ATTRIBUTES
/* linux-2.6.38.8/include/linux/compiler.h */
# define __percpu __attribute__((noderef, address_space(3)))
/* linux-2.6.38.8/include/asm-generic/percpu.h */
#ifndef PER_CPU_BASE_SECTION
#ifdef CONFIG_SMP
#define PER_CPU_BASE_SECTION ".data..percpu"
#else
#define PER_CPU_BASE_SECTION ".data"
#endif
#endif
#ifndef PER_CPU_ATTRIBUTES
#define PER_CPU_ATTRIBUTES
#endif
PER_CPU_DEF_ATTRIBUTESマクロの定義は空です.
/* linux-2.6.38.8/include/asm-generic/percpu.h */
#ifndef PER_CPU_DEF_ATTRIBUTES
#define PER_CPU_DEF_ATTRIBUTES
#endif
CONFIGでSMPが構成されていない場合、DEFINE_PER_CPUマクロ展開後の様子は以下の通りです.
__attribute__((noderef, address_space(3))) __attribute__((section(".data"))) __typeof__(type) name;
__typeof__typeofと等価で、typeを取得するためのデータ型(typeofの使用方法は、博文「GNU Cのtypeofの例を解く」を参照).
__attribute__(noderef,address_space(3))プロパティはsparseプログラムに使用されます.
__attribute__((section(".data"))属性は、定義変数を実行可能プログラムに格納ことを表す.dataセグメント.
文全体の核心的な意味は、データ型がtypeで、名前がnameの変数を定義することです.
(2)、使用
1)、get_を呼び出すcpu_var関数は、現在のプロセッサ上のper-cpu変数を取得し、カーネルプリエンプトを禁止します.
/* linux-2.6.38.8/include/linux/percpu.h */
#define get_cpu_var(var) (*({ \
preempt_disable(); /* */ \
&__get_cpu_var(var); }))
a、シングルプロセッサバージョンの定義
/* linux-2.6.38.8/include/asm-generic/percpu.h */
#define __get_cpu_var(var) (*VERIFY_PERCPU_PTR(&(var)))
#define VERIFY_PERCPU_PTR(__p) ({ \
__verify_pcpu_ptr((__p)); \
(typeof(*(__p)) __kernel __force *)(__p); \
})
/* linux-2.6.38.8/include/linux/percpu-defs.h */
#define __verify_pcpu_ptr(ptr) do { \
const void __percpu *__vpp_verify = (typeof(ptr))NULL; \
(void)__vpp_verify; \
} while (0)
__get_cpu_varのコアコードは以下に等しい.
*(typeof(*(&(var))) *)(&(var));
b、マルチプロセッサバージョンの定義
/* linux-2.6.38.8/include/asm-generic/percpu.h */
#define __get_cpu_var(var) (*this_cpu_ptr(&(var)))
#ifdef CONFIG_DEBUG_PREEMPT
#define this_cpu_ptr(ptr) SHIFT_PERCPU_PTR(ptr, my_cpu_offset)
#else
#define this_cpu_ptr(ptr) __this_cpu_ptr(ptr)
#endif
#ifndef __this_cpu_ptr
#define __this_cpu_ptr(ptr) SHIFT_PERCPU_PTR(ptr, __my_cpu_offset)
#endif
#ifndef SHIFT_PERCPU_PTR
/* Weird cast keeps both GCC and sparse happy. */
#define SHIFT_PERCPU_PTR(__p, __offset) ({ \
__verify_pcpu_ptr((__p)); \
RELOC_HIDE((typeof(*(__p)) __kernel __force *)(__p), (__offset)); \
})
#endif
/* linux-2.6.38.8/include/linux/compiler-gcc.h */
#define RELOC_HIDE(ptr, off) \
({ unsigned long __ptr; \
__asm__ ("" : "=r"(__ptr) : "0"(ptr)); /* ptr __ptr */ \
(typeof(ptr)) (__ptr + (off)); })
ただし、_my_cpu_offsetの定義は次のとおりです.
/* linux-2.6.38.8/include/asm-generic/percpu.h */
#ifndef __my_cpu_offset
#define __my_cpu_offset per_cpu_offset(raw_smp_processor_id())
#endif
#define per_cpu_offset(x) (__per_cpu_offset[x])
/* linux-2.6.38.8/mm/percpu.c */
unsigned long __per_cpu_offset[NR_CPUS] __read_mostly; //NR_CPUS
/* linux-2.6.38.8/arch/arm/include/asm/smp.h */
#define raw_smp_processor_id() (current_thread_info()->cpu) // (0,1,...,NR_CPUS-1)
2)、put_を呼び出すcpu_var関数はカーネルプリエンプトを再アクティブにします.
#define put_cpu_var(var) do { \
(void)&(var); \
preempt_enable(); \
} while (0)
3)、per_を呼び出すcpu関数は、指定したプロセッサ上のper-cpu変数を取得します.
/* linux-2.6.38.8/include/asm-generic/percpu.h */
#ifdef CONFIG_SMP
#define per_cpu(var, cpu) \
(*SHIFT_PERCPU_PTR(&(var), per_cpu_offset(cpu)))
#else
#define per_cpu(var, cpu) (*((void)(cpu), VERIFY_PERCPU_PTR(&(var))))
#endif
注意、ここのper_cpu関数はカーネルプリエンプトを禁止したり、他の形式のロック保護を提供したりしていません.
2、動的作成
(1)、分配と釈放
呼び出しalloc_percpuマクロはtypeデータ型のper-cpu変数を動的に作成し、そのアドレスを返します.
/* linux-2.6.38.8/include/linux/percpu.h */
#define alloc_percpu(type) \
(typeof(type) __percpu *)__alloc_percpu(sizeof(type), __alignof__(type))
/* linux-2.6.38.8/mm/percpu.c */
void __percpu *__alloc_percpu(size_t size, size_t align)
{
return pcpu_alloc(size, align, false);
}
呼び出しfree_percpu関数を使用して、per-cpu変数を解放します.
/* linux-2.6.38.8/mm/percpu.c */
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);
}
(2)、使用
/* linux-2.6.38.8/include/linux/percpu.h */
#define get_cpu_ptr(var) ({ \
preempt_disable(); \
this_cpu_ptr(var); })
#define put_cpu_ptr(var) do { \
(void)(var); \
preempt_enable(); \
} while (0)
#ifdef CONFIG_SMP
#define per_cpu_ptr(ptr, cpu) SHIFT_PERCPU_PTR((ptr), per_cpu_offset((cpu)))
#else
#define per_cpu_ptr(ptr, cpu) ({ (void)(cpu); VERIFY_PERCPU_PTR((ptr)); })
#endif