Chinaunix首页 | 论坛 | 博客
  • 博客访问: 545831
  • 博文数量: 469
  • 博客积分: 50
  • 博客等级: 民兵
  • 技术积分: 1495
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-15 21:04
文章分类

全部博文(469)

文章存档

2015年(81)

2014年(125)

2013年(261)

2012年(2)

分类: LINUX

2014-02-11 16:51:44

避免对同一数据的并发访问(通常由中断、对称多处理器、内核抢占等引起)称为同步。 

——题记

    内核源码:linux-2.6.38.8.tar.bz2

    目标平台:ARM体系结构

 

    当创建一个per-cpu变量时,系统中的每一个处理器都会拥有该变量的独有副本。由于每个处理器都是在自己的副本上工作,所以对per-cpu变量的访问几乎不需要加锁。

    per-cpu变量只为来自不同处理器的并发访问提供保护,对来自异步函数(中断处理程序和可延迟函数)的访问,以及内核抢占并不提供保护,因此在这些情况下还需要另外的同步原语。

    1、静态创建

    (1)、定义

    使用DEFINE_PER_CPU宏静态地创建per-cpu变量。 

  1. /* linux-2.6.38.8/include/linux/percpu-defs.h */  
  2. #define DEFINE_PER_CPU(type, name)                  \  
  3.     DEFINE_PER_CPU_SECTION(type, name, "")  
  4.   
  5. #define DEFINE_PER_CPU_SECTION(type, name, sec)             \  
  6.     __PCPU_ATTRS(sec) PER_CPU_DEF_ATTRIBUTES            \  
  7.     __typeof__(type) name  
/* 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宏的定义如下: 

  1. /* linux-2.6.38.8/include/linux/percpu-defs.h */  
  2. #define __PCPU_ATTRS(sec)                       \  
  3.     __percpu __attribute__((section(PER_CPU_BASE_SECTION sec))) \  
  4.     PER_CPU_ATTRIBUTES  
  5.   
  6. /* linux-2.6.38.8/include/linux/compiler.h */  
  7. # define __percpu   __attribute__((noderef, address_space(3)))  
  8. /* linux-2.6.38.8/include/asm-generic/percpu.h */  
  9. #ifndef PER_CPU_BASE_SECTION  
  10. #ifdef CONFIG_SMP  
  11. #define PER_CPU_BASE_SECTION ".data..percpu"  
  12. #else  
  13. #define PER_CPU_BASE_SECTION ".data"  
  14. #endif  
  15. #endif  
  16.   
  17. #ifndef PER_CPU_ATTRIBUTES  
  18. #define PER_CPU_ATTRIBUTES  
  19. #endif  
/* 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宏的定义为空: 

  1. /* linux-2.6.38.8/include/asm-generic/percpu.h */  
  2. #ifndef PER_CPU_DEF_ATTRIBUTES  
  3. #define PER_CPU_DEF_ATTRIBUTES  
  4. #endif  
/* 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宏展开后的样子如下所示: 

  1. __attribute__((noderef, address_space(3))) __attribute__((section(".data"))) __typeof__(type) name;  
__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变量,并且禁止内核抢占。 

  1. /* linux-2.6.38.8/include/linux/percpu.h */  
  2. #define get_cpu_var(var) (*({               \  
  3.     preempt_disable();   /* 禁止内核抢占 */       \  
  4.     &__get_cpu_var(var); }))  
/* linux-2.6.38.8/include/linux/percpu.h */ #define get_cpu_var(var) (*({ \ preempt_disable(); /* 禁止内核抢占 */ \ &__get_cpu_var(var); }))

    a、单处理器版本的定义 

  1. /* linux-2.6.38.8/include/asm-generic/percpu.h */  
  2. #define __get_cpu_var(var)  (*VERIFY_PERCPU_PTR(&(var)))  
  3.   
  4. #define VERIFY_PERCPU_PTR(__p) ({           \  
  5.     __verify_pcpu_ptr((__p));           \  
  6.     (typeof(*(__p)) __kernel __force *)(__p);   \  
  7. })  
  8.   
  9. /* linux-2.6.38.8/include/linux/percpu-defs.h */  
  10. #define __verify_pcpu_ptr(ptr)  do {                    \  
  11.     const void __percpu *__vpp_verify = (typeof(ptr))NULL;      \  
  12.     (void)__vpp_verify;                     \  
  13. while (0)  
