Linux Kernel:CPU 状态管理之 cpumask

3,215 阅读45分钟

本文采用Linux 内核 v3.10 版本 x86-64 架构

一、概述

在 Linux 内核中,使用位图来保存 CPU 的状态,每个 CPU 对应着位图中的一个比特位。内核为 CPU 定义了 4 种可能的状态,对应着 4 个位图:

  • cpu_possible_mask
  • cpu_online_mask
  • cpu_present_mask
  • cpu_active_mask

cpu_possible_mask 指示系统实际支持CPU ,主要用于为 per-cpu 变量分配启动时内存。该值在系统启动阶段确定,限制了系统允许使用的最大 CPU 数量,一旦确定,在整个系统运行期间都是静态的、不可修改的。在支持热插拔的系统中,CPU 可在系统运行时添加或移除,但系统可用的最大 CPU 数量不能超过 cpu_possible_mask 指示的值。该值受内核配置选项 CONFIG_NR_CPUS 、命令行参数 possible_cpus=nnr_cpus=nmaxcpu=n 以及实际插入的 CPU 数量共同影响,计算过程详见 “3.4 参数的使用” 小节。

cpu_present_mask 指示系统当前存在CPU ,这些 CPU 并不一定全部处于 online 状态。

cpu_online_mask 指示系统当前在线CPU ,即当前可调度的 CPU ,或者说内核使用中的 CPUcpu_onlinecpu_present 的一个动态子集。在CPU 能被内核调度并能够接收设备中断之后,可在 __cpu_up() 函数中将其置位;当使用 __cpu_disable() 关闭某个 CPU 时,会清除对应比特位,在此之前,包括中断在内的所有操作系统服务都会迁移到另一个目标 CPU

cpu_active_mask 是指允许任务迁入CPU

在支持 CPU 热插拔(CPU-hotplug)的系统中,cpu_present_mask是动态的,因为可能会有 CPU 被添加或移除。

如果不支持热插拔(!CONFIG_HOTPLUG_CPU),那么 present == possible 并且 active == online

下文均假设系统支持热插拔,即内核配置选项 CONFIG_HOTPLUG_CPU 为真。

二、数据结构

2.1 结构体 cpumask / cpumask_t

内核使用结构体 cpumask 来表示 CPU 的状态位图,cpumask_tcpumask 的别名。cpumask 定义如下:

// file: include/linux/cpumask.h
typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;

cpumask 是对位图的包装,其内部引用了宏 DECLARE_BITMAP

2.1.1 宏 DECLARE_BITMAP

DECLARE_BITMAP 用来创建一个位图,其定义如下:

// file: include/linux/types.h
#define DECLARE_BITMAP(name,bits) \
	unsigned long name[BITS_TO_LONGS(bits)]

该宏接收 2 个参数:

  • name -- 位图名称
  • bits -- 位图的容量

由于我们使用 unsigned long 数组来表示位图,所以需要将比特位数量转换成数组的成员数量。宏 BITS_TO_LONGS 用来实现对应的转换,其定义如下:

  // file: include/linux/bitops.h
  #define BITS_TO_LONGS(nr)   DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))

BITS_TO_LONGS 中又引用了宏 DIV_ROUND_UPBITS_PER_BYTE

BITS_PER_BYTE扩展为 8,表示每个字节包含 8 个比特位:

  // file: include/linux/bitops.h
  #define BITS_PER_BYTE       8

BITS_PER_BYTE * sizeof(long) 计算 long 类型数据占用的比特位数量。

DIV_ROUND_UP 用来把整数 n 向上圆整到 d 的倍数,其实现方式就是把 n 加上 d-1 后再除以 d,该宏定义如下:

  // file: include/linux/kernel.h
  #define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))

在我们的案例中,就是将比特位数量向上圆整到unsigned long类型的整数倍,保证数组有足够的容量来保存比特位。

由于执行了向上圆整操作,实际的位图中有些比特位是无效的,如下图所示:

DECLARE_BITMAP.png

结构体 cpumask 扩展如下:

typedef struct cpumask { unsigned long bits[BITS_TO_LONGS(NR_CPUS)]; } cpumask_t;

可以看到,结构体 cpumask 的成员是一个名称为 bits 拥有 NR_CPUS 个有效比特位的 unsigned long 数组。

2.1.2 宏 NR_CPUS

NR_CPUS 扩展为 CONFIG_NR_CPUS,其定义如下:

// file: include/linux/threads.h
/* Places which use this should consider cpumask_var_t. */
#define NR_CPUS		CONFIG_NR_CPUS

CONFIG_NR_CPUS 是内核编译时指定的配置选项,其定义如下:

// file: include/generated/autoconf.h
#define CONFIG_NR_CPUS 4096

该宏扩展为 4096

// file: arch/x86/Kconfig
config NR_CPUS
	int "Maximum number of CPUs" if SMP && !MAXSMP
	range 2 8 if SMP && X86_32 && !X86_BIGSMP
	range 2 512 if SMP && !MAXSMP	//  --- 没有设置了 MAXSMP 选项时,SMP 系统下,NR_CPUS 最大为 512 ---
	default "1" if !SMP
	default "4096" if MAXSMP	 //  --- 当设置了 MAXSMP 选项时,NR_CPUS 默认为 4096 ---
	default "32" if SMP && (X86_NUMAQ || X86_SUMMIT || X86_BIGSMP || X86_ES7000)
	default "8" if SMP
	---help---
	  This allows you to specify the maximum number of CPUs which this
	  kernel will support.  The maximum supported value is 512 and the
	  minimum value which makes sense is 2.

	  This is purely to save memory - each supported CPU adds
	  approximately eight kilobytes to the kernel image.

4096 是在设置了 MAXSMP(Enable Maximum number of SMP Processors and NUMA Nodes) 选项时的默认值,不同的内核版本该值可能不同。

MAXSMP 选项会将处理器的数量设为最大值:

// file: arch/x86/Kconfig
config MAXSMP
	bool "Enable Maximum number of SMP Processors and NUMA Nodes"
	depends on X86_64 && SMP && DEBUG_KERNEL
	select CPUMASK_OFFSTACK
	---help---
	  Enable maximum number of CPUS and NUMA Nodes for this architecture.
	  If unsure, say N.

如果没有配置 MAXSMP 选项,在 x86-64 架构 SMP 系统下, NR_CPUS 最大只能设置为 512(见 config NR_CPUS 中的说明)。

使用 make menuconfig 命令, 配置 MAXSMP NR_CPUS 的界面如下图所示:

config_MAXSMP.png

可以看到,在选中 MAXSMP 的情况下,并不会出现设置最大 CPU 数量的选项,直接默认使用最大值。

config_NR_CPUS.png

MAXSMP 未选中时,显示设置 CPU 数量的选项,默认值为 8。

CONFIG_NR_CPUS 指示系统配置的最大 CPU 数量。我编译时使用的是默认配置,即选中了 MAXSMP ,所以CONFIG_NR_CPUS 的值比较大。在下文将会看到,内核中使用的多个 CPU 相关的位图,其默认容量都是 CONFIG_NR_CPUS ,如果此值设置的太大超出实际 CPU 数量很多的话,会浪费内存空间。所以,还是要根据实际情况设置 NR_CPUS的值,避免浪费内存。

2.2 cpu_possible_mask

cpu_possible_mask 是指系统实际支持CPU 位图,其定义如下:

// file: kernel/cpu.c
const struct cpumask *const cpu_possible_mask = to_cpumask(cpu_possible_bits);

变量 cpu_possible_mask 的实现中,又引用了 cpu_possible_bits 变量以及 to_cpumask() 函数。

2.2.1 位图 -- cpu_possible_bits

cpu_possible_bits 是使用宏 DECLARE_BITMAP 创建的位图,其定义如下:

// file: kernel/cpu.c
#ifdef CONFIG_INIT_ALL_POSSIBLE
static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly
	= CPU_BITS_ALL;
#else
static DECLARE_BITMAP(cpu_possible_bits, CONFIG_NR_CPUS) __read_mostly;
#endif

可以看到,该位图的容量为 CONFIG_NR_CPUS,即配置的 CPU 数量。

cpu_possible_bits 的实现依赖于内核配置选项 CONFIG_INIT_ALL_POSSIBLE。当该选项为假时,只是创建位图,并未初始化;当选项为真时,在创建位图后,还将其初始化为 CPU_BITS_ALL,即将 cpu_possible_bits 中的所有的有效位(不包括因对齐到 long 类型边界而填充的位)初始化为 1。

CPU_BITS_ALL 定义如下:

// file: include/linux/cpumask.h
#define CPU_BITS_ALL						\
{								\
	[0 ... BITS_TO_LONGS(NR_CPUS)-2] = ~0UL,		\
	[BITS_TO_LONGS(NR_CPUS)-1] = CPU_MASK_LAST_WORD		\
}

由于在将比特位数量转成数组成员数量时,数组最后一个成员中可能会填充无效比特位,所以需要对其进行特殊处理。在 CPU_BITS_ALL 的实现中,最后一个数组成员调用宏 CPU_MASK_LAST_WORD 进行处理,其它成员的比特位全部初始化为 1。

CPU_MASK_LAST_WORD 定义如下,该宏内部又引用了 BITMAP_LAST_WORD_MASK

// file: include/linux/cpumask.h
#define CPU_MASK_LAST_WORD BITMAP_LAST_WORD_MASK(NR_CPUS)

