卷一 内核源代码分析 第一章 linux内核对cortex A9多核处理器的支持 1.1 CPU的基本管理 图书试读版-请勿转载

来源:互联网 发布:帽子淘宝网 编辑:程序博客网 时间:2024/04/27 17:15


作者 crosskernel@gmail.com


1 多核处理器


计算性能是处理器演进的第一动力,然而,尽管各种架构的高性能处理器层出不穷,真正大规模普及开来的似乎只有Intel和ARM体系。我们观察其中的现象不难发现如下规律:
如果处理器性能得到大幅改善,但是无法得到现有主流操作系统的支持,就无法大规模应用。进一步来讲,即使某种处理器得到主流操作系统的支持,但是由于其指令集的不兼容性,导致大量的应用无法运行,这种处理器也是难以普及的,安腾的失败是个很好的例子。
“兼容性”是处理器演进的第一法则。现有的软件体系是计算机世界的主宰,所有不服从现在软件体系的的指令集都只能被边缘化或者被淘汰。所以我们看到Intel和ARM不断扩展寄存器长度、增加发射单元、支持乱序、扩展总线单元等一系列手段来改进处理器架构,就是不敢废弃一条既有指令。
降低计算功耗是处理器演进的第二动力,对于服务器来说功耗就是运营成本,对于移动计算来说功耗就是生命。似乎台式机功耗要求不大,但是台式机与服务器、移动设备不仅在处理器的生产及设计上共享基础设施外,软件体系也有着相同的基础,导致整个软件体系为了照顾移动设备和服务器而收敛了扩张的步伐。这就是人们常说的“计算过剩”,其实计算永远不会过剩,只是庞大的软件体系有所收敛罢了。
频率提升是功耗的大敌,为了提高能耗比,设计频率更低而核心更多的处理器是好的方法,由此,近年来处理器的扩张由单核高频的转变为多核体系。
Linux支持的多核体系的基本特点为,所有处理器拥有完全相同的指令集,所有处理器共享同一内存。如果不符合以上任何一点,操作系统内核都需要做架构级修改才能支持。若满足这两个前提,其他方面的问题,都可以通过内核和系统小规模修改来支持。如Big.little架构下互相搭配的处理器尽管核心内部实现不同,但各核心有着相同的指令集,Linux内核可以将它们当作同样的处理器来分配线程。内核在完全不加改动的情况下,也许会出现一个重量级线程被分配到了一个little的核心上,但是内核本身可以通过处理器负载均衡将其平衡到别的处理器上。当liittle负载较高时big必定会上线运行,但是系统有可能会出现重负载线程独自霸占liittle的情形的极限情况,这时需要将调度算法稍加修改,首先记录每个处理器能力,然后监测little上单线程高负载发生的时长,若超出一定阀值则将高负载线程时间片减为零,从little摘下来,再将其挂到big处理器运行队列即可。
进一步讲,多核心之间只要基本的读写、跳转之类指令相同,其余指令集不同,Linux也可以支持的。内核只需用到基本指令即可,各核心用户空间指令可以不同。内核需做如下修改,每颗核心的调度队列记录下其核心用户态指令集特性,每个线程创建之初申明自身需要的指令集特性,内核在给线程分配核心及做负载均衡时比较两者是否匹配。用户层动态加载器也要做修改,针对当前处理器指令集特性动态加载、跳转到不同版本的动态库中,且在动态库执行过程中标记自身线程不可切换处理器。当然这种复杂情况超出了当前现状,尽管有用户态指令集不同的架构,但是运行指令集差集的线程不需要动态加载库支持,且直接绑定到指定核心上。




1.1 CPU的基本管理


  CPU管理的特点是自我管理,除了在启动、休眠、调频受控于CPU0的工作以外,处理器相关的绝大部分工作都是由处理器自我管理的。处理器是内核的执行体,又被内核控制。内核中准备了表征处理器运行状态的相应数据结构,处理器在运行时将自己的状态记录在这些结构中,而处理器也能通过别的处理器的表征结构了解其他处理器状态或发起控制。


通用cpu结构,每颗cpu都有对应一个
struct cpu {
/*cpu编号*/
int node_id;
/*对于arm smp 该值为真,表示每颗cpu都可以被关掉*/
int hotpluggable;
/*正如每个外设都有一个“struct device”表示自身一样,cpu也不例外*/
struct device dev;
 };
