CPU 拓撲用來表示 CPU 在硬件層面的組合方式,本文主要講解 CPU 拓撲中的 SMP(Symmetric Multi-Processor,對稱多處理器系統)架構,CPU 拓撲還包括其他信息,比如:cache 等,這些部分會在后面進行補充。CPU 拓撲除了描述 CPU 的組成關系外,還為內核的調度器提供服務,從而提供更好的性能。在 StratoVirt 中,支持 CPU 拓撲為后續的 CPU 熱插拔開發打下一個基礎。
常見的 CPU SMP 結構是:
Socket-->die-->cluster-->core-->thread
socket:對應主板上的 CPU 插槽
die:處理器在生產過程中,從晶圓上切割下來的一個個小方塊,Die 之間的組件是通過片內總線互聯的。
cluster:簇,大核或者小核的一種組合
core:表示獨立的物理 CPU
thread:邏輯 CPU,英特爾超線程技術引入的新概念
CPU 拓撲的獲取原理
因為 x86 和 ARM 的拓撲獲取方式不同,下面將會分開進行介紹。
x86
在 x86 架構下面,操作系統會通過讀取 CPUID 來獲取 CPU 拓撲結構。在 x86 體系結構中,CPUID 指令(由 CPUID 操作碼標識)是處理器補充指令(其名稱源自 CPU 標識),允許軟件發現處理器的細節。程序可以使用 CPUID 來確定處理器類型。
CPUID 隱式使用 EAX 寄存器來確定返回的信息的主要類別,這被稱為 CPUID 葉。跟 CPU 拓撲相關的 CPUID 葉分別是:0BH 和 1FH。1FH 是 0BH 的擴展,可以用來表示更多的層級。Intel 建議先檢查 1FH 是否存在,如果 1FH 存在會優先使用它。當 EAX 的值被初始化為 0BH 的時候,CPUID 會在 EAX,EBX,ECX 和 EDX 寄存器中返回 core/logical 處理器拓撲信息。這個函數(EAX=0BH)要求 ECX 同時被初始化為一個 index,這個 index 表示的是在 core 層級還是 logical processor 層級。OS 調用這個函數是按 ECX=0,1,2..n 這個順序調用的。返回處理器拓撲級別的順序是特定的,因為每個級別報告一些累積數據,因此一些信息依賴于從先前級別檢索到的信息。在 0BH 下,ECX 可以表示的層級有:SMT 和 Core,在 1FH 下,可以表示的層級有:SMT,Core,Module,Tile 和 Die。
下表是一個更詳細的一個解釋:
Initial EAX Value | Information Provided about the Processor |
---|---|
0BH | EAX Bits 04 - 00: Number of bits to shift right on x2APIC ID to get a unique topology ID of the next level type*. All logical processors with the same next level ID share current level. Bits 31 - 05: Reserved. EBX Bits 15 - 00: Number of logical processors at this level type. The number reflects configuration as shipped by Intel. Bits 31- 16: Reserved. ECX Bits 07 - 00: Level number. Same value in ECX input. Bits 15 - 08: Level type. Bits 31 - 16: Reserved. EDX Bits 31- 00: x2APIC ID the current logical processor. |
1FH | EAX Bits 04 - 00: Number of bits to shift right on x2APIC ID to get a unique topology ID of the next level type*. All logical processors with the same next level ID share current level. Bits 31 - 05: Reserved. EBX Bits 15 - 00: Number of logical processors at this level type. The number reflects configuration as shipped by Intel. Bits 31- 16: Reserved. ECX Bits 07 - 00: Level number. Same value in ECX input. Bits 15 - 08: Level type. Bits 31 - 16: Reserved. EDX Bits 31- 00: x2APIC ID the current logical processor |
來源: Intel 64 and IA-32 Architectures Software Developer's Manual
ARM
在 ARM 架構下,如果操作系統是依靠 Device Tree 啟動的,則會通過 Device Tree 去獲取 CPU 拓撲。如果是以 ACPI 的方式啟動的話,操作系統會通過解析 ACPI 的 PPTT 表去獲取 CPU 拓撲結構。
ACPI——PPTT
ACPI 是 Advanced Configuration and Power Interface (高級配置和電源接口)的縮寫,ACPI 是一種與體系結構無關的電源管理和配置框架。這個框架建立了一個硬件寄存器集合來定義電源狀態。ACPI 是操作系統和固件之間的一個中間層,是他們兩者之間的一個接口。ACPI 定義了兩種數據結構:data tables 和 definition blocks。data tables 用于存儲給設備驅動使用的 raw data。definition blocks 由一些字節碼組成,這些碼可以被解釋器執行。
為了使硬件供應商在選擇其實施時具有靈活性,ACPI 使用表格來描述系統信息、功能和控制這些功能的方法。這些表列出了系統主板上的設備或無法使用其他硬件標準檢測或電源管理的設備,以及 ACPI 概念中所述的功能。它們還列出了系統功能,如支持的睡眠電源狀態、系統中可用的電源平面和時鐘源的說明、電池、系統指示燈等。這使 OSPM 能夠控制系統設備,而不需要知道系統控制是如何實現的。
PPTT 表就是其中的一個表格,PPTT 表全稱是 Processor Properties Topology Table,處理器屬性拓撲表用于描述處理器的拓撲結構,該表還可以描述附加信息,例如處理器拓撲中的哪些節點構成物理包。
下表是 PPTT 表的結構,包含一個表頭和主體,表頭和其他的 ACPI 表差別不大。其中 Signature 用于表示這是 PPTT 表,Length 是整張表的大小,其他的信息可以查看下面的這張表。表的主體是一系列處理器拓撲結構。
下面的表表示處理器層級節點結構,表示處理器結構的話 Type 要設置為 0,Length 表示這個節點的字節數。Flags 用來描述跟處理器相關的信息,詳細的看后面關于 Flags 的詳細信息。Parent 用于指向這個節點的上一級節點,存放的是一個偏移量地址
下表是 Flags 的結構,Flags 占據 4 個字節的長度。Physical package:如果處理器拓撲的此節點表示物理封裝的邊界,則設置 Physical package 為 1。如果處理器拓撲的此實例不表示物理軟件包的邊界,則設置為 0。Processor is a Thread:對于葉條目:如果代表此處理器的處理元素與兄弟節點共享功能單元,則必須將其設置為 1。對于非葉條目:必須設置為 0。Node is a Leaf:如果節點是處理器層次結構中的葉,則必須設置為 1。否則必須設置為 0。
參考:https://uefi.org/specs/ACPI/6.4/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#processor-properties-topology-table-pptt
Device Tree
Device Tree 是一種描述硬件的數據結構。內核的啟動程序會將設備樹加載入內存中,然后通過解析 Device Tree 來獲取硬件細節。Device Tree 是樹形結構,由一系列被命名的節點和屬性組成,節點可以包含子節點,它們之間的關系構成一棵樹。屬性就是 name 和 value 的鍵值對。
一個典型的設備樹如下圖:
ARM 的 CPU 拓撲是定義在 cpu-map 節點內,cpu-map 是 cpu 節點的子節點。在 cpu-map 節點里可以包含三種子節點:cluster 節點,core 節點,thread 節點。整個 dts 的例子如下:
cpus{ #size-cells=<0>; #address-cells=<2>; cpu-map{ cluster0{ cluster0{ core0{ thread0{ cpu=<&CPU0>; }; thread1{ cpu=<&CPU1>; }; }; core1{ thread0{ cpu=<&CPU2>; }; thread1{ cpu=<&CPU3>; }; }; }; cluster1{ core0{ thread0{ cpu=<&CPU4>; }; thread1{ cpu=<&CPU5>; }; }; core1{ thread0{ cpu=<&CPU6>; }; thread1{ cpu=<&CPU7>; }; }; }; }; }; //... };
參考:https://www.kernel.org/doc/Documentation/devicetree/bindings/arm/topology.txt
圖來源:https://www.devicetree.org/specifications/
StratoVirt 具體實現
CPUID
首先我們需要計算每個拓撲結構唯一的 topology ID,然后獲取或者自己建立相對應的 CPUID entry,當 entry 的 function 的值等于 0xB 和 0X1F 的時候,我們需要根據 CPUID 的規范去設置相對應的 EAX, EBX, ECX 的值。EAX 設置為拓撲 ID,EBX 用來表示那個層級的有幾個邏輯處理器,ECX 表示層級號。0xB 需要配置 index 等于 0,1 對應的值,0x1F 需要配置 index 等于 0,1,2 對應的值。下面是相對應的代碼:
//cpu/src/x86_64/mod.rs constECX_INVALID:u32=0u32<8; const?ECX_THREAD:?u32?=?1u32?<8; const?ECX_CORE:?u32?=?2u32?<8; const?ECX_DIE:?u32?=?5u32?<8; impl?X86CPUState?{ ????fn?setup_cpuid(&self,?vcpu_fd:?&Arc)->Result<()>{ //計算topologyID letcore_offset=32u32-(self.nr_threads-1).leading_zeros(); letdie_offset=(32u32-(self.nr_cores-1).leading_zeros())+core_offset; letpkg_offset=(32u32-(self.nr_dies-1).leading_zeros())+die_offset; //獲取KVM的fd和獲取它支持的CPUIDentries forentryinentries.iter_mut(){ matchentry.function{ //... 0xb=>{ //ExtendedTopologyEnumerationLeaf entry.edx=self.apic_idasu32; entry.ecx=entry.index&0xff; matchentry.index{ 0=>{ entry.eax=core_offset; entry.ebx=self.nr_threads; entry.ecx|=ECX_THREAD; } 1=>{ entry.eax=pkg_offset; entry.ebx=self.nr_threads*self.nr_cores; entry.ecx|=ECX_CORE; } _=>{ entry.eax=0; entry.ebx=0; entry.ecx|=ECX_INVALID; } } } //0x1f擴展,支持die層級 0x1f=>{ ifself.nr_dies2?{ ????????????????????????entry.eax?=?0; ????????????????????????entry.ebx?=?0; ????????????????????????entry.ecx?=?0; ????????????????????????entry.edx?=?0; ????????????????????????continue; ????????????????????} ????????????????????entry.edx?=?self.apic_id?as?u32; ????????????????????entry.ecx?=?entry.index?&?0xff; ????????????????????match?entry.index?{ ????????????????????????0?=>{ entry.eax=core_offset; entry.ebx=self.nr_threads; entry.ecx|=ECX_THREAD; } 1=>{ entry.eax=die_offset; entry.ebx=self.nr_cores*self.nr_threads; entry.ecx|=ECX_CORE; } 2=>{ entry.eax=pkg_offset; entry.ebx=self.nr_dies*self.nr_cores*self.nr_threads; entry.ecx|=ECX_DIE; } _=>{ entry.eax=0; entry.ebx=0; entry.ecx|=ECX_INVALID; } } } //... } } }
PPTT
根據 ACPI PPTT 表的標準來構建,我們需要計算每個節點的偏移值用于其子節點指向它。我們還需要計算每個節點的 uid,uid 初始化為 0,每增加一個節點 uid 的值加一。還需要根據 PPTT 表的標準計算 Flags 的值。最后需要計算整張表的大小然后修改原來的長度的值。
//machine/src/standard_vm/aarch64/mod.rs implAcpiBuilderforStdMachine{ fnbuild_pptt_table( &self, acpi_data:&Arc>>, loader:&mutTableLoader, )->super::Result { //... //配置PPTT表頭 //添加socket節點 forsocketin0..self.cpu_topo.sockets{ //計算到起始地址的偏移量 letsocket_offset=pptt.table_len()-pptt_start; letsocket_hierarchy_node=ProcessorHierarchyNode::new(0,0x2,0,socketasu32); //... forclusterin0..self.cpu_topo.clusters{ letcluster_offset=pptt.table_len()-pptt_start; letcluster_hierarchy_node= ProcessorHierarchyNode::new(0,0x0,socket_offsetasu32,clusterasu32); //... forcorein0..self.cpu_topo.cores{ letcore_offset=pptt.table_len()-pptt_start; //判斷是否需要添加thread節點 ifself.cpu_topo.threads>1{ letcore_hierarchy_node= ProcessorHierarchyNode::new(0,0x0,cluster_offsetasu32,coreasu32); //... for_threadin0..self.cpu_topo.threads{ letthread_hierarchy_node= ProcessorHierarchyNode::new(0,0xE,core_offsetasu32,uidasu32); //... uid+=1; } }else{ letthread_hierarchy_node= ProcessorHierarchyNode::new(0,0xA,cluster_offsetasu32,uidasu32); //... uid+=1; } } } } //將PPTT表添加到loader中 } }
Device Tree
StratoVirt 的 microvm 使用 device tree 啟動,所以我們需要配置 device tree 中的 cpus 節點下的 cpu-map 來使 microvm 支持解析 CPU 拓撲。在 StratoVirt 中,我們支持兩層 cluster。我們使用了多層循環來創建這個 tree,第一層是創建第一層 cluster,第二層對應創建第二層的 cluster,第三層創建 core,第四層創建 thread。
implCompileFDTHelperforLightMachine{ fngenerate_cpu_nodes(&self,fdt:&mutFdtBuilder)->util::Result<()>{ //創建cpus節點 //... //GenerateCPUtopology //創建cpu-map節點 letcpu_map_node_dep=fdt.begin_node("cpu-map")?; //創建第一層cluster節點 forsocketin0..self.cpu_topo.sockets{ letsock_name=format!("cluster{}",socket); letsock_node_dep=fdt.begin_node(&sock_name)?; //創建第二層cluster節點 forclusterin0..self.cpu_topo.clusters{ letclster=format!("cluster{}",cluster); letcluster_node_dep=fdt.begin_node(&clster)?; //創建core節點 forcorein0..self.cpu_topo.cores{ letcore_name=format!("core{}",core); letcore_node_dep=fdt.begin_node(&core_name)?; //創建thread節點 forthreadin0..self.cpu_topo.threads{ letthread_name=format!("thread{}",thread); letthread_node_dep=fdt.begin_node(&thread_name)?; //計算cpu的id //letvcpuid=... //然后添加到節點中 } fdt.end_node(core_node_dep)?; } fdt.end_node(cluster_node_dep)?; } fdt.end_node(sock_node_dep)?; } fdt.end_node(cpu_map_node_dep)?; Ok(()) } }
這個代碼構建出來設備樹的結構和前面原理中展示的結構基本一致
驗證方法
我們可以通過下面的命令啟動一個虛擬機,smp 參數用來配置 vCPU 拓撲
sudo./target/release/stratovirt -machinevirt -kernel/home/hwy/std-vmlinux.bin.1 -appendconsole=ttyAMA0root=/dev/vdarwreboot=kpanic=1 -drivefile=/usr/share/edk2/aarch64/QEMU_EFI-pflash.raw,if=pflash,unit=0,readonly=true -drivefile=/home/hwy/openEuler-22.03-LTS-stratovirt-aarch64.img,id=rootfs,readonly=false -devicevirtio-blk-pci,drive=rootfs,bus=pcie.0,addr=0x1c.0x0,id=rootfs -qmpunix:/var/tmp/hwy.socket,server,nowait -serialstdio -m2048 -smp4,sockets=2,clusters=1,cores=2,threads=1
接著,我們可以通過觀察 /sys/devices/system/cpu/cpu0/topology 下面的文件來查看配置的 topology。
[root@StratoVirttopology]ll total0 -r--r--r--1rootroot64KJul1809:04cluster_cpus -r--r--r--1rootroot64KJul1809:04cluster_cpus_list -r--r--r--1rootroot64KJul1809:04cluster_id -r--r--r--1rootroot64KJul1809:04core_cpus -r--r--r--1rootroot64KJul1809:04core_cpus_list -r--r--r--1rootroot64KJul1809:01core_id -r--r--r--1rootroot64KJul1809:01core_siblings -r--r--r--1rootroot64KJul1809:04core_siblings_list -r--r--r--1rootroot64KJul1809:04die_cpus -r--r--r--1rootroot64KJul1809:04die_cpus_list -r--r--r--1rootroot64KJul1809:04die_id -r--r--r--1rootroot64KJul1809:04package_cpus -r--r--r--1rootroot64KJul1809:04package_cpus_list -r--r--r--1rootroot64KJul1809:01physical_package_id -r--r--r--1rootroot64KJul1809:01thread_siblings -r--r--r--1rootroot64KJul1809:04thread_siblings_list
比如:
catcore_cpus_list
結果是
0
表示和 cpu0 同一個 core 的 cpu 只有 cpu0。
catpackage_cpus_list
會顯示
0-1
表示和 cpu0 同一個 socket 的 cpu 有從 cpu0 到 cpu1。
下面這些工具也可以輔助進行驗證。
比如:lscpu
lscpu
通過執行 lscpu 命令會出現下面結果
Architecture:aarch64 CPUop-mode(s):32-bit,64-bit ByteOrder:LittleEndian CPU(s):64 On-lineCPU(s)list:0-63 VendorID:ARM Modelname:Cortex-A72 Model:2 Thread(s)percore:1 Core(s)percluster:16 Socket(s):- Cluster(s):4 Stepping:r0p2 BogoMIPS:100.00 Flags:fpasimdevtstrmaespmullsha1sha2crc32cpuid NUMA: NUMAnode(s):4 NUMAnode0CPU(s):0-15 NUMAnode1CPU(s):16-31 NUMAnode2CPU(s):32-47 NUMAnode3CPU(s):48-63 Vulnerabilities: Itlbmultihit:Notaffected L1tf:Notaffected Mds:Notaffected Meltdown:Notaffected Specstorebypass:Vulnerable Spectrev1:Mitigation;__userpointersanitization Spectrev2:Vulnerable Srbds:Notaffected Tsxasyncabort:Notaffected
-
處理器
+關注
關注
68文章
19761瀏覽量
233043 -
英特爾
+關注
關注
61文章
10149瀏覽量
173711 -
cpu
+關注
關注
68文章
11015瀏覽量
215425 -
SMP
+關注
關注
0文章
77瀏覽量
20128
原文標題:StratoVirt 的 vCPU 拓撲(SMP)
文章出處:【微信號:openEulercommunity,微信公眾號:openEuler】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
AliOS Things SMP系統及其在esp32上實現示例
PCIE基本概念與拓撲架構圖
RT-Thread SMP和AMP初體驗簡介
RT-Thread框架下的SMP支持
如何啟用SMP?
SMP技術
Linux在SMP系統上的移植研究

SMP是什么?多核芯片(SMP)的啟動方法

RT-Thread SMP啟動流程

評論