BITMAP_LAST_WORD_MASK 会根据比特位数量 NR_CPUS ,计算最后一个数组成员中的有效比特位,并将这些位设置为 1,其它位为 0,该宏定义如下:

 // file: include/linux/bitmap.h
 #define BITMAP_LAST_WORD_MASK(nbits)                    \
 (                                   \
     ((nbits) % BITS_PER_LONG) ?                 \
         (1UL<<((nbits) % BITS_PER_LONG))-1 : ~0UL       \
 )

当比特位数量不是 long 类型容量的整数倍时,宏 BITMAP_LAST_WORD_MASK 效果如下:

BITMAP_LAST_WORD_MASK.png

to_cpumask 用来将位图转换成 struct cpumask 类型,详见 to_cpumask 小节。

2.3 cpu_online_mask、cpu_present_mask、cpu_active_mask

这 3 个变量的实现与 cpu_possible_mask 类似,其定义如下:

// file: kernel/cpu.c
static DECLARE_BITMAP(cpu_online_bits, CONFIG_NR_CPUS) __read_mostly;
const struct cpumask *const cpu_online_mask = to_cpumask(cpu_online_bits);


static DECLARE_BITMAP(cpu_present_bits, CONFIG_NR_CPUS) __read_mostly;
const struct cpumask *const cpu_present_mask = to_cpumask(cpu_present_bits);


static DECLARE_BITMAP(cpu_active_bits, CONFIG_NR_CPUS) __read_mostly;
const struct cpumask *const cpu_active_mask = to_cpumask(cpu_active_bits);

可以看到,这 3 个变量的位图容量均为 CONFIG_NR_CPUS,因为是静态变量,所有比特位的初始值都为 0。

2.4 cpu_all_bits

cpu_all_bits 用以表示在 menuconfig 中配置的 CPU 的数量。该位图的容量为 NR_CPUS ,并通过宏 CPU_BITS_ALL 将所有的有效位(不包括因对齐到 long 类型边界而填充的位)设置为 1。

const DECLARE_BITMAP(cpu_all_bits, NR_CPUS) = CPU_BITS_ALL;

2.5 nr_cpumask_bits

nr_cpumask_bits 定义如下:

// file: include/linux/cpumask.h
#ifdef CONFIG_CPUMASK_OFFSTACK
/* Assuming NR_CPUS is huge, a runtime limit is more efficient.  Also,
 * not all bits may be allocated. */
#define nr_cpumask_bits	nr_cpu_ids
#else
#define nr_cpumask_bits	NR_CPUS
#endif

如果配置了内核选项 CONFIG_CPUMASK_OFFSTACK,该宏扩展为 nr_cpu_ids,否则,扩展为 NR_CPUSNR_CPUS是系统配置的最大 CPU 数量,在我们的案例中扩展为 4096;nr_cpu_ids系统实际支持的 CPU 数量,即 nr_cpu_ids == possible,具体计算见 “prefill_possible_map() 函数”。

CPUMASK_OFFSTACK 选项说明如下:

// file: lib/Kconfig
config CPUMASK_OFFSTACK
	bool "Force CPU masks off stack" if DEBUG_PER_CPU_MAPS
	help
	  Use dynamic allocation for cpumask_var_t, instead of putting
	  them on the stack.  This is a bit more expensive, but avoids
	  stack overflow.

可以看到,该选项是为了防止当 CPU 数量过大时,使用栈保存变量可能导致栈溢出的情况,从而不使用栈而使用动态分配变量内存的方案。在我们选中 MAXSMP 选项时,该选项默认也会选中。

2.6 cpu_bit_bitmap

cpu_bit_bitmap 声明如下:

// file: include/linux/cpumask.h
/*
 * Special-case data structure for "single bit set only" constant CPU masks.
 *
 * We pre-generate all the 64 (or 32) possible bit positions, with enough
 * padding to the left and the right, and return the constant pointer
 * appropriately offset.
 */
extern const unsigned long
	cpu_bit_bitmap[BITS_PER_LONG+1][BITS_TO_LONGS(NR_CPUS)];

其中,宏 BITS_PER_LONG 扩展为 64,NR_CPUS 是系统配置的最大 CPU 数量,BITS_TO_LONGS 用于将比特位数量转换成 long 类型数组成员数量。

cpu_bit_bitmap 定义如下:

// file: kernel/cpu.c
/*
 * cpu_bit_bitmap[] is a special, "compressed" data structure that
 * represents all NR_CPUS bits binary values of 1<<nr.
 *
 * It is used by cpumask_of() to get a constant address to a CPU
 * mask value that has a single bit set only.
 */

const unsigned long cpu_bit_bitmap[BITS_PER_LONG+1][BITS_TO_LONGS(NR_CPUS)] = {

	MASK_DECLARE_8(0),	MASK_DECLARE_8(8),
	MASK_DECLARE_8(16),	MASK_DECLARE_8(24),
#if BITS_PER_LONG > 32
	MASK_DECLARE_8(32),	MASK_DECLARE_8(40),
	MASK_DECLARE_8(48),	MASK_DECLARE_8(56),
#endif
};

/* cpu_bit_bitmap[0] is empty - so we can back into it */
#define MASK_DECLARE_1(x)	[x+1][0] = (1UL << (x))
#define MASK_DECLARE_2(x)	MASK_DECLARE_1(x), MASK_DECLARE_1(x+1)
#define MASK_DECLARE_4(x)	MASK_DECLARE_2(x), MASK_DECLARE_2(x+2)
#define MASK_DECLARE_8(x)	MASK_DECLARE_4(x), MASK_DECLARE_4(x+4)

可以看到,cpu_bit_bitmap 是一个包含 65 个成员的数组,每个成员都是一个容量为 NR_CPUS的位图。其中,第 0 个成员未做任何设置,其余的成员都只置位了一个比特位。

cpu_bit_bitmap 中,每个成员中置位的比特位是有规律的,如下所示:

cpu_bit_bitmap[][] = {
    [1][0] = 1 << 0,
    [2][0] = 1 << 1,
    [3][0] = 1 << 2,
    [4][0] = 1 << 3,
    ...
    [64][0] = 1 << 63
}

三、命令行参数

3.1 maxcpus=n

maxcpus 参数限制启动时激活的 CPU 数量。举例来说,假如有 4 个 CPU,使用 maxcpus=2只会启动 2 个,剩余的 2 个 CPU 可以在以后上线。

maxcpus 参数会修改变量 setup_max_cpus 的值。默认情况下,该值为 NR_CPUS,即配置选项 CONFIG_NR_CPUS的值。

// file: kernel/smp.c
/* Setup configured maximum number of CPUs to activate */
unsigned int setup_max_cpus = NR_CPUS;

指定了命令行参数 maxcpus 后,通过参数处理函数 maxcpus(),将 setup_max_cpus设置为指定的值。

// file: kernel/smp.c
static int __init maxcpus(char *str)
{
	get_option(&str, &setup_max_cpus);
	if (setup_max_cpus == 0)
		arch_disable_smp_support();

	return 0;
}

early_param("maxcpus", maxcpus);

maxcpus()函数内部,调用 get_option()函数,解析参数并赋值给 setup_max_cpus

如果 setup_max_cpus 的值为 0,说明是单处理器系统,调用 arch_disable_smp_support() 函数禁用 SMP 系统支持。

3.2 nr_cpus=n

nr_cpus 参数限制内核支持的最大 CPU 数量。如果该参数的值小于物理 CPU 的数量,那么多出的 CPU 将不能上线使用。

nr_cpus 参数将修改变量 nr_cpu_ids的值。默认情况下,该值为 NR_CPUS,即配置选项 CONFIG_NR_CPUS的值。

// file: kernel/smp.c
/* Setup number of possible processor ids */
int nr_cpu_ids __read_mostly = NR_CPUS;

指定了命令行参数 nr_cpus 后,通过参数处理函数 nrcpus(),将 nr_cpus设置为参数指定的值。

// file: kernel/smp.c
/* this is hard limit */
static int __init nrcpus(char *str)
{
	int nr_cpus;

	get_option(&str, &nr_cpus);
	if (nr_cpus > 0 && nr_cpus < nr_cpu_ids)
		nr_cpu_ids = nr_cpus;

	return 0;
}

early_param("nr_cpus", nrcpus);

nrcpus() 函数内部,调用 get_option()函数,解析参数并赋值给 nr_cpus

如果指定的值小于 nr_cpu_ids 的初始值(即 NR_CPUS),那么将nr_cpu_ids调整为参数 nr_cpus 指定的数值。也就是说,系统实际支持的最大 CPU 数量,是配置选项 CONFIG_NR_CPUS 和命令行参数 nr_cpus 之中较小的那个值

3.3 possible_cpus=n

possible_cpus 参数跟其它参数一起,共同决定系统支持的最大 CPU 数量。计算过程详见 “3.4 参数的使用” 小节。

指定了该参数后,通过参数处理函数 _setup_possible_cpus(),将变量 setup_possible_cpus 设置为指定的值。 setup_possible_cpus 的默认值为 -1。

// file: arch/x86/kernel/smpboot.c
static int __initdata setup_possible_cpus = -1;
static int __init _setup_possible_cpus(char *str)
{
	get_option(&str, &setup_possible_cpus);
	return 0;
}
early_param("possible_cpus", _setup_possible_cpus);

3.4 参数的使用

prefill_possible_map() 函数中,会对 cpu_possible_mask 进行填充。填充过程中,使用到了我们上文介绍过的参数。

该函数的调用链: start_kernel() -> setup_arc() -> prefill_possible_map()

另外,在 prefill_possible_map() 函数中,还用到了 2 个全局变量:num_processorsdisabled_cpus

