Linux 2.6 kernel 中的 percpu 变量是经常用到的东西,因为现在很多计算机都已经支持多处理器了,而且 kernel 默认都会被编译成 SMP 的,相对于原来多个处理器共享数据并进行处理的方式,用 percpu 变量在 SMP、NUMA 等架构下可以提高性能,而且很多情况下必须用 percpu 来对不同的处理器做出数据区分。

本文以 kernel 中的 softirq 为例简单说下 percpu 变量,我们先来看看 kernel 中唤醒 ksoftirqd 的实现,ksoftirqd 在 ps 命令看到的进程列表中很容易找到,是每个处理器都有一个(如果有 4 个处理器,则有 4 个 kernel 线程名称分别从 ksoftirqd/0 到 ksoftirqd/3),关于 softirq 本身的实现不在本文讨论范围内,唤醒 ksoftirqd 的实现在 kernel/softirq.c 文件中:

static DEFINE_PER_CPU(struct task_struct *, ksoftirqd);

void wakeup_softirqd(void)

{

/* Interrupts are disabled: no need to stop preemption */

struct task_struct *tsk = __get_cpu_var(ksoftirqd);

if (tsk && tsk->state != TASK_RUNNING)

wake_up_process(tsk);

}

这里就用到了 percpu 变量 ksoftirqd,它是通过 DEFINE_PER_CPU 宏来进程定义的 percpu task_struct 列表,通过 __get_cpu_var 宏来得到相应处理器的 ksoftirqd/n 的 task_struct,然后调用 wake_up_process 函数唤醒进程(也就是 ksoftirqd/n kernel 线程),关于 wake_up_process 等进程调度的相关实现在之前的日志中有介绍的,请参考 [这里]。

__get_cpu_var、DEFINE_PER_CPU 等 percpu 宏的实现在 include/linux/percpu.h、include/asm-generic/percpu.h 等头文件中。先看看 include/asm-generic/percpu.h 中的一些定义:

#ifdef CONFIG_SMP

/*

* per_cpu_offset() is the offset that has to be added to a

* percpu variable to get to the instance for a certain processor.

*

* Most arches use the __per_cpu_offset array for those offsets but

* some arches have their own ways of determining the offset (x86_64, s390).

*/

#ifndef __per_cpu_offset

extern unsigned long __per_cpu_offset[NR_CPUS];

#define per_cpu_offset(x) (__per_cpu_offset[x])

#endif

/*

* Determine the offset for the currently active processor.

* An arch may define __my_cpu_offset to provide a more effective

* means of obtaining the offset to the per cpu variables of the

* current processor.

*/

#ifndef __my_cpu_offset

#define __my_cpu_offset per_cpu_offset(raw_smp_processor_id())

#endif

#ifdef CONFIG_DEBUG_PREEMPT

#define my_cpu_offset per_cpu_offset(smp_processor_id())

#else

#define my_cpu_offset __my_cpu_offset

#endif

/*

* Add a offset to a pointer but keep the pointer as is.

*

* Only S390 provides its own means of moving the pointer.

*/

#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

/*

* A percpu variable may point to a discarded regions. The following are

* established ways to produce a usable pointer from the percpu variable

* offset.

*/

#define per_cpu(var, cpu) \

(*SHIFT_PERCPU_PTR(&(var), per_cpu_offset(cpu)))

#define __get_cpu_var(var) \

(*SHIFT_PERCPU_PTR(&(var), my_cpu_offset))

#define __raw_get_cpu_var(var) \

(*SHIFT_PERCPU_PTR(&(var), __my_cpu_offset))

#define this_cpu_ptr(ptr) SHIFT_PERCPU_PTR(ptr, my_cpu_offset)

#define __this_cpu_ptr(ptr) SHIFT_PERCPU_PTR(ptr, __my_cpu_offset)

#ifdef CONFIG_HAVE_SETUP_PER_CPU_AREA

extern void setup_per_cpu_areas(void);

#endif

#else /* ! SMP */

#define per_cpu(var, cpu)(*((void)(cpu), &(var)))

