![4f29d36874ff3684db135d306e6afc25.png](https://img-blog.csdnimg.cn/img_convert/4f29d36874ff3684db135d306e6afc25.png)
内核版本
- linux-v5.7
今天在阅读内核源码时,对get_cpu_mask()函数中使用的小技巧颇有感触,在此记录一下。
一、背景知识
get_cpu_mask()函数的作用是依据输入的cpu编号,返回对应的struct cpumask结构体型指针;cpumask是代表了系统中cpu集合的位图,每一bit个代表一个cpu。
可用以下两种方法创建cpumask:
- 第一种是使用struct cpumask或者其别名cpumask_t定义一个变量,结构体定义如下:
typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;
struct cpumask结构体中只有一个成员bits,为一个bitmap;DECLARE_BITMAP宏定义入如下:
#define DECLARE_BITMAP(name,bits)
/* BITS_TO_LONGS宏的作用为计算bits需要几个long空间存储:
* 1、64位系统中long为8bytes,即64bits
* 2、传入的bits范围在[1,64] ==> 返回1
* 3、传入的bits范围在[65,128] ==> 返回2,以此类推
*/
unsigned long name[BITS_TO_LONGS(bits)]
可见,bits本质为一个数组,数组大小由编译时设置的cpu个数NR_CPUS决定。
- 第二种方法是先定义一个unsigned long型数组,然后使用to_cpumask宏转换为struct cpumask,其中to_cpumask宏定义如下:
#define to_cpumask(bitmap)
((struct cpumask *)(1 ? (bitmap)
: (void *)sizeof(__check_is_bitmap(bitmap))))
可见,在运行时,该三目运算符永远返回bitmap,然后直接强制转换为struct cpumask *型;那么三目运算符后面的式子是干嘛的呢?先看__check_is_bitmap()函数定义:
static inline int __check_is_bitmap(const unsigned long *bitmap)
{
return 1;
}
更加迷惑了,直接返回1;其实其不作用在运行时,而是在编译阶段检测传入的bitmap是否为unsigned long *类型,如不是则会产生编译警告。
二、get_cpu_mask实现
接下来是本文的主角:get_cpu_mask()函数,先贴出代码实现:
/* BITS_PER_LONG:long型变量所占的bit数,64位系统下为64bits */
extern const unsigned long
cpu_bit_bitmap[BITS_PER_LONG+1][BITS_TO_LONGS(NR_CPUS)];
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);
}
代码很简洁,只有三行,先来看cpu_bit_bitmap数组,其为一个二维数组,第一维大小固定为65,第二位与struct cpumask一样,依据NR_CPUS值而定,具体定义如下:
#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)
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
};
EXPORT_SYMBOL_GPL(cpu_bit_bitmap);
将宏全部展开(使用gcc进行预处理),即为:
const unsigned long cpu_bit_bitmap[BITS_PER_LONG+1][BITS_TO_LONGS(NR_CPUS)] = {
[0 +1][0] = (1UL << (0)), [0 +1 +1][0] = (1UL << (0 +1)),
[0 +2 +1][0] = (1UL << (0 +2)), [0 +2 +1 +1][0] = (1UL << (0 +2 +1)),
...
[56 +4 +2 +1][0] = (1UL << (56 +4 +2)), [56 +4 +2 +1 +1][0] = (1UL << (56 +4 +2 +1)),
};
只对cpu_bit_bitmap数组中的cpu_bit_bitmap[x][0]进行了初始化(x范围为[1,64]),假设传入的NR_CPUS范围在[65,128],数组定义即为cpu_bit_bitmap[65][2],其在内存中的布局如下:
![71d6e79ce1ccb5ee5ccd3c9c85ad8f6c.png](https://img-blog.csdnimg.cn/img_convert/71d6e79ce1ccb5ee5ccd3c9c85ad8f6c.png)
蓝色部分为赋了初值的部分,初始值大小为1UL<<(x-1);灰色部分为未赋初值的部分;虚线部分的cpu_bit_bitmap[0]未赋初值。
再回头看get_cpu_mask()函数实现,举例说明如下:
- 假设传入的cpu编号为1,get_cpu_mask()函数处理如下:
const unsigned long *p = cpu_bit_bitmap[1 + 1];
p -= 0;
return to_cpumask(p);
返回的cpumask如图所示:
![20ba0c5bc85727f7d24cc3cdb8e4da64.png](https://img-blog.csdnimg.cn/img_convert/20ba0c5bc85727f7d24cc3cdb8e4da64.png)
cpumask中第二个bit置位,即代表cpu1。
- 假设传入的cpu编号为65,get_cpu_mask()函数处理如下:
/* 初始值还是cpu_bit_bitmap[2] */
const unsigned long *p = cpu_bit_bitmap[1 + 1];
/* 因指针p指向的数据类型为unsigned long型,所以此处p指向的地址
* 需减去1*sizeof(unsigned long),64为系统中即为8
*/
p -= 1;
return to_cpumask(p);
返回的cpumask如图所示:
![f186d6b4b925b556aae068c1b8dd87f6.png](https://img-blog.csdnimg.cn/img_convert/f186d6b4b925b556aae068c1b8dd87f6.png)
cpumask中第65个bit置位,即代表cpu65。
当NR_CPUS为[129,192],传入的cpu编号在[128,191]时,p指向的地址需减去2*sizeof(unsigned long);当NR_CPUS为[193,256],传入的cpu编号在[192,255]时,p指向的地址需减去3*sizeof(unsigned long),依此类推;另外cpu_bit_bitmap[0]为何不赋初值也就显而易见了。