// file: arch/x86/kernel/apic/apic.c
unsigned int num_processors;

unsigned disabled_cpus __cpuinitdata;

num_processors是插入且可用的 CPU 数量,该值不能大于 nr_cpu_idsdisabled_cpus 是指已插入但因超出 nr_cpu_ids 数量而被禁用的 CPU 数量。两者相加,即为实际插入的 CPU 总数量。

3.4.1 generic_processor_info() 函数

变量 num_processorsdisabled_cpus 的值在 generic_processor_info() 函数中被更新:

// file: arch/x86/kernel/apic/apic.c
void __cpuinit generic_processor_info(int apicid, int version)
{
	int cpu, max = nr_cpu_ids;
	bool boot_cpu_detected = physid_isset(boot_cpu_physical_apicid,
				phys_cpu_present_map);

	/*
	 * If boot cpu has not been detected yet, then only allow upto
	 * nr_cpu_ids - 1 processors and keep one slot free for boot cpu
	 */
	if (!boot_cpu_detected && num_processors >= nr_cpu_ids - 1 &&
	    apicid != boot_cpu_physical_apicid) {
		int thiscpu = max + disabled_cpus - 1;

		pr_warning(
			"ACPI: NR_CPUS/possible_cpus limit of %i almost"
			" reached. Keeping one slot for boot cpu."
			"  Processor %d/0x%x ignored.\n", max, thiscpu, apicid);

		disabled_cpus++;
		return;
	}

	if (num_processors >= nr_cpu_ids) {
		int thiscpu = max + disabled_cpus;

		pr_warning(
			"ACPI: NR_CPUS/possible_cpus limit of %i reached."
			"  Processor %d/0x%x ignored.\n", max, thiscpu, apicid);

		disabled_cpus++;
		return;
	}

	num_processors++;
	if (apicid == boot_cpu_physical_apicid) {
		/*
		 * x86_bios_cpu_apicid is required to have processors listed
		 * in same order as logical cpu numbers. Hence the first
		 * entry is BSP, and so on.
		 * boot_cpu_init() already hold bit 0 in cpu_present_mask
		 * for BSP.
		 */
		cpu = 0;
	} else
		cpu = cpumask_next_zero(-1, cpu_present_mask);

	......
        
	set_cpu_possible(cpu, true);
	set_cpu_present(cpu, true);
}

generic_processor_info() 函数执行过程如下:

  • 检查当前 CPU 是否是系统启动的 CPU,即 BSP(Bootstrap Processor)。
  • 如果不是 BSP,且已检测到的 CPU 数量 num_processors 大于等于 nr_cpu_ids - 1,说明实际插入的 CPU 数量超出允许的最大数量了,那么该 CPU 会被禁用,此时禁用数量加 1,即 disabled_cpus++。然后直接返回。之所以将上限值设置为 nr_cpu_ids - 1,是因为检测到的 CPU 不是 BSP,为了保证 BSP 是可用的,必须为 BSP 保留一个位置。
  • 如果是 BSP,且已检测到的 CPU 数量 num_processors 大于等于 nr_cpu_ids,说明实际插入的 CPU 数量已经超出允许的最大数量了,那么该 CPU 会被禁用,此时禁用数量加 1,即 disabled_cpus++。然后直接返回。
  • 如果没有超出 nr_cpu_ids 的限制,那么可用的 CPU 数量加 1,即 num_processors++
  • 确定 CPU 编号。如果 apicid == boot_cpu_physical_apicid,说明当前 CPU 是 BSP,BSP 的 CPU 编号为 0。否则,说明不是 BSP,那么调用 cpumask_next_zero() 函数,从位图 cpu_present_mask中找出第一个为 0 的位,将该位的索引设置为CPU 的编号。
  • 调用 set_cpu_possible() 函数,将 cpu 状态设置为 possible
  • 调用 set_cpu_present() 函数,将 cpu 状态设置为 present

3.4.2 prefill_possible_map() 函数

prefill_possible_map() 函数会计算 cpu_possible 的值,计算过程中会用到上文介绍的参数,该函数定义如下:

// file: arch/x86/kernel/smpboot.c
/*
 * cpu_possible_mask should be static, it cannot change as cpu's
 * are onlined, or offlined. The reason is per-cpu data-structures
 * are allocated by some modules at init time, and dont expect to
 * do this dynamically on cpu arrival/departure.
 * cpu_present_mask on the other hand can change dynamically.
 * In case when cpu_hotplug is not compiled, then we resort to current
 * behaviour, which is cpu_possible == cpu_present.
 * - Ashok Raj
 *
 * Three ways to find out the number of additional hotplug CPUs:
 * - If the BIOS specified disabled CPUs in ACPI/mptables use that.
 * - The user can overwrite it with possible_cpus=NUM
 * - Otherwise don't reserve additional CPUs.
 * We do this because additional CPUs waste a lot of memory.
 * -AK
 */
__init void prefill_possible_map(void)
{
	int i, possible;

	/* no processor from mptable or madt */
	if (!num_processors)
		num_processors = 1;

	i = setup_max_cpus ?: 1;
	if (setup_possible_cpus == -1) {
		possible = num_processors;
#ifdef CONFIG_HOTPLUG_CPU
		if (setup_max_cpus)
			possible += disabled_cpus;
#else
		if (possible > i)
			possible = i;
#endif
	} else
		possible = setup_possible_cpus;

	total_cpus = max_t(int, possible, num_processors + disabled_cpus);

	/* nr_cpu_ids could be reduced via nr_cpus= */
	if (possible > nr_cpu_ids) {
		pr_warn("%d Processors exceeds NR_CPUS limit of %d\n",
			possible, nr_cpu_ids);
		possible = nr_cpu_ids;
	}

#ifdef CONFIG_HOTPLUG_CPU
	if (!setup_max_cpus)
#endif
	if (possible > i) {
		pr_warn("%d Processors exceeds max_cpus limit of %u\n",
			possible, setup_max_cpus);
		possible = i;
	}

	pr_info("Allowing %d CPUs, %d hotplug CPUs\n",
		possible, max_t(int, possible - num_processors, 0));

	for (i = 0; i < possible; i++)
		set_cpu_possible(i, true);
	for (; i < NR_CPUS; i++)
		set_cpu_possible(i, false);

	nr_cpu_ids = possible;
}

prefill_possible_map() 函数执行流程如下:

i = setup_max_cpus ?: 1;

setup_max_cpus 是使用命令行参数 maxcpus=n 设置的系统启动时激活的 CPU 数量,默认为 NR_CPUS。当该值为 0 时,按照单处理器系统启动;否则,按照 SMP 系统启动。使用命令行选项 maxcpus=0nosmp,都会导致 setup_max_cpus 的值为 0。maxcpus=n 参数我们在上文已经介绍过了,nosmp 参数处理如下:

// file: kernel/smp.c
/*
 * Setup routine for controlling SMP activation
 *
 * Command-line option of "nosmp" or "maxcpus=0" will disable SMP
 * activation entirely (the MPS table probe still happens, though).
 *
 * Command-line option of "maxcpus=<NUM>", where <NUM> is an integer
 * greater than 0, limits the maximum number of CPUs activated in
 * SMP mode to <NUM>.
 */
static int __init nosmp(char *str)
{
	setup_max_cpus = 0;
	arch_disable_smp_support();

	return 0;
}

early_param("nosmp", nosmp);

setup_max_cpus ?: 1 等同于 setup_max_cpus ? setup_max_cpus : 1。当 setup_max_cpus为真时,说明是 SMP 系统,将 i 设置为 setup_max_cpus ;否则,说明是单处理器系统,将 i 设置为 1。我们仅考虑 SMP 系统,此时 setup_max_cpus 大于 0 ,i 等于 setup_max_cpus

	if (setup_possible_cpus == -1) {
		possible = num_processors;
#ifdef CONFIG_HOTPLUG_CPU
		if (setup_max_cpus)
			possible += disabled_cpus;
#else
		if (possible > i)
			possible = i;
#endif
	} else
		possible = setup_possible_cpus;

setup_possible_cpus的默认值为 -1,使用命令行参数 possible_cpus=n 可以修改此值。

如果 setup_possible_cpus == -1,说明没有使用命令行参数,possible 的默认值为 num_processors,即系统实际可用的 CPU 数量。

  • 如果支持热插拔(CONFIG_HOTPLUG_CPU)且 setup_max_cpus 不为 0(SMP 系统),possible的值还要再加上被禁用的 CPU 数量,即 possible += disabled_cpus。此时,possible 的值为实际插入的 CPU 数量。
  • 如果不支持热插拔,当 possible 的值大于 i时,即比启动时激活的 CPU 数量要大,将 possible 修正为 i 的值,即 setup_max_cpus

如果 setup_possible_cpus != -1,说明使用命令行参数对可能的 CPU 数量做了设置,那么 possible 的值即为设置值 setup_possible_cpus

	/* nr_cpu_ids could be reduced via nr_cpus= */
	if (possible > nr_cpu_ids) {
		pr_warn("%d Processors exceeds NR_CPUS limit of %d\n",
			possible, nr_cpu_ids);
		possible = nr_cpu_ids;
	}

如果此时 possible 的值大于系统支持的最大值 nr_cpu_ids,那么将 possible 的值修正为 nr_cpu_ids

#ifdef CONFIG_HOTPLUG_CPU
	if (!setup_max_cpus)
#endif
	if (possible > i) {
		pr_warn("%d Processors exceeds max_cpus limit of %u\n",
			possible, setup_max_cpus);
		possible = i;
	}