#define __get_cpu_var(var)(var)

#define __raw_get_cpu_var(var)(var)

#define this_cpu_ptr(ptr) per_cpu_ptr(ptr, 0)

#define __this_cpu_ptr(ptr) this_cpu_ptr(ptr)

#endif/* SMP */

#ifndef PER_CPU_BASE_SECTION

#ifdef CONFIG_SMP

#define PER_CPU_BASE_SECTION ".data.percpu"

#else

#define PER_CPU_BASE_SECTION ".data"

#endif

#endif

#ifdef CONFIG_SMP

#ifdef MODULE

#define PER_CPU_SHARED_ALIGNED_SECTION ""

#define PER_CPU_ALIGNED_SECTION ""

#else

#define PER_CPU_SHARED_ALIGNED_SECTION ".shared_aligned"

#define PER_CPU_ALIGNED_SECTION ".shared_aligned"

#endif

#define PER_CPU_FIRST_SECTION ".first"

#else

#define PER_CPU_SHARED_ALIGNED_SECTION ""

#define PER_CPU_ALIGNED_SECTION ".shared_aligned"

#define PER_CPU_FIRST_SECTION ""

#endif

通常所有的 percpu 变量是一起存放在特定的 section 里的,像上面头文件中的 .data.percpu 基础 section( 当然非 SMP 系统下就是 .data 了)、.shared_aligned、.first section。使用 objdump 可以看到编译 kernel 时的 vmlinux 文件的 section(结果没有完全显示):

objdump -h vmlinux

vmlinux: file format elf64-x86-64

0 .text 0037a127 ffffffff81000000 0000000001000000 00200000 2**12

CONTENTS, ALLOC, LOAD, READONLY, CODE

3 .rodata 0013c8ec ffffffff8137f000 000000000137f000 0057f000 2**6

CONTENTS, ALLOC, LOAD, READONLY, DATA

11 .data 0004d920 ffffffff814ec000 00000000014ec000 006ec000 2**12

CONTENTS, ALLOC, LOAD, DATA

19 .data.percpu 00012880 0000000000000000 000000000153b000 00a00000 2**12

CONTENTS, ALLOC, LOAD, DATA

可以看到 vmlinux 文件中的 .data 和 .data.percpu section。

percpu 变量的地址实际上就是其在上面说到的 section 里的偏移量,这个偏移量还要加上特定处理器的偏移量(也就是上面头文件中的 per_cpu_offset、my_cpu_offset 等)得到最终的变量地址,并最终以指针引用的方式得到值,这样访问的效果就有点类似于访问全局变量了。percpu 变量通常用于更新非常频繁而访问机会又相对比较少的场合,这样的处理方式可以避免多处理器环境下的频繁加锁等操作。

从上面的注释也可以看到 per_cpu_offset 是在一个 percpu 变量上增加的偏移量,大多数系统架构下使用 __per_cpu_offset 数组来作为偏移量,而 x86_64 等架构下处理方式则不同。my_cpu_offset 是在调用 per_cpu_offset 时使用 smp_processor_id() 得到当前处理器 ID 作为参数,__my_cpu_offset 则是用 raw_smp_processor_id() 的值作为 per_cpu_offset 的参数(smp_processor_id() 在抢占被关闭时是安全的)。SHIFT_PERCPU_PTR 宏用于给指针增加偏移量,它使用的 RELOC_HIDE 宏在不同的编译器下实现不同,在 include/linux/compiler.h 头文件中,看看 gcc 编译下的处理:

#define RELOC_HIDE(ptr, off)\

({ unsigned long __ptr;\

__asm__ ("" : "=r"(__ptr) : "0"(ptr));\

(typeof(ptr)) (__ptr + (off)); })

可以看到 gcc 中使用内嵌汇编先将 ptr 值赋给 __ptr(unsigned long 类型),然后在 __ptr 基础上增加偏移量,这样可以避免编译报错,ptr 值不变而且最终以 ptr 指定的类型来返回。