Arm core的表示,包含了通用cpu,在ARM初始化时会依次初始化并注册每个“struct cpuinfo_arm。”
struct cpuinfo_arm {
struct cpu cpu;
#ifdef CONFIG_SMP
//当前cpu的idle线程
struct task_struct *idle;
unsigned int loops_per_jiffy;
#endif
};
//在arch/arm/kernel/setup.c中:
static int __init topology_init(void)
{   …
    /*对于每个物理存在处理器的操作:*/
for_each_possible_cpu(cpu) {
struct cpuinfo_arm *cpuinfo = &per_cpu(cpu_data, cpu);
/*ARM SMP的架构是每个core都可以被单独关闭的*/
cpuinfo->cpu.hotpluggable = 1;
/*将当前CPU的“struct device”注册到“struct sysdev_class cpu_sysdev_class”*/
register_cpu(&cpuinfo->cpu, cpu);


}


cpu设备集合,每个cpu的“struct device”和“struct sysdev_driver”都会被加进来。这是联系cpu设备集合类驱动的纽带,当cpu设备或驱动注册的时候,会依次扫描这里驱动列表或cpu设备,并进行初始化,如“cpufreq_sysdev_driver”。


struct sysdev_class cpu_sysdev_class = {
.name = "cpu",
.attrs = cpu_sysdev_class_attrs,
};


struct cpumask : cpu状态的基本数据结构,其定义如下


 typedef struct cpumask { DECLARE_BITMAP(bits, NR_CPUS); } cpumask_t;
#define DECLARE_BITMAP(name,bits) \
unsigned long name[BITS_TO_LONGS(bits)]


展开如下:
typedef struct cpumask { unsigned long bits[BITS_TO_LONGS(NR_CPUS)] } cpumask_t;


cpu_possible_mask 位图,用来表示系统中的CPU,每颗处理器对应其中一位
cpu_online_mask  位图,用来当前处于工作状态的CPU,,每颗处理器对应其中一位




cpu_bit_bitmap: 
const unsigned long cpu_bit_bitmap[BITS_PER_LONG+1][BITS_TO_LONGS(NR_CPUS)]
对于双核CA9,这个数组是:const unsigned long cpu_bit_bitmap[33][1]。可见这是列数为1的数组。其中列数跟CPU个数和“sizeof(long)”有关。每颗CPU对应一行,但是最上面的一行是保留不用的。


#define MASK_DECLARE_1(x) [x+1][0] = (1UL << (x))




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
};
以前8个数据分析,对于cpu0-cpu7。
MASK_DECLARE_8(0)


宏定义:#define MASK_DECLARE_8(x) MASK_DECLARE_4(x), MASK_DECLARE_4(x+4)
展开:
MASK_DECLARE_4(0), MASK_DECLARE_4(0+4)


宏定义:#define MASK_DECLARE_4(x) MASK_DECLARE_2(x), MASK_DECLARE_2(x+2)
展开:


MASK_DECLARE_2(0), MASK_DECLARE_2(0+2);MASK_DECLARE_2(4), MASK_DECLARE_2(4+2)


宏定义:#define MASK_DECLARE_2(x) MASK_DECLARE_1(x), MASK_DECLARE_1(x+1)
展开:
MASK_DECLARE_1(0), MASK_DECLARE_1(0+1);
MASK_DECLARE_1(2),MASK_DECLARE_1(2+1);
MASK_DECLARE_1(4), MASK_DECLARE_1(4+1)
MASK_DECLARE_1(6), MASK_DECLARE_1(6+1)


宏定义:#define MASK_DECLARE_1(x) [x+1][0] = (1UL << (x))
展开:
[1][0] = (1UL << (0))
[2][0] = (1UL << (1))
[3][0] = (1UL << (2))
[4][0] = (1UL << (3))

[8][0] = (1UL << (7))


第0行没有初始化。对于CPU0,CPU1其MASK分别对应第一行和第二行,值分别为“(1UL << (0))”,“(1UL << (1))”。


对于cpu8-31的则由MASK_DECLARE_8(8), MASK_DECLARE_8(16), MASK_DECLARE_8(24)定义了对应的MASK了。




处理器在内核中也被作为系统设备存在,而其相关操作以驱动形式通过处理器系统设备类--“struct sysdev_class cpu_sysdev_class”来识别管理处理器。在处理器拓扑初始化时,内核完成设备侧的注册:
//处理器拓扑初始化
static int __init topology_init(void)
{    …
    /*位图cpu_possible_bits 描述了系统中的处理器,无论处理器是否online,都对应其中一位*/
for_each_possible_cpu(cpu) {
struct cpuinfo_arm *cpuinfo = &per_cpu(cpu_data, cpu);
/*注册每颗处理器的设备 */
register_cpu(&cpuinfo->cpu, cpu);
}

}
//注册一颗处理器的设备
int __cpuinit register_cpu(struct cpu *cpu, int num)
{
int error;
    //对CA9,非NUMA,为0
cpu->node_id = cpu_to_node(num);
cpu->sysdev.id = num;
    //处理器系统设备类
cpu->sysdev.cls = &cpu_sysdev_class;
    /* 向处理器系统设备类注册该处理器,在每注册一个设备时,都会调用设备类驱动来初始化该驱动。*/
error = sysdev_register(&cpu->sysdev);



}
在对方驱动侧,通过向处理器系统设备类注册驱动来匹配初始化处理器。参见CPUFREQ一节。

0 0