table在页面初始化是可以计算行列值_NUMA架构初始化详解

NUMA架构的初始化包括3个部分:processor affinity, memory affinity 和numa_distance[nr_nodes^2]的初始化。

首先先介绍下NUMA中的node概念,网上有很多解释,大体都是从socket,node,core,thread这4个层级展开介绍的,这里就不展开了,感兴趣的网上找很多。

这里只想强调下node的构成,每个node有自己的计算资源(core/threads),存储资源(内存),和总线(内部总线和内存控制器)。

这样做的好处是利于SMP的横向扩展(通过加node实现多核扩展),每个node通过自己的内部总线直接访问与自己直连的memory,从而避免了集中总线架构带来的访存压力(想想看MESI引起的bus storm)。

但这样做的缺点是内存管理更加复杂,而且跨node访存肯定会导致延迟(性能损失),所以在内存管理这块显得尤为重要。

  1. Processor affinity

acpi_parse_processor_affinity()通过解析ACPI中SRAT表中的子表sub_table.affinity_type=ACPI_SRAT_TYPE_CPU_AFFINITY,遍历出所有CPU被绑定到哪个node。

cpu_affinity子表的结构如下:

struct acpi_srat_cpu_affinity {

struct acpi_subtable_header header;

u8 proximity_domain_lo; //临近域的低8bits

u8 apic_id;

u32 flags;

u8 local_sapic_eid;

u8 proximity_domain_hi[3]; //临近域的高24bits

u32 clock_domain;

};

这里是根据acpi_srat_revision的版本设置int proximity的值。

acpi_srat_revision <= 1,proximity = proximity_domain_lo

acpi_srat_revision >=2,proximity = proximity_domain_hi << 8 |= proximity_domain_lo

然后根据proximity的值去初始化pxm_to_node_map[proximity] = node数组,这里proximity的值其实就是nodeId,绑定到相同node的cpu拥有相同的proximity.

SRAT(System resource affinity table):该表用于描述系统中所有硬件资源之间的affinity。

不同的CPU可以被bind到同一个node,也可以被bind到不同的node,NUMA架构中通常是CPU:NODE=n:1关系。

这里假设有4个cpus,其中2个CPU0和cpu1被bind到node0,另外2个cpu2和cpu3被绑定到node1,这种就是集中总线和NUMA混合架构了。

一般来说,cpu0与CPU1之间通信的效率肯定是要优于cpu0/cpu1与cpu2/3之间的通信效率,因为跨node访存一般是不能直接访问的,要通过IPI(inter-processor-interrupt, x86架构,ARM应该有类似的中断机制)中断去触发绑定了目标node的cpu去执行相应的中断处理函数,然后将得到的中断结果返回给请求CPU。

但是为什么没说的那么绝对呢,因为也有可能node0和node1之间的node_distance=LOCAL_DISTANCE(10),这意味着两个node的内存是被同一个内存控制器管理的或node之间可通过bypass直接向对方的内存控制器发送访存指令,这样node0要访问node1内存的话,就可以通过node0的内部总线或bypass直接发送访存指令给相应的内存控制器访存即可,不需要通过IPI访问了。但这种情况不是很常见,这里仅仅是讨论有这种可能性,关于NUMA中的node_distance下面会详细介绍。

所以在setup_pcpu_areas()中也会根据cpu是否被绑定到同一个node对CPU进行分组。

通过设置__apicid_to_node[apicid] = node;该数组来描述cpu与node之间的映射关系。

2. Memory affinity

acpi_parse_memory_affinity()也是通过解析ACPI中SRAT表中的子表sub_table.affinity_type=ACPI_SRAT_TYPE_MEMORY_AFFINITY,并通过子表结构体中的proximity_domain(也是nodeId)来定位这些内存块属于哪个node,该结构体如下:

struct acpi_srat_mem_affinity {

struct acpi_subtable_header header;

u32 proximity_domain; //nodeId

u16 reserved; /* Reserved, must be zero */

u64 base_address;

u64 length;

u32 reserved1;

u32 flags;

u64 reserved2; /* Reserved, must be zero */

};

3. numa_distance[nr_nodes^2]

该数组用于初始化NUMA架构中不同的node之间的distance,这个distance就决定了这两个node是否属于相同的local domain(既它们的内存是否由相同的内存控制器管理或可直接向对方的内存控制器发送访存指令);因为硬件系统根据应用场景的不同,其架构的设计可能会千奇百怪,因此两个node之间的distance是有方向性的,也就是说node0 ---> node1 = LOCAL_DISTANCE(10), 而有可能node1 ---> node0 = REMOTE_DISTANCE(20),再加上有node0 ---> node0 = LOCAL_DISTANCE这种常量情况,因此总的可能性有nr_nodes^2个。

acpi_parse_slit()函数就是通过解析ACPI中SLIT(System locality info table)表中的entry[nr_nodes^2]数组,来初始化numa_distance[nr_nodes^2], 该表结构如下:

struct acpi_table_slit {

struct acpi_table_header header; /* Common ACPI table header */

u64 locality_count;

u8 entry[1]; /* Real size = localities^2, Recording distance between any two nodes */

};

  1. 通过qemu模拟验证。

执行如下命令模拟SMP=4,其中cpu0和CPU1绑定到node0, cpu2和CPU3绑定到node1,

设置SLIT表如下:

(node0,node0) = 10

(node0,node1) = 20

(node1,node0) = 40

(node1,node1) = 10

qemu-system-x86_64 -smp cpus=4 -numa node,cpus=0-1,nodeid=0 -numa node,cpus=2-3,nodeid=1 -numa dist,src=0,dst=0,val=10 -numa dist,src=0,dst=1,val=20 -numa dist,src=1,dst=0,val=40 -numa dist,src=1,dst=1,val=10 -kernel arch/x86/boot/bzImage -initrd initramfs.img -monitor stdio -s -S

调试结果如下:

table在页面初始化是可以计算行列值_NUMA架构初始化详解_第1张图片
SLIT.entry打印结果

SLIT表中和上面qemu命令中的设置的一样

table在页面初始化是可以计算行列值_NUMA架构初始化详解_第2张图片
numa_distance初始化结果

numa_distance和slit_entry一样。

到这里终于快把kmem_cache_init()看完了,这块内容也是从其中调用的get_any_partial()引入的,感觉这个函数包含了内存管理的半壁江山了,下面会详细介绍kmem_cache_node和kmen_cache对象的作用,以及chicken-egg dilemma是如何解决的。

你可能感兴趣的:(table在页面初始化是可以计算行列值_NUMA架构初始化详解)