如果系统支持热插拔且 setup_max_cpus 为 0(单处理器系统), 当possible 的值大于 i 时,将 possible 的值修正为 i。在单处理器系统下,i 的值为 1,所以此时 possible 值也为 1。

如果系统支持热插拔且 setup_max_cpus 不为 0(SMP 系统), 这段代码什么也不做。

如果系统不支持热插拔,当possible 的值大于 i 时,将 possible 的值修正为 i,即系统启动时激活的处理器数量。对于单处理器系统,该值为 1;对于 SMP 系统,该值为 setup_max_cpus

	for (i = 0; i < possible; i++)
		set_cpu_possible(i, true);
	for (; i < NR_CPUS; i++)
		set_cpu_possible(i, false);

	nr_cpu_ids = possible;

cpu_possible_mask 中所有小于 possible 的位设置为 1,其它位设置为 0。

最后,将 possible 赋值给 nr_cpu_ids,即系统允许的最大 CPU 数量与 possible 一致。

3.4.3 总结

在单处理器系统(setup_max_cpus == 0)下,possible 值为 1;

在 SMP 系统下(setup_max_cpus > 0):

  • 如果 setup_possible_cpus == -1(未使用命令行参数 possible_cpus=n
    • 支持热插拔,则possible = min(num_processors + disabled_cpus, nr_cpu_ids)
    • 不支持热插拔,则 possible = min(num_processors, setup_max_cpus, nr_cpu_ids)
  • 如果 setup_possible_cpus != -1 (使用了命令行参数 possible_cpus=n
    • 支持热插拔的情况下,则 possible = min(setup_possible_cpus, nr_cpu_ids)
    • 不支持热插拔,则 possible = min(setup_possible_cpus, nr_cpu_ids, setup_max_cpus)

possible 计算示意图:

cpumask_possible.png

3.5 查看 CPU 状态

可以在 /sys/devices/system/cpu 目录下查看 CPU 相关信息:

$ pwd
/sys/devices/system/cpu

$ ls -l
total 0
drwxr-xr-x 6 root root    0 Nov 27 03:12 cpu0
drwxr-xr-x 6 root root    0 Nov 27 03:12 cpu1
...
-r--r--r-- 1 root root 4096 Nov 27 03:12 kernel_max
-r--r--r-- 1 root root 4096 Nov 27 03:12 offline
-r--r--r-- 1 root root 4096 Nov 27 03:12 online
-r--r--r-- 1 root root 4096 Nov 27 03:12 possible
-r--r--r-- 1 root root 4096 Nov 27 03:12 present
...

在该目录下,有几个文件,对应着系统中 CPU 的状态信息。各文件说明如下:

  • kernel_max : 内核配置的最大 CPU 的索引值,即 NR_CPUS-1
  • offline:不在线的或超出内核配置的 CPU ,即 ~cpu_online_mask + cpus >= NR_CPUS
  • online:在线的、正在被调度的 CPU,即 cpu_online_mask
  • possible:已经分配资源,如果存在则可以上线的 CPU,即 cpu_possible_mask
  • present:系统中存在且可用的 CPU,即 cpu_present_mask

示例 1

如果系统中有 64 个 CPU,内核配置参数 NR_CPUS 为 32,且 cpu2cpu4 - 31 不在线,那么各文件内容如下:

kernel_max: 31
offline: 2,4-31,32-63
online: 0-1,3
possible: 0-31
present: 0-31

示例 2

如果内核配置参数 NR_CPUS 的值为 128,但是命令行参数 possible_cpus=144。系统中有 4 个 CPU,其中 cpu2 被手动下线,那么各文件内容如下:

kernel_max: 127
offline: 2,4-127,128-143
online: 0-1,3
possible: 0-127
present: 0-3

注:以上 2 个示例均来自内核文档 Documentation/cputopology.txt

示例 3

在我的 2 核虚拟机(内核版本 v4.15.0)上,各文件内容如下:

$ cat kernel_max
8191					
$ cat online
0-1
$ cat possible
0-1
$ cat present
0-1
$ cat offline

3.6 CPU 逻辑下线

/sys/devices/system/cpu 目录下,还存在多个 cpuX 目录,其中 XCPU 编号。cpuX 目录下有个 online 文件:

$ ls -l cpu1
total 0
...
-rw-r--r--  1 root root 4096 Nov 30 16:11 online
...

可以使用如下命令,使 CPU 逻辑下线:

#echo 0 > /sys/devices/system/cpu/cpuX/online

四、APIs

提示:在 cpumask 相关的接口函数中,大量使用了位图(bitmap)相关的接口。位图(bitmap)相关内容请参考:Linux Kernel:内核数据结构之位图(Bitmap)

4.1 宏 cpumask_bits

cpumask_bits 用来将结构体 cpumask 解包装,返回内部的位图成员 bits

/**
 * cpumask_bits - get the bits in a cpumask
 * @maskp: the struct cpumask *
 *
 * You should only assume nr_cpu_ids bits of this mask are valid.  This is
 * a macro so it's const-correct.
 */
#define cpumask_bits(maskp) ((maskp)->bits)

4.2 宏 to_cpumask

to_cpumaskcpumask_bits 的功能相反,用来将位图包装成 cpumask 结构体。

该宏定义如下:

// file: include/linux/cpumask.h
/**
 * to_cpumask - convert an NR_CPUS bitmap to a struct cpumask *
 * @bitmap: the bitmap
 *
 * There are a few places where cpumask_var_t isn't appropriate and
 * static cpumasks must be used (eg. very early boot), yet we don't
 * expose the definition of 'struct cpumask'.
 *
 * This does the conversion, and can be used as a constant initializer.
 */
#define to_cpumask(bitmap)						\
	((struct cpumask *)(1 ? (bitmap)				\
			    : (void *)sizeof(__check_is_bitmap(bitmap))))

因为三元运算符的条件始终为真,所以该宏会将 bitmap 转换成 struct cpumask类型并返回。

__check_is_bitmap 函数的作用是在编译时检查入参类型是否为 unsigned long 指针。如果入参类型错误,在编译时就会报错。

// file: include/linux/cpumask.h
static inline int __check_is_bitmap(const unsigned long *bitmap)
{
	return 1;
}

4.3 宏 cpu_all_mask

cpu_all_mask 用来将全 CPU 位图 cpu_all_bits转换成 cpumask 结构体。

// file: include/linux/cpumask.h
#define cpu_all_mask to_cpumask(cpu_all_bits)

4.4 cpumask_size()

cpumask_size() 函数计算创建 cpumask 结构体时需要分配的字节数,其位图容量为 NR_CPUS

// file: include/linux/cpumask.h
/**
 * cpumask_size - size to allocate for a 'struct cpumask' in bytes
 *
 * This will eventually be a runtime variable, depending on nr_cpu_ids.
 */
static inline size_t cpumask_size(void)
{
	/* FIXME: Once all cpumask assignments are eliminated, this
	 * can be nr_cpumask_bits */
	return BITS_TO_LONGS(NR_CPUS) * sizeof(long);
}

4.5 cpumask_check()

cpumask_check() 函数定义如下:

// file: include/linux/cpumask.h
/* verify cpu argument to cpumask_* operators */
static inline unsigned int cpumask_check(unsigned int cpu)
{
#ifdef CONFIG_DEBUG_PER_CPU_MAPS
	WARN_ON_ONCE(cpu >= nr_cpumask_bits);
#endif /* CONFIG_DEBUG_PER_CPU_MAPS */
	return cpu;
}

如果配置了选项 CONFIG_DEBUG_PER_CPU_MAPS,会检查指定的 CPU 编号是否超过了最大值 nr_cpumask_bits,超过的话会打印警告信息。

最后,会将 CPU 编号原样返回。

4.6 cpumask_set_cpu()

cpumask_set_cpu() 函数接收 2 个参数:

  • cpu -- 要测试的 CPU 编号
  • cpumask -- 掩码指针

该函数调用位图接口 set_bit ,将目的掩码dstpcpu 对应的比特位置位。cpumask_check 函数和 cpumask_bits 函数我们在上文已经介绍过,此处不再赘述。

// file: include/linux/cpumask.h
/**
 * cpumask_set_cpu - set a cpu in a cpumask
 * @cpu: cpu number (< nr_cpu_ids)
 * @dstp: the cpumask pointer
 */
static inline void cpumask_set_cpu(unsigned int cpu, struct cpumask *dstp)
{
	set_bit(cpumask_check(cpu), cpumask_bits(dstp));
}

4.7 cpumask_setall()

cpumask_setall() 函数接收 1 个参数:

  • dstp -- cpumask 结构体指针

该函数调用位图接口 bitmap_fill() ,将目的掩码 dstp 中前 nr_cpumask_bits 个比特位置位(设置为 1)。

// file: include/linux/cpumask.h
/**
 * cpumask_setall - set all cpus (< nr_cpu_ids) in a cpumask
 * @dstp: the cpumask pointer
 */
static inline void cpumask_setall(struct cpumask *dstp)
{
	bitmap_fill(cpumask_bits(dstp), nr_cpumask_bits);
}

4.8 cpumask_clear_cpu()

cpumask_clear_cpu() 函数实现的功能与 cpumask_set_cpu() 函数相反,其内部调用位图接口 clear_bit() 将目的掩码 dstpcpu 对应的比特位清除。

// file: include/linux/cpumask.h
/**
 * cpumask_clear_cpu - clear a cpu in a cpumask
 * @cpu: cpu number (< nr_cpu_ids)
 * @dstp: the cpumask pointer
 */
static inline void cpumask_clear_cpu(int cpu, struct cpumask *dstp)
{
	clear_bit(cpumask_check(cpu), cpumask_bits(dstp));
}

4.9 cpumask_clear()

cpumask_clear() 函数接收 1 个参数:

  • dstp -- cpumask 结构体指针

该函数调用位图接口 bitmap_zero() ,将目的掩码 dstp 中前 nr_cpumask_bits 个比特位清除(设置为 0)。

/**
 * cpumask_clear - clear all cpus (< nr_cpu_ids) in a cpumask
 * @dstp: the cpumask pointer
 */
static inline void cpumask_clear(struct cpumask *dstp)
{
	bitmap_zero(cpumask_bits(dstp), nr_cpumask_bits);
}

4.10 宏 cpumask_test_cpu()

cpumask_test_cpu() 的功能是测试位图掩码 cpumaskcpu 对应的比特位是否被置位。如果置位,返回 1;否则,返回 0。

该宏其定义如下:

// file: include/linux/cpumask.h
/**
 * cpumask_test_cpu - test for a cpu in a cpumask
 * @cpu: cpu number (< nr_cpu_ids)
 * @cpumask: the cpumask pointer
 *
 * Returns 1 if @cpu is set in @cpumask, else returns 0
 *
 * No static inline type checking - see Subtlety (1) above.
 */
#define cpumask_test_cpu(cpu, cpumask) \
	test_bit(cpumask_check(cpu), cpumask_bits((cpumask)))

该函数内部调用位图接口 test_bit() 完成实际的测试工作。

4.11 cpumask_test_and_set_cpu()

该函数将掩码 cpumask 中的指定 cpu 处的比特位设置为 1,并返回旧值。

/**
 * cpumask_test_and_set_cpu - atomically test and set a cpu in a cpumask
 * @cpu: cpu number (< nr_cpu_ids)
 * @cpumask: the cpumask pointer
 *
 * Returns 1 if @cpu is set in old bitmap of @cpumask, else returns 0
 *
 * test_and_set_bit wrapper for cpumasks.
 */
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() 的简单包装。

4.12 cpumask_test_and_clear_cpu()

将掩码 cpumask 中的指定 cpu 处的比特位设置为 0,并返回旧值。该函数是对位图接口 test_and_clear_bit() 的简单包装。

/**
 * cpumask_test_and_clear_cpu - atomically test and clear a cpu in a cpumask
 * @cpu: cpu number (< nr_cpu_ids)
 * @cpumask: the cpumask pointer
 *
 * Returns 1 if @cpu is set in old bitmap of @cpumask, else returns 0
 *
 * test_and_clear_bit wrapper for cpumasks.
 */
static inline int cpumask_test_and_clear_cpu(int cpu, struct cpumask *cpumask)
{
	return test_and_clear_bit(cpumask_check(cpu), cpumask_bits(cpumask));
}

4.13 cpumask_first()

cpumask_first() 函数查找掩码 srcp 中第一个为 1 的比特位,其定义如下:

// file: include/linux/cpumask.h
/**
 * cpumask_first - get the first cpu in a cpumask
 * @srcp: the cpumask pointer
 *
 * Returns >= nr_cpu_ids if no cpus set.
 */
static inline unsigned int cpumask_first(const struct cpumask *srcp)
{
	return find_first_bit(cpumask_bits(srcp), nr_cpumask_bits);
}

内部调用了位图接口 find_first_bit() 完成实际的查找功能。

4.14 cpumask_next()

cpumask_next() 函数查找位图掩码 srcp 中,n 位之后(不包括 n)的第一个为 1 的比特位,该函数定义如下:

// file: include/linux/cpumask.h
/**
 * cpumask_next - get the next cpu in a cpumask
 * @n: the cpu prior to the place to search (ie. return will be > @n)
 * @srcp: the cpumask pointer
 *
 * Returns >= nr_cpu_ids if no further cpus set.
 */
static inline unsigned int cpumask_next(int n, const struct cpumask *srcp)
{
	/* -1 is a legal arg here. */
	if (n != -1)
		cpumask_check(n);
	return find_next_bit(cpumask_bits(srcp), nr_cpumask_bits, n+1);
}

当比特位 n 不等于 -1 时,调用 cpumask_check() 函数检查 n 是否超出最大值。如果超出,会打印警告信息。

然后,调用位图接口 find_next_bit() ,从 n+1 位开始,搜索第一个为 1 的比特位,并返回其索引。

4.15 cpumask_next_zero()

cpumask_next_zero() 函数查找位图掩码 srcp 中,n 位之后(不包括 n)的第一个为 0 的比特位,该函数定义如下:

// file: include/linux/cpumask.h
/**
 * cpumask_next_zero - get the next unset cpu in a cpumask
 * @n: the cpu prior to the place to search (ie. return will be > @n)
 * @srcp: the cpumask pointer
 *
 * Returns >= nr_cpu_ids if no further cpus unset.
 */
static inline unsigned int cpumask_next_zero(int n, const struct cpumask *srcp)
{
	/* -1 is a legal arg here. */
	if (n != -1)
		cpumask_check(n);
	return find_next_zero_bit(cpumask_bits(srcp), nr_cpumask_bits, n+1);
}

当比特位 n 不等于 -1 时,调用 cpumask_check() 函数检查 n 是否超出最大值。如果超出,会打印警告信息。

然后,调用 find_next_zero_bit() 函数,从 n+1 位开始,搜索第一个为 0 的比特位,并返回其索引。

4.16 宏 for_each_cpu()

for_each_cpu() 会遍历位图掩码中所有为 1 的位,其定义如下:

// file: include/linux/cpumask.h
/**
 * for_each_cpu - iterate over every cpu in a mask
 * @cpu: the (optionally unsigned) integer iterator
 * @mask: the cpumask pointer
 *
 * After the loop, cpu is >= nr_cpu_ids.
 */
#define for_each_cpu(cpu, mask)				\
	for ((cpu) = -1;				\
		(cpu) = cpumask_next((cpu), (mask)),	\
		(cpu) < nr_cpu_ids;)

4.17 宏 for_each_cpu_not()

for_each_cpu() 会遍历位图掩码中所有为 0 的位,其定义如下:

/**
 * for_each_cpu_not - iterate over every cpu in a complemented mask
 * @cpu: the (optionally unsigned) integer iterator
 * @mask: the cpumask pointer
 *
 * After the loop, cpu is >= nr_cpu_ids.
 */
#define for_each_cpu_not(cpu, mask)				\
	for ((cpu) = -1;					\
		(cpu) = cpumask_next_zero((cpu), (mask)),	\
		(cpu) < nr_cpu_ids;)

4.18 统计不同状态的处理器数量

4.18.1 num_online_cpus()

4.18.2 num_possible_cpus()

4.18.3 num_present_cpus()

4.18.4 num_active_cpus()

内核中提供了一组宏,用来统计不同状态下的 CPU 数量:

  • num_online_cpus():统计在线 CPU 数量
  • num_possible_cpus():统计系统支持的最大 CPU 数量
  • num_present_cpus():统计已插入并可用的 CPU 数量
  • num_active_cpus():统计 active 状态下的 CPU 数量
// file: include/linux/cpumask.h
#define num_online_cpus()	cpumask_weight(cpu_online_mask)
#define num_possible_cpus()	cpumask_weight(cpu_possible_mask)
#define num_present_cpus()	cpumask_weight(cpu_present_mask)
#define num_active_cpus()	cpumask_weight(cpu_active_mask)

这些宏内部,又调用了位图接口cpumask_weight() 计算出指定位图掩码中为 1 的比特位数量。

// file: include/linux/cpumask.h
/**
 * cpumask_weight - Count of bits in *srcp
 * @srcp: the cpumask to count bits (< nr_cpu_ids) in.
 */
static inline unsigned int cpumask_weight(const struct cpumask *srcp)
{
	return bitmap_weight(cpumask_bits(srcp), nr_cpumask_bits);
}

bitmap_weight 函数用于计算位图中为 1 的比特位数量,其实现详见 :Linux Kernel:内核数据结构之位图(Bitmap)

4.19 测试 CPU 状态

4.19.1 cpu_online()

4.19.2 cpu_possible()

4.19.3 cpu_present()

4.19.4 cpu_active()

内核提供了 4 个宏,用来测试 CPU 的不同状态:

  • cpu_online() -- 测试 CPU 是否处于在线状态
  • cpu_possible() -- 测试 CPU 是否在系统支持的最大范围
  • cpu_present() -- 测试 CPU 是否存在
  • cpu_active() -- 测试 CPU 是否处于 active 状态
// file: include/linux/cpumask.h
#define cpu_online(cpu)		cpumask_test_cpu((cpu), cpu_online_mask)
#define cpu_possible(cpu)	cpumask_test_cpu((cpu), cpu_possible_mask)
#define cpu_present(cpu)	cpumask_test_cpu((cpu), cpu_present_mask)
#define cpu_active(cpu)		cpumask_test_cpu((cpu), cpu_active_mask)

这 4 个宏内部,调用了上文介绍的 cpumask_test_cpu() 函数来完成具体工作。

4.20 设置 CPU 状态

4.20.1 set_cpu_possible()

4.20.2 set_cpu_present()

4.20.3 set_cpu_online()

4.20.4 set_cpu_active()

内核提供了 4 个函数,用来在不同的位图掩码中设置 CPU 的状态。

// file: include/linux/cpumask.h
/* Wrappers for arch boot code to manipulate normally-constant masks */
void set_cpu_possible(unsigned int cpu, bool possible);
void set_cpu_present(unsigned int cpu, bool present);
void set_cpu_online(unsigned int cpu, bool online);
void set_cpu_active(unsigned int cpu, bool active);

这 4 个函数,定义在文件 kernel/cpu.c 中,其实现过程也比较类似:

// file: kernel/cpu.c
void set_cpu_possible(unsigned int cpu, bool possible)
{
	if (possible)
		cpumask_set_cpu(cpu, to_cpumask(cpu_possible_bits));
	else
		cpumask_clear_cpu(cpu, to_cpumask(cpu_possible_bits));
}

void set_cpu_present(unsigned int cpu, bool present)
{
	if (present)
		cpumask_set_cpu(cpu, to_cpumask(cpu_present_bits));
	else
		cpumask_clear_cpu(cpu, to_cpumask(cpu_present_bits));
}

void set_cpu_online(unsigned int cpu, bool online)
{
	if (online)
		cpumask_set_cpu(cpu, to_cpumask(cpu_online_bits));
	else
		cpumask_clear_cpu(cpu, to_cpumask(cpu_online_bits));
}

void set_cpu_active(unsigned int cpu, bool active)
{
	if (active)
		cpumask_set_cpu(cpu, to_cpumask(cpu_active_bits));
	else
		cpumask_clear_cpu(cpu, to_cpumask(cpu_active_bits));
}

我们以 set_cpu_possible() 函数来说明其实现细节,其它函数与之类似。

set_cpu_possible() 函数接收 2 个参数:

  • cpu -- 要设置的 CPU 编号
  • possible -- 要设置的值,布尔类型。

如果 possible 为真,说明是要将位图中 CPU 对应的比特位置位,则调用 cpumask_set_cpu() 函数进行处理;否则,说明是要将位图中 CPU 对应的比特位清除,则调用 cpumask_clear_cpu() 函数进行处理。

4.21 CPU 位图遍历

4.21.1 宏 for_each_possible_cpu()

4.21.2 宏 for_each_online_cpu()

4.21.3 宏 for_each_present_cpu()

内核定义了 3 个宏,用来遍历对应位图掩码中所有置位的比特位:

// file: include/linux/cpumask.h
#define for_each_possible_cpu(cpu) for_each_cpu((cpu), cpu_possible_mask)
#define for_each_online_cpu(cpu)   for_each_cpu((cpu), cpu_online_mask)
#define for_each_present_cpu(cpu)  for_each_cpu((cpu), cpu_present_mask)

这 3 个宏内部又调用了 for_each_cpu 宏完成遍历工作,该宏实现详见上文。

4.22 宏 cpu_none_mask

cpu_none_mask 将位图 cpu_bit_bitmap[0] 转换成 cpumask 结构体。cpu_bit_bitmap[0] 中的所有比特位全部为 0,相当于一个空的位图。

// file: include/linux/cpumask.h
/* First bits of cpu_bit_bitmap are in fact unset. */
#define cpu_none_mask to_cpumask(cpu_bit_bitmap[0])

4.23 宏 cpumask_of() / get_cpu_mask() 函数

cpumask_of() 接收 1 个参数,即 CPU 编号;返回一个 CPU 位图掩码,该掩码里只有CPU 对应的比特位置位,其余比特位全部为 0。该宏定义如下:

// file: include/linux/cpumask.h
/**
 * cpumask_of - the cpumask containing just a given cpu
 * @cpu: the cpu (<= nr_cpu_ids)
 */
#define cpumask_of(cpu) (get_cpu_mask(cpu))

cpumask_of() 内部调用了 get_cpu_mask() 函数,其实现如下:

// file: include/linux/cpumask.h
static inline const struct cpumask *get_cpu_mask(unsigned int cpu)
{
	const unsigned long *p = cpu_bit_bitmap[1 + cpu % BITS_PER_LONG];
	p -= cpu / BITS_PER_LONG;
	return to_cpumask(p);
}

可以看到,get_cpu_mask() 函数是从cpu_bit_bitmap 中截取一段作为掩码返回的。我们在上文已经介绍过,cpu_bit_bitmap 是一个拥有 65 个成员的数组,每个数组成员是容量为 NR_CPUS 的位图。cpu_bit_bitmap 的特殊之处在于,除索引为 0 的成员外,其它每个成员都只有一个位被置位,且有如下规律:

cpu_bit_bitmap[][] = {
    [1][0] = 1 << 0,
    [2][0] = 1 << 1,
    [3][0] = 1 << 2,
    [4][0] = 1 << 3,
    ...
    [64][0] = 1 << 63
}

由于位图是使用 unsigned long 数组来表示的, cpu % BITS_PER_LONG 得到 cpu 在某个数组成员中的比特位索引。由于我们要返回 cpu 对应位为 1 的位图,在 cpu_bit_bitmap 的 65 个 成员中,只有 cpu_bit_bitmap[1 + cpu % BITS_PER_LONG]cpu 对应的比特位是 1,所以从该地址向后推进 cpu / BITS_PER_LONG 个数组成员,就得到位图的起始地址,该位图的 cpu 位为 1。

比如,当 cpu 为 68 时,它肯定位于某个成员的第 4(68 % 64) 位;cpu_bit_bitmap[5] 的第 4 位为 1;我们往后推进 68 / 64 个成员(即 1 个成员,64位),就得到位图起始地址。该位图的第 68 位为 1。

最后调用 to_cpumask() 函数将位图转换成 struct cpumask 类型并返回。

get_cpu_mask 示意图:

get_cpu_mask.png

五、CPU 状态的变化

5.1 通知链(notifier chains)机制简介

通知链机制是内核中的异步事件通知机制,当订阅者收到事件通知时,会执行相应的回调函数。

通知链分为 4 种类型:Atomic notifier chains,Blocking notifier chains,Raw notifier chains,SRCU notifier chains。

内核注释中,对这 4 种通知链说明如下:

// file: include/linux/notifier.h
/*
 * Notifier chains are of four types:
 *
 *	Atomic notifier chains: Chain callbacks run in interrupt/atomic
 *		context. Callouts are not allowed to block.
 *	Blocking notifier chains: Chain callbacks run in process context.
 *		Callouts are allowed to block.
 *	Raw notifier chains: There are no restrictions on callbacks,
 *		registration, or unregistration.  All locking and protection
 *		must be provided by the caller.
 *	SRCU notifier chains: A variant of blocking notifier chains, with
 *		the same restrictions.
 *
 * atomic_notifier_chain_register() may be called from an atomic context,
 * but blocking_notifier_chain_register() and srcu_notifier_chain_register()
 * must be called from a process context.  Ditto for the corresponding
 * _unregister() routines.
 *
 * atomic_notifier_chain_unregister(), blocking_notifier_chain_unregister(),
 * and srcu_notifier_chain_unregister() _must not_ be called from within
 * the call chain.
 *
 * SRCU notifier chains are an alternative form of blocking notifier chains.
 * They use SRCU (Sleepable Read-Copy Update) instead of rw-semaphores for
 * protection of the chain links.  This means there is _very_ low overhead
 * in srcu_notifier_call_chain(): no cache bounces and no memory barriers.
 * As compensation, srcu_notifier_chain_unregister() is rather expensive.
 * SRCU notifier chains should be used when the chain will be called very
 * often but notifier_blocks will seldom be removed.  Also, SRCU notifier
 * chains are slightly more difficult to use because they require special
 * runtime initialization.
 */

在本文中,只关注 Raw notifier chains。

5.1.1 数据结构

通知链是由不同链块组成的单向链表,链表头和链表块分别有不同的数据类型。

链表头为 raw_notifier_head 结构体类型,该结构体只有一个指向链块(notifier_block)的指针 head

// file: include/linux/notifier.h
struct raw_notifier_head {
	struct notifier_block __rcu *head;
};

链块为 notifier_block 结构体类型,其定义如下:

// file: include/linux/notifier.h
struct notifier_block {
	notifier_fn_t notifier_call;
	struct notifier_block __rcu *next;
	int priority;
};

链块包含三个成员:

  • notifier_call -- 回调函数的指针;
  • next -- 指向下一个链块的指针;
  • priority -- 链块的优先级,决定链块在链表中的位置,优先级越高,位置越靠前。

回调函数 notifier_callnotifier_fn_t 类型,该类型定义如下:

// file: include/linux/notifier.h
typedef	int (*notifier_fn_t)(struct notifier_block *nb,
			unsigned long action, void *data);

该类型函数接收 3 个参数:

  • nb -- 链块指针;
  • action -- 事件类型。在通知链中,包含多种事件的回调函数。该参数用来区分不同的事件。
  • data -- 附加的数据。

5.1.2 链表头的初始化

RAW_NOTIFIER_HEAD 用来对 raw 类型通知链的表头进行初始化,该宏定义如下:

// file: include/linux/notifier.h
#define RAW_NOTIFIER_HEAD(name)					\
	struct raw_notifier_head name =				\
		RAW_NOTIFIER_INIT(name)

该宏内部又引用了宏 RAW_NOTIFIER_INIT,其定义如下:

// file: include/linux/notifier.h
#define RAW_NOTIFIER_INIT(name)	{				\
		.head = NULL }

CPU 通知链的表头定义如下:

// kernel/cpu.c
static RAW_NOTIFIER_HEAD(cpu_chain);

使用宏 RAW_NOTIFIER_HEAD定义了一个名称为 cpu_chain 的链表头。

5.1.3 APIs

注册函数 -- raw_notifier_chain_register()

raw_notifier_chain_register() 函数接收 2 个参数:

  • nh -- 链表头指针
  • n -- 要注册的链表块指针

该函数会将 n 指向的链表块插入以 nh 为表头的链表中,该函数定义如下:

/*
 *	Raw notifier chain routines.  There is no protection;
 *	the caller must provide it.  Use at your own risk!
 */

/**
 *	raw_notifier_chain_register - Add notifier to a raw notifier chain
 *	@nh: Pointer to head of the raw notifier chain
 *	@n: New entry in notifier chain
 *
 *	Adds a notifier to a raw notifier chain.
 *	All locking must be provided by the caller.
 *
 *	Currently always returns zero.
 */
int raw_notifier_chain_register(struct raw_notifier_head *nh,
		struct notifier_block *n)
{
	return notifier_chain_register(&nh->head, n);
}

该函数内部,会调用 notifier_chain_register() 函数完成实际的注册工作:

// file: kernel/notifier.c
/*
 *	Notifier chain core routines.  The exported routines below
 *	are layered on top of these, with appropriate locking added.
 */

static int notifier_chain_register(struct notifier_block **nl,
		struct notifier_block *n)
{
	while ((*nl) != NULL) {
		if (n->priority > (*nl)->priority)
			break;
		nl = &((*nl)->next);
	}
	n->next = *nl;
	rcu_assign_pointer(*nl, n);
	return 0;
}

notifier_chain_register()函数会遍历链表,将链表块插入到链表中。连标块的优先级越大,在链表中的位置越靠前。也就是说,链表元素是按照优先级从大到小的顺序排列的。

执行回调函数 -- raw_notifier_call_chain()

当发出事件通知时,最终会调用 raw_notifier_call_chain() 函数来执行通知链中的回调函数。

raw_notifier_call_chain() 函数定义如下:

// file: kernel/notifier.c
int raw_notifier_call_chain(struct raw_notifier_head *nh,
		unsigned long val, void *v)
{
	return __raw_notifier_call_chain(nh, val, v, -1, NULL);
}

该函数接收 3 个参数:

  • nh -- 通知链的表头
  • val -- 通知类型
  • v -- 回调函数所用的参数

该函数内部又调用了 __raw_notifier_call_chain() 函数来完成实际功能。

__raw_notifier_call_chain() 函数定义如下:

// file: kernel/notifier.c
/**
 *	__raw_notifier_call_chain - Call functions in a raw notifier chain
 *	@nh: Pointer to head of the raw notifier chain
 *	@val: Value passed unmodified to notifier function
 *	@v: Pointer passed unmodified to notifier function
 *	@nr_to_call: See comment for notifier_call_chain.
 *	@nr_calls: See comment for notifier_call_chain
 *
 *	Calls each function in a notifier chain in turn.  The functions
 *	run in an undefined context.
 *	All locking must be provided by the caller.
 *
 *	If the return value of the notifier can be and'ed
 *	with %NOTIFY_STOP_MASK then raw_notifier_call_chain()
 *	will return immediately, with the return value of
 *	the notifier function which halted execution.
 *	Otherwise the return value is the return value
 *	of the last notifier function called.
 */
int __raw_notifier_call_chain(struct raw_notifier_head *nh,
			      unsigned long val, void *v,
			      int nr_to_call, int *nr_calls)
{
	return notifier_call_chain(&nh->head, val, v, nr_to_call, nr_calls);
}

__raw_notifier_call_chain() 函数接收 5 个参数:

  • nh -- 链表头指针
  • val -- 通知类型
  • v -- 回调函数参数
  • nr_to_call -- 回调函数被调用的次数,为 -1 时表示不做限制
  • nr_calls -- 用于记录通知发送的次数

__raw_notifier_call_chain() 函数内部调用 notifier_call_chain() 函数来执行回调。 notifier_call_chain() 函数定义如下:

// file: kernel/notifier.c
/**
 * notifier_call_chain - Informs the registered notifiers about an event.
 *	@nl:		Pointer to head of the blocking notifier chain
 *	@val:		Value passed unmodified to notifier function
 *	@v:		Pointer passed unmodified to notifier function
 *	@nr_to_call:	Number of notifier functions to be called. Don't care
 *			value of this parameter is -1.
 *	@nr_calls:	Records the number of notifications sent. Don't care
 *			value of this field is NULL.
 *	@returns:	notifier_call_chain returns the value returned by the
 *			last notifier function called.
 */
static int __kprobes notifier_call_chain(struct notifier_block **nl,
					unsigned long val, void *v,
					int nr_to_call,	int *nr_calls)
{
	int ret = NOTIFY_DONE;
	struct notifier_block *nb, *next_nb;

	nb = rcu_dereference_raw(*nl);

	while (nb && nr_to_call) {
		next_nb = rcu_dereference_raw(nb->next);

		...
            
		ret = nb->notifier_call(nb, val, v);

		if (nr_calls)
			(*nr_calls)++;

		if ((ret & NOTIFY_STOP_MASK) == NOTIFY_STOP_MASK)
			break;
		nb = next_nb;
		nr_to_call--;
	}
	return ret;
}

notifier_call_chain() 函数执行时,会遍历链表,逐个执行链表块中注册的回调函数,直到达到 nr_to_call 限制的次数或遍历到链表末尾。在回调函数中,会判断消息类型,如果不是自己关注的消息,不做任何处理,直接返回。

5.2 BSP 掩码位的填充 -- boot_cpu_init()

BSP(Bootstrap Processor)启动时,在 boot_cpu_init() 函数中,将该 CPU 对应的各状态位置位。

// file: init/main.c
/*
 *	Activate the first processor.
 */

static void __init boot_cpu_init(void)
{
	int cpu = smp_processor_id();
	/* Mark the boot cpu "present", "online" etc for SMP and UP case */
	set_cpu_online(cpu, true);
	set_cpu_active(cpu, true);
	set_cpu_present(cpu, true);
	set_cpu_possible(cpu, true);
}

5.3 AP 掩码位的填充 -- smp_init()

在系统初始化后期,BSP 会调用 smp_init() 函数启动其它的处理器(Application Processor,AP)。在 smp_init() 函数内部,会调用 for_each_present_cpu() 函数,遍历 cpu_present_mask 中被置位的位,如果比特位对应的 CPU 没有上线,即 !cpu_online(cpu)true,则调用 cpu_up() 函数启动该 CPU 。另外,注意到,当在线的 CPU 数量大于等于setup_max_cpus 时,直接跳出循环。还记得么,setup_max_cpus 限制了系统启动的 CPU 数量,其默认值是内核配置 CONFIG_NR_CPUS ,但可以通过命令行参数 maxcpus 来修改。

// file: kernel/smp.c
/* Called by boot processor to activate the rest. */
void __init smp_init(void)
{
	...

	/* FIXME: This should be done in userspace --RR */
	for_each_present_cpu(cpu) {
		if (num_online_cpus() >= setup_max_cpus)
			break;
		if (!cpu_online(cpu))
			cpu_up(cpu);
	}

	...
}

系统启动时,除了 BSP 外,其它处理器都处于 halt 状态。BSP 调用 cpu_up() 函数,通知其它处理器(AP)启动。AP 启动后,会执行 start_secondary() 函数,该函数内部,会调用 smp_callin() 函数以及 set_cpu_online() 函数。在 smp_callin() 函数中,会执行回调函数 sched_cpu_active(),将 CPU 设置为 active 状态;然后执行set_cpu_online()函数将 CPU 设置为 online 状态。

// arch/x86/kernel/smpboot.c
/*
 * Activate a secondary processor.
 */
notrace static void __cpuinit start_secondary(void *unused)
{
	...
    smp_callin();
    ...
    set_cpu_online(smp_processor_id(), true);
	...
}

smp_callin() 函数内部会调用notify_cpu_starting() 函数,来执行事件回调函数。

// file: arch/x86/kernel/smpboot.c
/*
 * Report back to the Boot Processor during boot time or to the caller processor
 * during CPU online.
 */
static void __cpuinit smp_callin(void)
{
    ...
        
    notify_cpu_starting(cpuid);
    
    ...
}

notify_cpu_starting()函数内部,会触发 CPU_STARTING 事件,并执行系统启动时注册的事件回调函数。

// file: kernel/cpu.c
/**
 * notify_cpu_starting(cpu) - call the CPU_STARTING notifiers
 * @cpu: cpu that just started
 *
 * This function calls the cpu_chain notifiers with CPU_STARTING.
 * It must be called by the arch code on the new cpu, before the new cpu
 * enables interrupts and before the "boot" cpu returns from __cpu_up().
 */
void __cpuinit notify_cpu_starting(unsigned int cpu)
{
	unsigned long val = CPU_STARTING;

	...
        
	cpu_notify(val, (void *)(long)cpu);
}

cpu_notify() 函数内部,调用了 __cpu_notify() 函数:

// file: kernel/cpu.c
static int cpu_notify(unsigned long val, void *v)
{
	return __cpu_notify(val, v, -1, NULL);
}

__cpu_notify() 函数,调用了 __raw_notifier_call_chain() 函数:

// file: kernel/cpu.c
static int __cpu_notify(unsigned long val, void *v, int nr_to_call,
			int *nr_calls)
{
	int ret;

	ret = __raw_notifier_call_chain(&cpu_chain, val, v, nr_to_call,
					nr_calls);

	return notifier_to_errno(ret);
}

__raw_notifier_call_chain() 函数,又调用了 notifier_call_chain() 函数。在 notifier_call_chain() 函数中,会遍历链表中的每一个成员,执行成员中的回调函数。

__raw_notifier_call_chain() 函数及 __raw_notifier_call_chain() 函数的实现,见 "5.1 通知链机制简介"。

5.3.1 sched_cpu_active() 回调函数

CPU_STARTING 事件的回调函数为 sched_cpu_active,该函数会调用 set_cpu_active()函数,将 CPU 设置为 acitve 状态。

// file: kernel/sched/core.c
static int __cpuinit sched_cpu_active(struct notifier_block *nfb,
				      unsigned long action, void *hcpu)
{
	switch (action & ~CPU_TASKS_FROZEN) {
	case CPU_STARTING:
	case CPU_DOWN_FAILED:
		set_cpu_active((long)hcpu, true);
		return NOTIFY_OK;
	default:
		return NOTIFY_DONE;
	}
}

该回调函数是何时注册的呢?在内核启动时,通过 early_initcall() 注册了 migration_init() 函数;在 migration_init() 函数中,调用 cpu_notifier() 函数注册了 CPU_STARTING 事件的回调函数 sched_cpu_active();与其一起注册的,还有 CPU_DOWN_PREPARE 事件的回调函数 sched_cpu_inactive()

// file: kernel/sched/core.c
static int __init migration_init(void)
{
	...

	/* Register cpu active notifiers */
	cpu_notifier(sched_cpu_active, CPU_PRI_SCHED_ACTIVE);
	cpu_notifier(sched_cpu_inactive, CPU_PRI_SCHED_INACTIVE);

	...
}
early_initcall(migration_init);

通过 early_initcall() 注册的函数,会在系统启动时在 do_pre_smp_initcalls() 函数执行时被调用,其调用链路为:

start_kernel() -> rest_init() -> kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND) -> kernel_init() -> kernel_init_freeable() ->do_pre_smp_initcalls()