include/asm-generic/percpu.h 头文件中定义了 per_cpu、__get_cpu_var、__raw_get_cpu_var、this_cpu_ptr、__this_cpu_ptr 等几个常用的宏。per_cpu 就用于得到某个指定处理器的变量,__get_cpu_var 用于得到当前处理器的 percpu 变量值。

再来看看 DEFINE_PER_CPU 的实现,它在 include/linux/percpu-defs.h 头文件中:

#define __PCPU_ATTRS(sec)\

__percpu __attribute__((section(PER_CPU_BASE_SECTION sec)))\

PER_CPU_ATTRIBUTES

#define DEFINE_PER_CPU_SECTION(type, name, sec)\

__PCPU_ATTRS(sec) PER_CPU_DEF_ATTRIBUTES\

__typeof__(type) name

#define DEFINE_PER_CPU(type, name)\

DEFINE_PER_CPU_SECTION(type, name, "")

使用 DEFINE_PER_CPU 宏可以静态的定义 percpu 变量。__PCPU_ATTRS 指定输入的 section 类型,DEFINE_PER_CPU_SECTION 用于在特定的 section 上定义特定类型的变量。__typeof__ 和 上面见到的 typeof 是一样的,都用于获取 type 的数据类型。__attribute__((section(xxx))) 表示把定义的变量存储在指定的 section 上。DEFINE_PER_CPU 就用于定义在 PER_CPU_BASE_SECTION section 上(从最开始的代码中也可以看出非 SMP 时用 .data 段,SMP 时用 .data.percpu 段)。

然后是 get_cpu_var 宏的实现,它在 include/linux/percpu.h 头文件中:

/*

* Must be an lvalue. Since @var must be a simple identifier,

* we force a syntax error here if it isn't.

*/

#define get_cpu_var(var) (*({\

preempt_disable();\

&__get_cpu_var(var); }))

/*

* The weird & is necessary because sparse considers (void)(var) to be

* a direct dereference of percpu variable (var).

*/

#define put_cpu_var(var) do {\

(void)&(var);\

preempt_enable();\

} while (0)

#define alloc_percpu(type)\

(typeof(type) __percpu *)__alloc_percpu(sizeof(type), __alignof__(type))

get_cpu_var 会先禁止抢占然后调用 __get_cpu_var 得到 percpu 变量值。put_cpu_var 则重新启用抢占。

另外在 include/linux/percpu.h 等文件中还定义了 alloc_percpu 和 free_percpu 宏来动态定义和释放 percpu 变量,他们都是通过 percpu memory allocator 来实现的,在 mm/percpu.c 中,动态分配的 percpu 变量可以通过 per_cpu_ptr 宏来得到,为此 kernel 还引入了 this_cpu_ptr、this_cpu_read 等一系列相关机制用寄存器替代内存提高对 percpu 变量的访问速度,关于 percpu memory allocator 等信息以后再来详细分析了。

以上为个人分析结果,有任何问题欢迎指正咯 ^_^

