Files
linux-insides-zh/Concepts/linux-cpu-2.md
2018-03-14 15:04:05 -04:00

225 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
CPU masks
================================================================================
介绍
--------------------------------------------------------------------------------
`Cpumasks` 是Linux内核提供的保存系统CPU信息的特殊方法。包含 `Cpumasks` 操作 API 相关的源码和头文件:
* [include/linux/cpumask.h](https://github.com/torvalds/linux/blob/master/include/linux/cpumask.h)
* [lib/cpumask.c](https://github.com/torvalds/linux/blob/master/lib/cpumask.c)
* [kernel/cpu.c](https://github.com/torvalds/linux/blob/master/kernel/cpu.c)
正如 [include/linux/cpumask.h](https://github.com/torvalds/linux/blob/master/include/linux/cpumask.h) 注释Cpumasks 提供了代表系统中 CPU 集合的位图,一位放置一个 CPU 序号。我们已经在 [Kernel entry point](http://0xax.gitbooks.io/linux-insides/content/Initialization/linux-initialization-4.html) 部分,函数 `boot_cpu_init` 中看到了一点 cpumask。这个函数将第一个启动的 cpu 上线、激活等等……
```C
set_cpu_online(cpu, true);
set_cpu_active(cpu, true);
set_cpu_present(cpu, true);
set_cpu_possible(cpu, true);
```
`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`。定义如下:
```C
typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;
```
它封装了 `cpumask` 结构,其包含了一个位掩码 `bits` 字段。`DECLARE_BITMAP` 宏有两个参数:
* bitmap name;
* number of bits.
并以给定名称创建了一个 `unsigned long` 数组。它的实现非常简单:
```C
#define DECLARE_BITMAP(name,bits) \
unsigned long name[BITS_TO_LONGS(bits)]
```
其中 `BITS_TO_LONGS`
```C
#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))
#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
```
因为我们专注于 `x86_64` 架构,`unsigned long` 是8字节大小因此我们的数组仅包含一个元素
```
(((8) + (8) - 1) / (8)) = 1
```
`NR_CPUS` 宏表示的是系统中 CPU 的数目,且依赖于在 [include/linux/threads.h](https://github.com/torvalds/linux/blob/master/include/linux/threads.h) 中定义的 `CONFIG_NR_CPUS` 宏,看起来像这样:
```C
#ifndef CONFIG_NR_CPUS
#define CONFIG_NR_CPUS 1
#endif
#define NR_CPUS CONFIG_NR_CPUS
```
第二种定义 cpumask 的方法是直接使用宏 `DECLARE_BITMAP``to_cpumask` 宏,后者将给定的位图转化为 `struct cpumask *`
```C
#define to_cpumask(bitmap) \
((struct cpumask *)(1 ? (bitmap) \
: (void *)sizeof(__check_is_bitmap(bitmap))))
```
可以看到这里的三目运算符每次总是 `true``__check_is_bitmap` 内联函数定义为:
```C
static inline int __check_is_bitmap(const unsigned long *bitmap)
{
return 1;
}
```
每次都是返回 `1`。我们需要它只是因为:编译时检测一个给定的 `bitmap` 是一个位图,换句话说,它检测一个 `bitmap` 是否有 `unsigned long *` 类型。因此我们传递 `cpu_possible_bits` 给宏 `to_cpumask` ,将 `unsigned long` 数组转换为 `struct cpumask *`
cpumask API
--------------------------------------------------------------------------------
因为我们可以用其中一个方法来定义 cpumaskLinux 内核提供了 API 来处理 cpumask。我们来研究下其中一个函数例如 `set_cpu_online`,这个函数有两个参数:
* CPU 数目;
* CPU 状态;
这个函数的实现如下所示:
```C
void set_cpu_online(unsigned int cpu, bool online)
{
if (online) {
cpumask_set_cpu(cpu, to_cpumask(cpu_online_bits));
cpumask_set_cpu(cpu, to_cpumask(cpu_active_bits));
} else {
cpumask_clear_cpu(cpu, to_cpumask(cpu_online_bits));
}
}
```
该函数首先检测第二个 `state` 参数并调用依赖它的 `cpumask_set_cpu``cpumask_clear_cpu`。这里我们可以看到在中 `cpumask_set_cpu` 的第二个参数转换为 `struct cpumask *`。在我们的例子中是位图 `cpu_online_bits`,定义如下:
```C
static DECLARE_BITMAP(cpu_online_bits, CONFIG_NR_CPUS) __read_mostly;
```
函数 `cpumask_set_cpu` 仅调用了一次 `set_bit` 函数:
```C
static inline void cpumask_set_cpu(unsigned int cpu, struct cpumask *dstp)
{
set_bit(cpumask_check(cpu), cpumask_bits(dstp));
}
```
`set_bit` 函数也有两个参数,设置了一个给定位(第一个参数)的内存(第二个参数或 `cpu_online_bits` 位图)。这儿我们可以看到在调用 `set_bit` 之前,它的两个参数会传递给
* cpumask_check;
* cpumask_bits.
让我们细看下这两个宏。第一个 `cpumask_check` 在我们的例子里没做任何事,只是返回了给的参数。第二个 `cpumask_bits` 只是返回了传入 `struct cpumask *` 结构的 `bits` 域。
```C
#define cpumask_bits(maskp) ((maskp)->bits)
```
现在让我们看下 `set_bit` 的实现:
```C
static __always_inline void
set_bit(long nr, volatile unsigned long *addr)
{
if (IS_IMMEDIATE(nr)) {
asm volatile(LOCK_PREFIX "orb %1,%0"
: CONST_MASK_ADDR(nr, addr)
: "iq" ((u8)CONST_MASK(nr))
: "memory");
} else {
asm volatile(LOCK_PREFIX "bts %1,%0"
: BITOP_ADDR(addr) : "Ir" (nr) : "memory");
}
}
```
这个函数看着吓人,但它没有看起来那么难。首先传参 `nr` 或者说位数给 `IS_IMMEDIATE` 宏,该宏调用了 GCC 内联函数 `__builtin_constant_p`
```C
#define IS_IMMEDIATE(nr) (__builtin_constant_p(nr))
```
`__builtin_constant_p` 检查给定参数是否编译时恒定变量。因为我们的 `cpu` 不是编译时恒定变量,将会执行 `else` 分支:
```C
asm volatile(LOCK_PREFIX "bts %1,%0" : BITOP_ADDR(addr) : "Ir" (nr) : "memory");
```
让我们试着一步一步来理解它如何工作的:
`LOCK_PREFIX` 是个 x86 `lock` 指令。这个指令告诉 CPU 当指令执行时占据系统总线。这允许 CPU 同步内存访问,防止多核(或多设备 - 比如 DMA 控制器并发访问同一个内存cell。
`BITOP_ADDR` 转换给定参数至 `(*(volatile long *)` 并且加了 `+m` 约束。`+` 意味着这个操作数对于指令是可读写的。`m` 显示这是一个内存操作数。`BITOP_ADDR` 定义如下:
```C
#define BITOP_ADDR(x) "+m" (*(volatile long *) (x))
```
接下来是 `memory`。它告诉编译器汇编代码执行内存读或写到某些项,而不是那些输入或输出操作数(例如,访问指向输出参数的内存)。
`Ir` - 寄存器操作数。
`bts` 指令设置一个位字符串的给定位,存储给定位的值到 `CF` 标志位。所以我们传递 cpu 号,我们的例子中为 0`set_bit` 并且执行后,其设置了在 `cpu_online_bits` cpumask 中的 0 位。这意味着第一个 cpu 此时上线了。
当然,除了 `set_cpu_*` API 外cpumask 提供了其它 cpumasks 操作的 API。让我们简短看下。
附加的 cpumask API
--------------------------------------------------------------------------------
cpumaks 提供了一系列宏来得到不同状态 CPUs 序号。例如:
```C
#define num_online_cpus() cpumask_weight(cpu_online_mask)
```
这个宏返回了 `online` CPUs 数量。它读取 `cpu_online_mask` 位图并调用了 `cpumask_weight` 函数。`cpumask_weight` 函数使用两个参数调用了一次 `bitmap_weight` 函数:
* cpumask bitmap;
* `nr_cpumask_bits` - 在我们的例子中就是 `NR_CPUS`
```C
static inline unsigned int cpumask_weight(const struct cpumask *srcp)
{
return bitmap_weight(cpumask_bits(srcp), nr_cpumask_bits);
}
```
并计算给定位图的位数。除了 `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' 字节数大小;
还有很多。
链接
--------------------------------------------------------------------------------
* [cpumask documentation](https://www.kernel.org/doc/Documentation/cpu-hotplug.txt)