// file: init/main.c
static void __init do_pre_smp_initcalls(void)
{
	initcall_t *fn;

	for (fn = __initcall_start; fn < __initcall0_start; fn++)
		do_one_initcall(*fn);
}

do_pre_smp_initcalls() 函数中,通过 early_initcall() 注册的每一个函数都会被执行。

另外,由于 do_pre_smp_initcalls() 函数在 smp_init() 之前执行,所以在初始化 SMP 系统之前,sched_cpu_active() sched_cpu_inactive() 函数已经被注册到通知链中了。

migration_init() 函数中,宏 CPU_PRI_SCHED_ACTIVECPU_PRI_SCHED_INACTIVE 是消息优先级,数值越高,优先级越大,在通知链中的位置越靠前,越先被执行。

cpu_notifier() 宏定义如下:

// file: include/linux/cpu.h
#define cpu_notifier(fn, pri) {					\
	static struct notifier_block fn##_nb __cpuinitdata =	\
		{ .notifier_call = fn, .priority = pri };	\
	register_cpu_notifier(&fn##_nb);			\
}

cpu_notifier() 宏将回调函数 fn 以及事件优先级 pri 组合成链块 notifier_block,然后调用 register_cpu_notifier()函数将回调函数注册到通知链中。

register_cpu_notifier() 函数内部调用 raw_notifier_chain_register() 将完成实际的注册工作:

