Linuxカーネル同期原語のper-cpu変数


同じデータへの同時アクセスを回避することを同期と呼びます(通常、割り込み、対称マルチプロセッサ、カーネルプリエンプトなどによって引き起こされます). 
——題記
カーネルソース: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