/* 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的核心代码等价于: 

  1. *(typeof(*(&(var))) *)(&(var));  
*(typeof(*(&(var))) *)(&(var));

    b、多处理器版本的定义 

  1. /* linux-2.6.38.8/include/asm-generic/percpu.h */  
  2. #define __get_cpu_var(var) (*this_cpu_ptr(&(var)))  
  3.   
  4. #ifdef CONFIG_DEBUG_PREEMPT  
  5. #define this_cpu_ptr(ptr) SHIFT_PERCPU_PTR(ptr, my_cpu_offset)  
  6. #else  
  7. #define this_cpu_ptr(ptr) __this_cpu_ptr(ptr)  
  8. #endif  
  9.   
  10. #ifndef __this_cpu_ptr  
  11. #define __this_cpu_ptr(ptr) SHIFT_PERCPU_PTR(ptr, __my_cpu_offset)  
  12. #endif  
  13.   
  14. #ifndef SHIFT_PERCPU_PTR  
  15. /* Weird cast keeps both GCC and sparse happy. */  
  16. #define SHIFT_PERCPU_PTR(__p, __offset) ({              \  
  17.     __verify_pcpu_ptr((__p));                   \  
  18.     RELOC_HIDE((typeof(*(__p)) __kernel __force *)(__p), (__offset)); \  
  19. })  
  20. #endif  
  21.   
  22. /* linux-2.6.38.8/include/linux/compiler-gcc.h */  
  23. #define RELOC_HIDE(ptr, off)                    \  
  24.   ({ unsigned long __ptr;                   \  
  25.     __asm__ ("" : "=r"(__ptr) : "0"(ptr)); /* 把ptr的值赋给__ptr */  \  
  26.     (typeof(ptr)) (__ptr + (off)); })  
/* 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的定义如下: 

  1. /* linux-2.6.38.8/include/asm-generic/percpu.h */  
  2. #ifndef __my_cpu_offset  
  3. #define __my_cpu_offset per_cpu_offset(raw_smp_processor_id())  
  4. #endif  
  5.   
  6. #define per_cpu_offset(x) (__per_cpu_offset[x])  
  7.   
  8. /* linux-2.6.38.8/mm/percpu.c */  
  9. unsigned long __per_cpu_offset[NR_CPUS] __read_mostly;  //NR_CPUS为系统中处理器的数量  
  10.   
  11. /* linux-2.6.38.8/arch/arm/include/asm/smp.h */  
  12. #define raw_smp_processor_id() (current_thread_info()->cpu) //获取当前处理器的编号(0,1,...,NR_CPUS-1)  
/* 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函数重新激活内核抢占。 

  1. #define put_cpu_var(var) do {               \  
  2.     (void)&(var);                   \  
  3.     preempt_enable();               \  
  4. while (0)  
#define put_cpu_var(var) do { \ (void)&(var); \ preempt_enable(); \ } while (0)

    3)、调用per_cpu函数获取指定处理器上的per-cpu变量。 

  1. /* linux-2.6.38.8/include/asm-generic/percpu.h */  
  2. #ifdef CONFIG_SMP  
  3. #define per_cpu(var, cpu) \  
  4.     (*SHIFT_PERCPU_PTR(&(var), per_cpu_offset(cpu)))  
  5. #else  
  6. #define per_cpu(var, cpu)   (*((void)(cpu), VERIFY_PERCPU_PTR(&(var))))  
  7. #endif  
/* 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变量,并返回它的地址。 

  1. /* linux-2.6.38.8/include/linux/percpu.h */  
  2. #define alloc_percpu(type)  \  
  3.     (typeof(type) __percpu *)__alloc_percpu(sizeof(type), __alignof__(type))  
  4.   
  5. /* linux-2.6.38.8/mm/percpu.c */  
  6. void __percpu *__alloc_percpu(size_t size, size_t align)  
  7. {  
  8.     return pcpu_alloc(size, align, false);  
  9. }  
/* 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变量。 

  1. /* linux-2.6.38.8/mm/percpu.c */  
  2. void free_percpu(void __percpu *ptr)  
  3. {  
  4.     void *addr;  
  5.     struct pcpu_chunk *chunk;  
  6.     unsigned long flags;  
  7.     int off;  
  8.   
  9.     if (!ptr)  
  10.         return;  
  11.   
  12.     addr = __pcpu_ptr_to_addr(ptr);  
  13.   
  14.     spin_lock_irqsave(&pcpu_lock, flags);  
  15.   
  16.     chunk = pcpu_chunk_addr_search(addr);  
  17.     off = addr - chunk->base_addr;  
  18.   
  19.     pcpu_free_area(chunk, off);  
  20.   
  21.     /* if there are more than one fully free chunks, wake up grim reaper */  
  22.     if (chunk->free_size == pcpu_unit_size) {  
  23.         struct pcpu_chunk *pos;  
  24.   
  25.         list_for_each_entry(pos, &pcpu_slot[pcpu_nr_slots - 1], list)  
  26.             if (pos != chunk) {  
  27.                 schedule_work(&pcpu_reclaim_work);  
  28.                 break;  
  29.             }  
  30.     }  
  31.   
  32.     spin_unlock_irqrestore(&pcpu_lock, flags);  
  33. }  
/* 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)、使用 

  1. /* linux-2.6.38.8/include/linux/percpu.h */  
  2. #define get_cpu_ptr(var) ({             \  
  3.     preempt_disable();              \  
  4.     this_cpu_ptr(var); })  
  5.   
  6.   
  7. #define put_cpu_ptr(var) do {               \  
  8.     (void)(var);                    \  
  9.     preempt_enable();               \  
  10. while (0)  
  11.   
  12. #ifdef CONFIG_SMP  
  13. #define per_cpu_ptr(ptr, cpu)   SHIFT_PERCPU_PTR((ptr), per_cpu_offset((cpu)))  
  14. #else  
  15. #define per_cpu_ptr(ptr, cpu)   ({ (void)(cpu); VERIFY_PERCPU_PTR((ptr)); })  
  16. #endif 
阅读(831) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~