// file: kernel/cpu.c
/* Need to know about CPUs going up/down? */
int __ref register_cpu_notifier(struct notifier_block *nb)
{
	...
	ret = raw_notifier_chain_register(&cpu_chain, nb);
	...
}

raw_notifier_chain_register() 函数的具体实现请参考 “5.1 通知链机制简介”。

SMP 系统启动过程中,与 CPU 状态变化相关的函数:

cpu_up.png

注意,红色背景区的函数修改了 CPU 的状态。notify_cpu_starting() 函数,将 CPU 设置为 active 状态,set_cpu_online() 函数将 CPU 设置为 online 状态。

5.4 掩码位的清除 -- cpu_down()

当处理器需要下线时,会调用 cpu_down() 函数。

// file: kernel/cpu.c
int __ref cpu_down(unsigned int cpu)
{
	...

	err = _cpu_down(cpu, 0);

	...
}

cpu_down() 函数内部,会调用 _cpu_down() 函数。

// file: kernel/cpu.c
/* Requires cpu_add_remove_lock to be held */
static int __ref _cpu_down(unsigned int cpu, int tasks_frozen)
{
	...

	err = __cpu_notify(CPU_DOWN_PREPARE | mod, hcpu, -1, &nr_calls);
	if (err) {
		...
		__cpu_notify(CPU_DOWN_FAILED | mod, hcpu, nr_calls, NULL);
		...
	}
	...

	err = __stop_machine(take_cpu_down, &tcd_param, cpumask_of(cpu));
	if (err) {
		...
		cpu_notify_nofail(CPU_DOWN_FAILED | mod, hcpu);
		...
	}

	...

	/* CPU is completely dead: tell everyone.  Too late to complain. */
	cpu_notify_nofail(CPU_DEAD | mod, hcpu);

	

out_release:
	...
	if (!err)
		cpu_notify_nofail(CPU_POST_DEAD | mod, hcpu);
	return err;
}

