linux内核percpu变量声明,Linux kernel percpu变量解析

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 等信息以后再来详细分析了。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/366821.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

django组件 分页器

1 from django.shortcuts import render,HttpResponse2 3 # Create your views here.4 from app01.models import *5 from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger6 7 def index(request):8 9 10 批量导入数据: 11 12 Booklist[] …

自己写一个H5项目CI系统

持续集成(Continuous integration,简称CI)系统在软件自动化构建(包括编译、发布、自动化测试)方面有着重要的作用,在之前,前端项目简单,很多时候发布都只是一些简单的拷贝,而随着web…

25.QT-模型视图

模型视图设计模式的核心思想 使模型(数据)与视图(显示)相分离模型只需要对外提供标准接口存取数据,无需数据如何显示视图只需要自定义数据的显示方式,无需数据如何组织存储当数据发生改变时,会通过信号通知视图当用户与视图进行交互时,会通过信号向模型发送交互信息 在QT中提供…

休眠事实:多级访存

在多个级别上检索根实体及其子关联是很常见的。 在我们的示例中,我们需要加载一个包含其树,分支和叶子的森林,并且我们将尝试查看Hibernate对于三种集合类型的行为:集合,索引列表和包。 这是我们的类层次结构的样子&…

linux系统fuser命令,Linux系统使用Fuser命令的方法

fuser命令是一个非常聪明的unix实用程序,用于查找正在使用某个文件、目录或socket的进程。 它还提供有关拥有该进程的用户和访问类型的信息。。fuser工具显示了使用指定文件或文件系统的每个进程的进程ID(PID)。安装如果你的精简版运行fuser提示如下信息&#xff1a…

网络基础之 Nmap 命令

nmap......转载于:https://www.cnblogs.com/changha0/p/9898020.html

react-router 源码浅析

用 react-router 也用了比较久了,对他的内部工作方式却只是了解皮毛,而且大部分还是通过别人的博客。最近两周打算自己探究一下他的实现。 注意!因为我只使用过 v3 版本的 react-router,因为对他的使用方式比较熟悉,所…

前5个有用的隐藏Eclipse功能

Eclipse是野兽。 仅凭其力量才能超越其神秘感的设备。 有人将其称为连续体跨功能器 。 其他人则称它为透湿器 。 是的,它是如此之大,需要花费数年才能掌握。 然后,您的经理出现并告诉您:我们正在使用NetBeans。 开玩笑。 除了Ada…

linux如何解除密码,如何在Linux下解除PDF文件的密码?

【51CTO.com快译】今天,我碰巧与一位朋友共享一个受密码保护的PDF文件。我知道该PDF文件的密码,但不想透露。相反,我只想解除密码,将文件发送给朋友。于是我开始在网上找一些简单的方法,好解除PDF文件的密码保护。上网…

C#中结构体定义并转换字节数组

ref: https://www.cnblogs.com/dafanjoy/p/7818126.html C#中结构体定义并转换字节数组 最近的项目在做socket通信报文解析的时候,用到了结构体与字节数组的转换;由于客户端采用C开发,服务端采用C#开发,所以双方必须保证各自定义结…

解析robots.txt

案例: http://www.taobao.com/robots.txt 学习: User-agent: * 这里的*代表的所有的搜索引擎种类,*是一个通配符Disallow: /admin/ 这里定义是禁止爬寻admin目录下面的目录Disallow: /require/ 这里定义是禁止爬寻require目录下面的目录Disal…

2018移动端页面适配-自适应最新方案直接写px--------通过gulp工作流搭建一体化的移动端开发环境

1.开始 在flexible的GitHub上面写着 由于viewport单位得到众多浏览器的兼容,lib-flexible这个过渡方案已经可以放弃使用,不管是现在的版本还是以前的版本,都存有一定的问题。建议大家开始使用viewport来替代此方案。vw的兼容方案可以参阅《如…

jclouds的命令行界面

序幕 我使用和为jclouds贡献了一年多的时间。 到目前为止,我已经在很多领域广泛使用了它,尤其是在Fuse生态系统中 。 它的强大之处在于它缺少一件事,该工具可用于管理jclouds也提供访问权限的任何云提供商。 类似于EC2命令之类的工具&#xf…

中兴linux下载软件,国产操作系统中兴新支点使用WPS For Linux办公软件的体验报告...

以下将给你带来在国产操作系统中兴新支点操作系统下使用WPS For Linux办公软件的体验报告,WPS For Linux提供Deb、Rpm、Tar.xz、Snap软件包,你可以选择Tar.xz源码包编译安装,或在系统自带的软件中心下安装,也可以参考采用snap方式…

Java 教程(开发环境配置+基础语法)

Java 开发环境配置 在本章节中我们将为大家介绍如何搭建Java开发环境。 window系统安装java 下载JDK 首先我们需要下载java开发工具包JDK,下载地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html,点击如下下载按钮&am…

数据采集工具Telegraf:简介及安装

接着上一篇博客:InfluxDB简介及安装,这篇博客介绍下Linux环境下Telegraf安装以及其功能特点。。。 官网地址:influxdata 官方文档:telegraf文档 环境:CentOS7.4 64位 Telegraf版本:0.11.1-1 一、Telegraf介…

初探小程序插件

插播公司招聘信息: https://cnodejs.org/topic/5a915706653c43b914684f90 小程序插件可以干嘛? 周二晚上(3.13)的一个小程序新功能发布了-【小程序插件】,一开始以为是小程序发布了类似npm的组件管理工具,…

流畅和稳定的API的Lambda

几周前,我写了关于Java 8 lambda的介绍 。 在本简介中,我解释了什么是lambda以及如何将它们与Java 8中也引入的新Stream API结合使用。 Stream API为集合提供了更实用的接口。 此接口在很大程度上取决于lambda。 但是,lambda不仅具有改进的收…

linux 内存使用原理,linux中内存使用原理

首先介绍一下linux中内存是如何使用的。当有应用需要读写磁盘数据时,由系统把相关数据从磁盘读取到内存,如果物理内存不够,则把内存中的部分数据导入到磁盘,从而把磁盘的部分空间当作虚拟内存来使用,也称为Swap。如果给…

Confluence 6 站点备份和恢复

Atlassian 推荐针对生产环境中安装使用的 Confluence 使用原始数据库工具备份策略。 在默认的情况下,Confluence 每天都会备份所有数据和附件到 XML 文件备份中。这些文件被称为 XML 站点备份,同时这些文件存储在 Confluence home 目录中的 backups 目录…