linux内核的数据结构:3 每CPU变量
来源:互联网 发布:串口数据帧格式 编辑:程序博客网 时间:2024/05/22 03:28
本文采用linux 3.04内核版本。
多核情况下,CPU是同时并发运行的,但是多它们共同使用其他的硬件资源的,因此我们需要解决多个CPU之间的同步问题。每CPU变量(per-cpu-variable)是内核中一种重要的同步机制。顾名思义,每CPU变量就是为每个CPU构造一个变量的副本,这样多个CPU相互操作各自的副本,互不干涉。比如我们标识当前进程的变量current_task就被声明为每CPU变量。
每CPU变量的特点:
- 用于多个CPU之间的同步,如果是单核结构,每CPU变量没有任何用处。
- 每CPU变量不能用于多个CPU相互协作的场景。(每个CPU的副本都是独立的)
- 每CPU变量不能解决由中断或延迟函数导致的同步问题
- 访问每CPU变量的时候,一定要确保关闭进程抢占,否则一个进程被抢占后可能会更换CPU运行,这会导致每CPU变量的引用错误。
显然,每CPU变量的实现不会这么简单。理由:我们知道为了加快内存访问,处理器中设计了硬件高速缓存(也就是CPU的cache),每个处理器都会有一个硬件高速缓存。如果每CPU变量用数组来实现,那么任何一个CPU修改了其中的内容,都会导致其他CPU的高速缓存中对应的块失效。而频繁的失效会导致性能急剧的下降。
每CPU变量分为静态和动态两种,静态的每CPU变量使用DEFINE_PER_CPU声明,在编译的时候分配空间;而动态的使用alloc_percpu和free_percpu来分配回收存储空间。下面我们来看看Linux中的具体实现:
每CPU变量的函数和宏
- DECLARE_PER_CPU(type, name)声明每CPU变量name,类型为type
- DEFINE_PER_CPU(type, name)定义每CPU变量name,类型为type
- alloc_percpu(type)动态为type类型的每CPU变量分配空间,并返回它的地址
- free_percpu(pointer)释放为动态分配的每CPU变量的空间,pointer是起始地址
- per_cpu(var, cpu)获取编号cpu的处理器上面的变量var的副本
- get_cpu_var(var)获取本处理器上面的变量var的副本,该函数关闭进程抢占,主要由__get_cpu_var来完成具体的访问
- get_cpu_ptr(var) 获取本处理器上面的变量var的副本的指针,该函数关闭进程抢占,主要由__get_cpu_var来完成具体的访问
- put_cpu_var(var) & put_cpu_ptr(var)表示每CPU变量的访问结束,恢复进程抢占
- __get_cpu_var(var) 获取本处理器上面的变量var的副本,该函数不关闭进程抢占
每CPU变量的实现原理
静态的每CPU变量
#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
#define __PCPU_ATTRS(sec)\__percpu __attribute__((section(PER_CPU_BASE_SECTION sec)))\PER_CPU_ATTRIBUTES
#define PER_CPU_BASE_SECTION ".data..percpu"
__attribute__((section(PER_CPU_BASE_SECTION sec)备注:每CPU变量的声明和普通变量的声明一样,主要的区别是使用了__attribute__((section(PER_CPU_BASE_SECTION sec)))来指定该变量被放置的段中,普通变量默认会被放置data段或者bss段中。
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];}
备注:分配内存以及复制.data.percup内容的工作由pcpu_embed_first_chunk来完成,这里就不展开了。__per_cpu_offset数组中记录了每个CPU的percpu区域的开始地址。我们访问每CPU变量就要依靠__per_cpu_offset中的地址。
动态每CPU变量
了解了静态的每CPU变量的实现机制后,就很容易想到动态的每CPU变量的实现方法了。实际上,在setup_per_cpu_areas的时候,我们会为每个CPU都多申请一部分空间留作动态分配每CPU变量之用(一个场景就是内核模块中的每CPU变量)。相对于静态的每CPU变量,我们需要额外管理内存的分配和回收。
每CPU变量的访问
#define per_cpu(var, cpu) \(*SHIFT_PERCPU_PTR(&(var), per_cpu_offset(cpu)))其中per_cpu_offset是获取编号为cpu的处理器上的每CPU区域的地址,实际上就是数组__per_cpu_offset中对应的项。具体实现如下:
#define per_cpu_offset(x) (__per_cpu_offset[x])
#define SHIFT_PERCPU_PTR(__p, __offset)({\__verify_pcpu_ptr((__p));\RELOC_HIDE((typeof(*(__p)) __kernel __force *)(__p), (__offset)); \})#define __verify_pcpu_ptr(ptr)do {\const void __percpu *__vpp_verify = (typeof(ptr))NULL;\(void)__vpp_verify;\} while (0)# define RELOC_HIDE(ptr, off)\ ({ unsigned long __ptr;\ __ptr = (unsigned long) (ptr);\ (typeof(ptr)) (__ptr + (off)); })
备注:__verify_pcpu是为了验证var是否是一个每CPU变量(如果不是,会再编译的时候报错)。实际上的存取简化后相当于*(var的地址(即相对偏移)+__per_cpu_offset)。
- linux内核的数据结构:3 每CPU变量
- linux内核的数据结构:3 每CPU变量
- linux内核中的每cpu变量
- linux内核中的每cpu变量
- linux内核中的每cpu变量
- linux:cpu 每-CPU 的变量
- linux:cpu 每-CPU 的变量
- linux:每CPU变量
- 每-CPU 的变量
- 每-CPU 的变量
- 每-CPU 的变量
- linux内核同步之每CPU变量、原子操作、内存屏障、自旋锁
- linux内核同步之每CPU变量、原子操作、内存屏障、自旋锁
- 每CPU变量
- 每CPU变量
- 每-CPU变量
- 每CPU变量
- 每CPU变量
- jQuery(第2章案例 动态品牌列表效果)2章完
- HDU 1032 The 3n + 1 problem
- 成为PHP编程高手的四条指南
- Thirft框架介绍
- C#中的自定义控件
- linux内核的数据结构:3 每CPU变量
- linux下MySQL的安装方法
- iphone开发-UIWebView 的使用
- (转)如何防止代码腐烂
- IE和Firefox的显示效果不一样,都是padding惹的祸
- 修改Oracle 11g 内存
- initialization of 'XXX' is skipped by 'case' label 原因及解决办法
- Yum 笔记
- php利用syslog函数分布式将log集中到中央log服务器