_cpu_down() 函数内部,首先调用 __cpu_notify() 函数,发出 CPU_DOWN_PREPARE事件通知。我们在上文介绍过,CPU_DOWN_PREPARE事件的回调函数是 sched_cpu_inactive() 函数,该函数定义如下:

// file: kernel/sched/core.c
static int __cpuinit sched_cpu_inactive(struct notifier_block *nfb,
					unsigned long action, void *hcpu)
{
	switch (action & ~CPU_TASKS_FROZEN) {
	case CPU_DOWN_PREPARE:
		set_cpu_active((long)hcpu, false);
		return NOTIFY_OK;
	default:
		return NOTIFY_DONE;
	}
}

sched_cpu_inactive()函数内部,会调用 set_cpu_active()函数,将 cpu_active_mask 中对应 CPU 的比特位清除。

接下来执行 __stop_machine() 函数,该函数声明如下:

// file: include/linux/stop_machine.h
/**
 * __stop_machine: freeze the machine on all CPUs and run this function
 * @fn: the function to run
 * @data: the data ptr for the @fn
 * @cpus: the cpus to run the @fn() on (NULL = any online cpu)
 *
 * Description: This is a special version of the above, which assumes cpus
 * won't come or go while it's being called.  Used by hotplug cpu.
 */