linux内核percpu变量声明,Linux kernel percpu变量解析相关推荐

  1. linux内核 lts长期演进,Linux Kernel 4.19 将成为下一个LTS(长期支持)系列

    最近Linux内核开发人员和维护人员Greg Kroah-Hartman透露,Linux Kernel 4.19将下一个长期支持的Linux内核系列. 现在Linux Kernel 4.17已经达到使 ...

  2. Linux内核中makefile有什么作用?深入解析makefile工作过程和原理

    Table of Contents Makefile 中的变量 常用的变量有以下几类: 1) 版本信息 2) CPU 体系结构:ARCH 3) 路径信息:TOPDIR, SUBDIRS 4) 内核组成 ...

  3. 【Linux 内核】进程管理 ( Linux 内核中的进程状态 | TASK_RUNNING | TASK_INTERRUPTIBLE | __TASK_STOPPED | EXIT_ZOMBIE )

    文章目录 一.Linux 内核中的进程状态 二.TASK_RUNNING 状态 三.TASK_RUNNING 状态 四.TASK_UNINTERRUPTIBLE 状态 五.__TASK_STOPPED ...

  4. 一文了解linux内核,一文了解Linux的系统结构

    什么是 Linux ? 如果你以前从未接触过Linux,可能就不清楚为什么会有这么多不同的Linux发行版.在查看Linux软件包时,你肯定被发行版.LiveCD和GNU之类的术语搞晕过.初次进入Li ...

  5. Linux内核入门-如何获取Linux内核源代码、生成配置内核

    如何获取Linux内核源代码 如何获取Linux内核源代码 下载Linux内核当然要去官方网站了,网站提供了两种文件下载,一种是完整的Linux内核,另一种是内核增量补丁,它们都是tar归档压缩包.除 ...

  6. 在win10查看本机linux的文件,Windows 10变身开发者利器:内置Linux内核,轻松查看Linux子系统文件...

    原标题:Windows 10变身开发者利器:内置Linux内核,轻松查看Linux子系统文件 来源:创事记 终于!在Windows里可以访问Linux文件了. 这表明,微软插入开源界的触角,越来越深入 ...

  7. 搭建《深入Linux内核架构》的Linux环境

    搭建<深入Linux内核架构>的Linux环境 阅读目录(Content) 作者 软件 概述 正文 一.安装GCC 二.编译Linux内核 三.制作跟文件系统 四.运行qemu 五.启动l ...

  8. linux内核启动分析 三,Linux内核分析 实验三:跟踪分析Linux内核的启动过程

    贺邦 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一. 实验过程 ...

  9. 查看linux内核的编译时间,linux内核编译步骤

    linux内核编译步骤 对于linux新手来说,编译内核相对有一些难度,甚至不知道如何入手,我通过在网上收集这方面的资料,最终编译成功.现在我归纳了一下,写出这一篇还算比较详细的步骤,希望能对各位新手 ...

  10. Linux内核开发_1_编译LInux内核

    目录 1. 准备工作 1.1 学习环境 1.2 下载Linux内核源码 1.3 解压Linux内核 1.4 目录结构介绍 2. Linux内核配置 2.1 配置选项 1. make config 2. ...

最新文章

  1. SpringBoot(四)-- 整合Servlet、Filter、Listener
  2. 2013年7月份第4周51Aspx源码发布详情
  3. MSSqlServer基础学习01
  4. bug的定义,分类与要求
  5. 创建SpringBoot项目的两种姿势
  6. C#ORM系统 Moon.ORM使用方法
  7. [复习计划]IMS5024
  8. snorkel_Snorkel AI:标记培训数据的程序化方法
  9. 中国各行各业的祖师爷是谁?
  10. 区块链开发之智能合约设计模式
  11. 10大程序员必逛网站,良心推荐,建议收藏!
  12. 向量代数,直线,平面
  13. 《变形金刚》成为现实 机器人变坦克
  14. 如何将电脑中的视频进行剪辑?电脑视频剪辑工具哪个好
  15. 报考计算机专业高校专项自荐信,2017年高校专项计划自荐信
  16. 高二会考计算机操作题试题及答案,2017高二数学会考试题及答案_高二会考答案(数学)(5)...
  17. SMETA认证咨询|会员Alsico欧洲最大的工作服生产商之一不断提升供应商工厂条件
  18. 无盘服务器架设之四:iPxe无盘超级应用实例
  19. golang:模拟枚举
  20. [O2JAM劲乐团] 音乐集...

热门文章

  1. getopt在Python中的使用
  2. 数据库设计(一对一、一对多、多对多)
  3. 项目管理最佳实践方法_项目管理:控制项目进度最佳实践
  4. GIS基础知识汇总篇(五)-无人机真正射影像的概念和制作原理
  5. 怎么圆角变直角_衣柜设计个圆角有什么用?效果好看又实用,会这样装的都是老木工...
  6. 云计算hcie贴吧_专业介绍|计算机网络技术
  7. jquery简单原则器(匹配索引为指定值的元素)
  8. 前端工程师和设计师必读文章推荐【系列三十六】
  9. httpHandlers和httpModules接口介绍 (5)
  10. HTML5教程之-文件拖拽功能实现