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


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tanglinux

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值