int __stop_machine(int (*fn)(void *), void *data, const struct cpumask *cpus);

__stop_machine() 接收 3 个参数:

  • fn -- 需要运行的函数
  • data -- fn 函数的参数
  • cpus -- 允许执行 fn 函数的 cpu 集合

在我们的案例中,会执行take_cpu_down() 函数,其定义如下:

// file: kernel/cpu.c
/* Take this CPU down. */
static int __ref take_cpu_down(void *_param)
{
	struct take_cpu_down_param *param = _param;
	int err;

	/* Ensure this CPU doesn't handle any more interrupts. */
	err = __cpu_disable();
	if (err < 0)
		return err;

	cpu_notify(CPU_DYING | param->mod, param->hcpu);
	/* Park the stopper thread */
	kthread_park(current);
	return 0;
}

take_cpu_down() 函数内,会执行 __cpu_disable() 函数并调用 cpu_notify() 发出 CPU_DYING 事件通知。

__cpu_disable() 函数内,会执行 cpu_disable() 函数:

// file: arch/x86/include/asm/smp.h
static inline int __cpu_disable(void)
{
	return smp_ops.cpu_disable();
}

smp_ops 结构体定义了 SMP 系统的不同操作,其中 cpu_disable 操作实际执行的是 native_cpu_disable() 函数。

// file: arch/x86/kernel/smp.c
struct smp_ops smp_ops = {
	...
	.cpu_disable		= native_cpu_disable,
	...
};

native_cpu_disable() 函数定义如下:

// file: arch/x86/kernel/smpboot.c
int native_cpu_disable(void)
{
	clear_local_APIC();

	cpu_disable_common();
	return 0;
}

native_cpu_disable() 函数内会调用 cpu_disable_common() 函数,其定义如下:

// file: 
void cpu_disable_common(void)
{
	int cpu = smp_processor_id();

	...
	remove_cpu_from_maps(cpu);
	...
}

cpu_disable_common() 函数内会调用 remove_cpu_from_maps() 函数:

static void __ref remove_cpu_from_maps(int cpu)
{
	set_cpu_online(cpu, false);
	...
}

remove_cpu_from_maps() 函数内,会调用 set_cpu_online() 函数,将指定 cpu 修改为下线状态。

cpu_down() 函数中,与 CPU 状态变化有关的函数如下图所示:

cpu_down.png

其中,红色背景的函数会修改 CPU 的状态。CPU_DOWN_PREPARE 事件会清除 CPUactive 状态;__cpu_disable() 函数最终会调用 set_cpu_online() 函数清除 CPUonline 状态。

5.5 cpu_online 与 cpu_active 的区别

在前面的小节中,我们看到,CPU 启动时,先是达到 active 状态,然后才达到 online 状态。在移除 CPU 时,先是清除 active 状态,然后才清除 online 状态。在启动时,先达到 active 状态,说明此时 CPU 活跃的,可以从其它处理器迁移一些任务过来,然后打开 online 状态,就可以调度任务了。在移除 CPU 时,先清除 active 状态,实际是告诉内核,我要下线了,不要再给我分配任务了;而且,下线之前还有些工作要做,比如说把自己任务队列的任务迁移给其它处理器,等这些准备工作都做完了,就可以清除 online 状态,变成下线状态了。

六、参考资料

1、CPU hotplug in the Kernel

2、CPU topology

3、CPU masks

4、Linux Kernel:内核数据结构之位图(Bitmap)

5、Cleaning Up Linux’s CPU Hotplug For Real Time and Energy Management