纪念自己第一次成为一个大开源项目的 contributor,尽管只是一个很小的PR。
什么是CPU mask
内核使用CPU mask 来记录 CPU 的状态,cpumasks 提供了代表系统中 CPU 集合的位图,一个 bit 的0/1代表了一个 CPU在每种cpu mask 时的状态。实现(Linux kernel 5.10.20)主要包含在以下文件中:
- include/linux/cpumask.h
- kernel/cpu.c
CPU 的状态基本可分为以下四种:
- cpu_possible :是一个在系统启动时任意时刻都可插入的 cpu ID 集合。它将等于通过
CONFIG_NR_CPUS
内核配置选项静态设置的NR_CPUS
的值; - cpu_present :表示当前哪个CPU是插入的;
- cpu_online : 是 cpu_present 的一个子集,表示哪些CPU 是调度器可访问的;
- cpu_active : 该掩码的某些 bit 告诉Linux内核,一个任务可能已移至某个处理器。
内核声明了结构体cpumask
,并实例化 4 个cpumask
分别用来记录 CPU 的各种状态。以__cpu_possible_mask
为例,其定义如下代码所示(定义在文件 kernel/cpu.c 中)。其中__read_mostly原语将定义的变量存放在 .data.read_mostly 段中,其定义在/arch/arm64/include/asm/cache.h
文件中,原型为#define __read_mostly __section(.data..read_mostly)
,将经常需要被读取的数据定义为__read_mostly类型, 这样Linux内核被加载时,该数据将自动被存放到Cache中,以提高整个系统的执行效率。
struct cpumask __cpu_possible_mask __read_mostly;
EXPORT_SYMBOL(__cpu_possible_mask);
cpumask
的定义如下,DECLARE_BITMAP
宏用来计算 cpumask
所需的大小。NR_CPUS
为内核支持的最大 CPU 数,在内核编译时 make menuconfig 指定。
typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;
DECLARE_BITMAP
宏定义在include/linux/types.h
文件中,其接收两个参数:name,位图名;bits,位图所使用的bit数量。其展开也就是定义了一个以 long为元素的数组。
#define DECLARE_BITMAP(name,bits) \
unsigned long name[BITS_TO_LONGS(bits)]
最后看一下BITS_TO_LONGS
是如何根据 bits 计算出占用多少个 long 的。此宏定义在include/linux/bitops.h
文件中,如下:
#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_TYPE(long))
DIV_ROUND_UP
宏展开如下:
// include/linux/kernel.h
#define DIV_ROUND_UP __KERNEL_DIV_ROUND_UP
// include/uapi/linux/const.h
#define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
假设NR_CPUS
为8,long 占 8 个字节,cpumask
结构体展开如下。也就是说,内核为每个 CPU 分配 long 变量中的一个 bit 来保存各个 CPU 的状态。
BITS_TO_LONGS(8) -> DIV_ROUND_UP(8, BITS_PER_TYPE(long)) -> DIV_ROUND_UP(8, 64) -> (((8) + (64) - 1) / (64)) -> 1
typedef struct cpumask {
unsigned long bits[1]
} cpumask_t;
[注意!] 参考文档1按以下计算方式存在问题。若此时 CPU 数量是 9 个,计算得出需要 2 个 long 变量来保存CPU状态,这样就会很容易理解为一个 CPU的状态需要 1 个 btye 而不是 1 个 bit。 我向源Github仓库提交了 PR,不知道是否能合并进主线已经合并进了主线——首次成为一个大项目的 Contributor。
(((8) + (8) - 1) / (8)) = 1
cpumask API
对 CPU mask 的访问通过函数set_cpu_xxxx()
完成,其接收两个参数(cpuid ,true or false)。当第二个参数为 true 时,set_cpu的功能将被调用;当为 false 时, clear_cpu 的功能将被调用。例如,在系统初始化时,boot CPU 的API 接口调用如下代码所示:
// in kernel/cpu.c
void __init boot_cpu_init(void)
{
int cpu = smp_processor_id();
set_cpu_online(cpu, true);
set_cpu_active(cpu, true);
set_cpu_present(cpu, true);
set_cpu_possible(cpu, true);
#ifdef CONFIG_SMP
__boot_cpu_id = cpu;
#endif
}
set_cpu_online()
接收两个参数(cpuid ,true or false),其实现如下。若第二个参数为 true, cpumask_test_and_set_cpu
将被调用。
void set_cpu_online(unsigned int cpu, bool online)
{
if (online) {
if (!cpumask_test_and_set_cpu(cpu, &__cpu_online_mask))
atomic_inc(&__num_online_cpus);
} else {
if (cpumask_test_and_clear_cpu(cpu, &__cpu_online_mask))
atomic_dec(&__num_online_cpus);
}
}
cpumask_test_and_set_cpu
作为内联函数定义在include/linux/cpumask.h
文件中,接收CPU id cpu
以及相应的 CPU mask __cpu_online_mask
。
static inline int cpumask_test_and_set_cpu(int cpu, struct cpumask *cpumask)
{
return test_and_set_bit(cpumask_check(cpu), cpumask_bits(cpumask));
}
在调用test_and_set_bit
之前,有两个宏对 cpu 和 cpumask 进行了处理。
cpumask_check
是一个内联函数,检查传入的 cpu 参数是否大于了NR_CPUS
,不展开cpumask_bits
获取了 cpumask 的 bits,即上文叙述的unsigned long bits[1]
的内容。#define cpumask_bits(maskp) ((maskp)->bits)
test_and_set_bit()
函数定义在include/asm-generic/bitops/atomic.h
文件中,实现如下。BIT_MASK
将 1 左移 nr 位获得 mask,再与(maskp->bits)
相与。若为 1 则返回直接返回1;- 不为 1 则需要修改 cpumask。因此调用
atomic_long_fetch_or()
函数将 cpumask 与 mask 相或置位,并返回原 cpumask 给 old 变量; - old 变量与 mask 相与为0,经过两次
!
,最后依然返回0;
static inline int test_and_set_bit(unsigned int nr, volatile unsigned long *p)
{
long old;
unsigned long mask = BIT_MASK(nr);
p += BIT_WORD(nr);
if (READ_ONCE(*p) & mask)
return 1;
old = atomic_long_fetch_or(mask, (atomic_long_t *)p);
return !!(old & mask);
}
#define BIT_MASK(nr) (UL(1) << ((nr) % BITS_PER_LONG))
#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
其他关于 cpu mask 的 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
字节数大小;
Comments | NOTHING