CPU 掩码
介绍
Cpumasks
是Linux内核提供的保存系统CPU信息的特殊方法。包含 Cpumasks
操作 API 相关的源码和头文件:
正如 include/linux/cpumask.h 注释:Cpumasks 提供了代表系统中 CPU 集合的位图,一位放置一个 CPU 序号。我们已经在 Kernel entry point 部分,函数 boot_cpu_init
中看到了一点 cpumask。这个函数将第一个启动的 cpu 上线、激活等等……
set_cpu_possible
是一个在系统启动时任意时刻都可插入的 cpu ID 集合。cpu_present
代表了当前插入的 CPUs。cpu_online
是 cpu_present
的子集,表示可调度的 CPUs。这些掩码依赖于 CONFIG_HOTPLUG_CPU
配置选项,以及 possible == present
和 active == online
选项是否被禁用。这些函数的实现很相似,检测第二个参数,如果为 true
,就调用 cpumask_set_cpu
,否则调用 cpumask_clear_cpu
。
有两种方法创建 cpumask
。第一种是用 cpumask_t
。定义如下:
它封装了 cpumask
结构,其包含了一个位掩码 bits
字段。DECLARE_BITMAP
宏有两个参数:
bitmap name;
number of bits.
并以给定名称创建了一个 unsigned long
数组。它的实现非常简单:
其中 BITS_TO_LONGS
:
因为我们专注于 x86_64
架构,unsigned long
是8字节大小,因此我们的数组仅包含一个元素:
NR_CPUS
宏表示的是系统中 CPU 的数目,且依赖于在 include/linux/threads.h 中定义的 CONFIG_NR_CPUS
宏,看起来像这样:
第二种定义 cpumask 的方法是直接使用宏 DECLARE_BITMAP
和 to_cpumask
宏,后者将给定的位图转化为 struct cpumask *
:
可以看到这里的三目运算符每次总是 true
。__check_is_bitmap
内联函数定义为:
每次都是返回 1
。我们需要它只是因为:编译时检测一个给定的 bitmap
是一个位图,换句话说,它检测一个 bitmap
是否有 unsigned long *
类型。因此我们传递 cpu_possible_bits
给宏 to_cpumask
,将 unsigned long
数组转换为 struct cpumask *
。
cpumask API
因为我们可以用其中一个方法来定义 cpumask,Linux 内核提供了 API 来处理 cpumask。我们来研究下其中一个函数,例如 set_cpu_online
,这个函数有两个参数:
CPU 数目;
CPU 状态;
这个函数的实现如下所示:
该函数首先检测第二个 state
参数并调用依赖它的 cpumask_set_cpu
或 cpumask_clear_cpu
。这里我们可以看到在中 cpumask_set_cpu
的第二个参数转换为 struct cpumask *
。在我们的例子中是位图 cpu_online_bits
,定义如下:
函数 cpumask_set_cpu
仅调用了一次 set_bit
函数:
set_bit
函数也有两个参数,设置了一个给定位(第一个参数)的内存(第二个参数或 cpu_online_bits
位图)。这儿我们可以看到在调用 set_bit
之前,它的两个参数会传递给
cpumask_check;
cpumask_bits.
让我们细看下这两个宏。第一个 cpumask_check
在我们的例子里没做任何事,只是返回了给的参数。第二个 cpumask_bits
只是返回了传入 struct cpumask *
结构的 bits
域。
现在让我们看下 set_bit
的实现:
这个函数看着吓人,但它没有看起来那么难。首先传参 nr
或者说位数给 IS_IMMEDIATE
宏,该宏调用了 GCC 内联函数 __builtin_constant_p
:
__builtin_constant_p
检查给定参数是否编译时恒定变量。因为我们的 cpu
不是编译时恒定变量,将会执行 else
分支:
让我们试着一步一步来理解它如何工作的:
LOCK_PREFIX
是个 x86 lock
指令。这个指令告诉 CPU 当指令执行时占据系统总线。这允许 CPU 同步内存访问,防止多核(或多设备 - 比如 DMA 控制器)并发访问同一个内存cell。
BITOP_ADDR
转换给定参数至 (*(volatile long *)
并且加了 +m
约束。+
意味着这个操作数对于指令是可读写的。m
显示这是一个内存操作数。BITOP_ADDR
定义如下:
接下来是 memory
。它告诉编译器汇编代码执行内存读或写到某些项,而不是那些输入或输出操作数(例如,访问指向输出参数的内存)。
Ir
- 寄存器操作数。
bts
指令设置一个位字符串的给定位,存储给定位的值到 CF
标志位。所以我们传递 cpu 号,我们的例子中为 0,给 set_bit
并且执行后,其设置了在 cpu_online_bits
cpumask 中的 0 位。这意味着第一个 cpu 此时上线了。
当然,除了 set_cpu_*
API 外,cpumask 提供了其它 cpumasks 操作的 API。让我们简短看下。
附加的 cpumask API
cpumaks 提供了一系列宏来得到不同状态 CPUs 序号。例如:
这个宏返回了 online
CPUs 数量。它读取 cpu_online_mask
位图并调用了 cpumask_weight
函数。cpumask_weight
函数使用两个参数调用了一次 bitmap_weight
函数:
cpumask bitmap;
nr_cpumask_bits
- 在我们的例子中就是NR_CPUS
。
并计算给定位图的位数。除了 num_online_cpus
,cpumask还提供了所有 CPU 状态的宏:
num_possible_cpus;
num_active_cpus;
cpu_online;
cpu_possible.
等等。
除了 Linux 内核提供的下述操作 cpumask
的 API:
for_each_cpu
- 遍历一个mask的所有 cpu;for_each_cpu_not
- 遍历所有补集的 cpu;cpumask_clear_cpu
- 清除一个 cpumask 的 cpu;cpumask_test_cpu
- 测试一个 mask 中的 cpu;cpumask_setall
- 设置 mask 的所有 cpu;cpumask_size
- 返回分配 'struct cpumask' 字节数大小;
还有很多。
链接
最后更新于