需求
现代服务器上运行多个用户的多个程序,硬件资源是共享的。我们可以通过类似cgroup的技
术对共享的硬件资源做调控,比如cgroup可以调控CPU和内存等资源。但是诸如cache使用、
内存带宽使用,软件难以直接做调控,这就需要引入新硬件,软件通过新硬件提供的接口做
资源调控。ARM上引入MPAM(memory partition and monitor)对内存相关的资源做控制和监
测。
对应的内存相关资源有:1. cache;2. 内部总线;3. 内存。控制的角度有使用大小和优先
级等。
概念和模型
MPAM使用一个全局唯一的三元组标记每个内存访问的源头,对于PE,这些信息需要配置到
MPAMx_ELn相关系统寄存器里,可以看到如果partition ID的语意就是PE本身,配置一次就
好,如果partition ID表示线程,就需要在线程切换的时候更新这个配置。在cache/内存控制器/
SMMU上增加MPAM控制节点(MSC(memory system component)),节点提供MMIO寄存器接口,这
些接口接受三元组的资源控制和监控的配置。相关配置提前配置好,系统运行的时候,访问
cache或者内存的请求根据提前配置的信息进行资源控制和监控。
三元组的三个元素是:partition ID space,partition ID,PMG(performance monitor group)。
partition ID space是安全态,主要用partition ID区分资源类型,PMG用来聚集一组监测
资源。MSC集成在各个内存相关的部件里,由专门的ACPI或者DTS MPAM表格报给OS。
MPAM协议里还定义了一些MPAM内部传递控制信息的概念(第四章),感觉这些概念以及相关的
部件是软件较少感知的。
MPAM协议里简单描述了下硬件内部partitioning control的逻辑,简单讲就是当前使用的资
源和提前配置好的资源不断地做比较,如果还有余量就给分资源,反之就不给分资源。更进一步
看,capacity-based partitioning才需要这种动态调整,portion-based partitioning
(比如,直接配置可以用哪几个cache way的情况)的调整逻辑就可以比较简单。可以看到硬件
内部会用表格记录各种资源配置,如果这个表格在硬件内部,那么这个部件在使用中有低功
耗的上下电动作时,这些内容都要做保存和恢复。
MPAM只有两种类型中断:1. 报告错误的中断;2. monitor计数溢出中断。
MPAM的软硬件接口寄存器大致分为四类:ID寄存器,系统寄存器,control相关寄存器和monitor
相关的寄存器。ID寄存器配置MPAM MSC的各种规格,系统寄存器配置从流量源头带上的partition
ID和PMG。
注意,现代处理器实现都采用流水线技术,在单点控制相当于控制流水线中的一个点,容易
引发系统问题。另外,一旦系统里有共享部件,硬件逻辑综合起来就容易冲突,也可能引发
问题。
ID和系统寄存器
系统寄存器就是如上提到的:MPAMx_ELn
ID寄存器整理如下:
1 2 3 4 5 6 7 8 9 10
| MPAMF_AIDR 版本信息 MPAMF_IIDR 厂商信息 MPAMF_IMPL_IDR 自定义特性信息 MPAMF_SIDR 安全相关特性支持情况
MPAMF_IDR 特性总体支持信息 MPAMF_CCAP/CPOR/CSUMON_IDR cache相关信息 MPAMF_MBW/MBWUMON_IDR memory相关信息 MPAMF_PRI_IDR 优先级相关信息 MPAMF_PARTID_NRW_IDR partition ID narrow相关信息
|
resource partitioning control
软件需要配置MSC上各种资源的配置,这些配置用partition ID和RIS做标记。一个MSC上可能
同时支持多种不同类型的控制和监控,比如,同时支持memory和cache,RIS(resource instance
selection)就是对支持类型的标记。
软件需要用MPAMCFG_PART_SEL选择当前要配置的partition ID和RIS,然后配置对应的寄存器,
配置选中partition ID和RIS对应的资源限制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| MPAMCFG_PART_SEL 配置partition ID和RIS
MPAMCFG_CASSOC Cache Maximum Associativity Partition MPAMCFG_CMAX Cache Maximum Capacity Partition 配置cache使用的最大百分比 MPAMCFG_CMIN Cache Minimum Capacity Partition 配置cache使用的最小百分比, 小于这个百分比的partition ID的优先级被提高,优先分cache。 MPAMCFG_CPBM<n> Cache Portion Bitmap Partition 按照portion的配置寄存器。配置的方式: 1. 最大bit数,2. select出要配置的partition ID,3. 执行配置。
MPAMCFG_MBW_MAX Memory Bandwidth Maximum Partition MPAMCFG_MBW_MIN Memory Bandwidth Minimum Partition MPAMCFG_MBW_PBM<n> Bandwidth Portion Bitmap Partition MPAMCFG_MBW_PROP Memory Bandwidth Proportional Stride Partition MPAMCFG_MBW_WINWD Memory Bandwidth Partitioning Window Width
MPAMCFG_PRI Priority Partition
|
resource monitor
一个MSC上可能既有cache又有内存的monitor,相同种类里需要监控的partid和PMG又不一样,
对于某个type,特定partid和PMG的监控,需要先配置对应的监控控制寄存器,然后从对应的
counter里读监控得到的数据。
每个MSC中,某种类型实际的monitor counter又可以有多个。这个配置可以在对应的ID寄存器
里查到,比如memory的MPAMF_MBWUMON_IDR.NUM_NON定义这个MSC有多少个内部的counter,
MPAM里叫做monitor instance。
MPAM定义了一组控制寄存器和一个counter对外接口。所以,对于一个MSC,对于每个type,
每个instance,软件要先通过这些控制寄存器选择要配置的具体type/instance/partid+PMG,
再进行配置,或者读counter信息。
比如,先配置MSMON_CFG_MON_SEL.RIS选择type,MSMON_CFG_MON_SEL.MON_SEL选择instance,
MSMON_CFG_MBWU_FLT.PARTID/PMG选择partid+PMG。然后配置MSMON_CFG_MBWU_CTL启动对应
的监控行为,读MSMON_MBWU得到监控数据。
寄存器的具体定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| MSMON_CFG_MON_SEL monitor选择寄存器,MON_SEL选择instance,RIS选择type
MSMON_CAPT_EVNT 提供了一个软件控制接口把counter里的值保存到CAPTURE寄存 器里,写NOW触发这个保存动作。通过如下CTL寄存器可以配置
MSMON_CSU_CAPTURE 上次capture事件保存的cache使用量 MSMON_MBWU_CAPTURE 上次capture事件保存的带宽使用量 MSMON_MBWU_L_CAPTURE MBWU长counter capture值
MSMON_CFG_CSU_CTL CSU控制寄存器,TYPE配置触发capture的源头,写如上NOW是其中一种 PARTID/PMG过滤使能,EN使能monitor MSMON_CFG_CSU_FLT CSU过滤寄存器,PARTID/PMG配置要过滤的参数,XCL配置是否只记录被修改的cache line MSMON_CFG_MBWU_CTL MBWU控制寄存器,TYPE语义如上,各种overflow标记,EN使能monitor MSMON_CFG_MBWU_FLT MBWU过滤寄存器,PARTID/PMG语义如上,RWBW配置过滤只读/只写/读写
MSMON_CSU CSU counter,VALUE为cache使用量,NRDY指示数据是否就绪 MSMON_MBWU MBWU counter(31位),VALUE为带宽使用量,NRDY语义如上 MSMON_MBWU_L MBWU长counter(44/63位),用于长时间监控减少溢出,L_NRDY语义如上
MSMON_OFLOW_MSI_ADDR_L/H 溢出中断MSI地址 MSMON_OFLOW_MSI_ATTR MSI属性配置 MSMON_OFLOW_MSI_DATA MSI数据 MSMON_OFLOW_MSI_MPAM MSI MPAM标识 MSMON_OFLOW_MSI_SR MSI状态寄存器
|
注意,如上描述的是MSC处怎么配置具体的监控。请求发出的源端也要配置partid以及PMG,
这个依然是配置到MPAMx_ELn寄存器中的。监控需要匹配PARTID+PMG的组合。
SMMU MPAM
SMMU上只是配置partid,联合内存或者cache上的控制单元实现内存或者cache资源的控制。
SMMU上的STE/CD可以看作是具体外设在SMMU上的代理,所以对一个具体的外设只需要把partid
配置到SMMU上即可。
Partid Narrow
所谓Partid Narrow是进入MSC的partid可以通过提前配置好的映射被映射成另外一个partid,
后面的控制和监控都基于新的partid。
Partid Narrow的配置分三点:1. 映射的配置,2. 映射配置的读取,3. 如何使用映射的partid。
映射配置:用reqPARTID写MPAMCFG_PART_SEL(不带INTERNAL位),先选择要配置的reqPARTID,
用目标intPARTID写MPAMCFG_INTPARTID,同时置上MPAMCFG_INTPARTID.INTERNAL为1。映射
配置的读取:和映射配置一样,先选reqPARTID,然后读MPAMCFG_INTPARTID就好。
Partid的使用分映射和配置两个方面。只要配置了映射,硬件会对对应的partid做映射。但是,
针对partid的控制和监控的配置比较奇怪,需要配置MPAMCFG_PART_SEL.INTERNAL为1,同时
把intPARTID配置到MPAMCFG_PART_SEL.PARTID上。注意,这里INTERNAL为1是告诉硬件,现在
选的PARTID是intPARTID,后面的配置是针对这个intPARTID。
主要用途是扩展监控容量——多个reqPARTID可以映射到同一个intPARTID,复用同一组资源配置,
但可以通过不同reqPARTID区分监控上下文。
举个例子,系统有L3 MSC(无narrow, 256 PARTID)和MATA MSC(有narrow, 32 intPARTID,
256 reqPARTID),PMG有8个值。两个MSC的PARTID交集是32,所以控制组数固定为32。
无narrow:监控靠(PARTID, PMG),最多 32×8 = 256 个监控端点。
有narrow:监控靠(reqPARTID, PMG),最多 256×8 = 2048 个监控端点。
控制组数都是32,但narrow把监控能力放大了8倍。本质是reqPARTID变成了PMG之上的第二
层监控维度。
MPAM虚拟化
- partition ID预留,2. 虚拟物理partition ID映射,3. MSC模拟。
Linux软件接口
Intel最早实现了MPAM类似的功能(RDT),软件上用一个独立的文件系统resctrl向外导出使用接口。
文件系统层次结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| /sys/fs/resctrl/ ├── cpus # 整个resctrl系统的CPU列表 ├── cpus_list # 人类可读的CPU列表格式 ├── mon_groups/ # 监控组目录 ├── info/ # 系统资源信息,静态信息,只出现在顶层目录 │ ├── L3/ # cache控制资源信息 │ │ ├── cbm_mask │ │ ├── min_cbm_bits │ │ ├── num_closids # 最大控制组数(class of service ID),Intel定义的古怪名字 │ │ └── shareable_bits │ ├── L3_MON/ # cache监控资源信息 │ │ ├── num_rmids # 最大PMG数 │ │ └── mon_features # 支持的监控事件 │ ├── MB/ # 内存带宽控制资源信息 │ │ ├── bandwidth_gran │ │ ├── delay_linear │ │ ├── min_bandwidth │ │ └── num_closids # MB支持的最大控制组数 │ ├── MB_MON/ # 内存带宽监控资源信息 │ │ ├── num_rmids │ │ └── mon_features │ └── last_cmd_status # 最后一次命令执行状态 ├── mon_data/ # 根控制组的监控数据。每种类型的monitor分开呈现 │ ├── mon_L3_00/ # L3 cache monitor域0。如果有多个L3 MSC,就有mon_L3_00/01/... │ │ └── llc_occupancy │ ├── mon_MB_00/ # 内存带宽monitor域0。如果有多个MB MSC,就有mon_MB_00/01/... │ │ ├── mbm_total_bytes │ │ └── mbm_local_bytes │ └── ... ├── schemata # 资源分配方案,每行一个class,每个id=value一项为一个component/domain │ 例如: L3:0=ffff;1=ffff │ MB:0=100;1=100 ├── size # 根控制组的缓存大小 ├── tasks # 根控制组的进程列表 ├── <用户创建的目录>/ # 用户自定义控制组,在用户目录下创建一样的文件 │ ├── cpus │ ├── cpus_list # A │ ├── schemata │ ├── size │ ├── mon_groups/ # B。该控制组的监控组。可以在这个目录下创建子监控组。 │ ├── mon_data/ # 该控制组的顶层监控数据。注意,这里会再次展示系统中所有的监控MSC │ └── tasks └── <mon_groups创建的目录>/ # 监控组目录。注意这里是全局监控组目录。 ├── mon_data/ # 监控组的具体监控数据 └── ... (AI生成)
|
resctrl使用层次化的结构控制资源,resctrl的根目录是全局资源,用户通过在resctrl
目录下创建目录,创建对应的控制组和监控组,可以看到在用户创建的目录下会新创建一整
套resctrl相关的控制和监控目录和文件。可以看到,用户创建的目录和对应的partition ID
对应起来,partition ID和CPU/线程的绑定关系通过配置cpus/cpus_list/tasks来实现。
控制组创建后,会在控制组目录自动生成监控组控制目录(mon_groups)和监控组数据目录
(mon_data)。可以在mon_groups里创建自定义子监控组。拿如上的用户自定义控制组作为一个
例子,比如在A处的cpus_list配置控制和监控CPU 1-4,完全可以在B处mon_groups中创建多个
子监控组目录,配置子监控组目录下的cpus_list,独立监控CPU1。
一般来说系统里有多个MSC,对于一个被控制对象,比如一个线程,可以在这些MSC上配置这个
被控制对象的资源控制和资源监控,这个完全是用户自己的配置行为。resctrl通过schemata
文件在每个控制层级暴露所有MSC的控制信息。
resctrl中标准监控事件有:llc_occupancy(末级缓存占用)、mbm_total_bytes(总内存带宽)等。
Linux内核实现
MPAM对外使用resctrl文件系统作为接口。以openEuler v6.6内核为例,驱动代码在drivers/platform/mpam/。
这个驱动是一个平台设备驱动,但是,真正probe的地方在注册的cpu online的回调函数里。
核心数据结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| /* MPAM设备的分类,比如,cache/memory/IOMMU等 */ struct mpam_class +-> components list
/* 表示一个MSC设备 */ struct mpam_msc +-> ris list
/* 表示一个MSC上的一个resource type */ struct mpam_msc_ris
/* * 表示一个控制域(domain),概念是resctrl里的一个最小控制单元,比如,如上resctrl * 文件系统中schemata文件下的"L3:0=ffff"。和MSC的区别是,MSC表示具体的物理控制单元, * mpam_component是对控制的抽象,可能多个物理控制单元软件上对外呈现一个最小控制 * 单元。一般mpam_component和MSC是一一对应的。 */ struct mpam_component +-> ris list
/* * 和resctrl fs的交互的数据结构,每个mpam_resctrl_res内嵌resctrl_resource, * 全局数组mpam_resctrl_exports[RDT_NUM_RESOURCES]索引L3/L2/MC。初始化时 * mpam_domains_init()遍历每个class的component,调用mpam_resctrl_alloc_domain() * 分配mpam_resctrl_dom(内嵌resctrl_domain),通过resctrl_online_domain()向 * resctrl核心注册。用户写schemata时,驱动遍历对应component的所有RIS,调用 * mpam_reprogram_ris_partid()写MSC硬件寄存器。 */ struct mpam_resctrl_res
|
MSC设备解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| mpam_msc_drv_probe <-- probe以及创建MSC +-> acpi_mpam_parse_resources <-- 创建mpam_ris +-> mpam_ris_create +-> mpam_class_get <-- 如果还没有,就创建一个 /* * component表示应统一配置的一组MSC。对于cache,component_id来自ACPI PPTT * 表的cache_reference,唯一标识一个特定缓存(如某个L3);对于memory, * component_id来自proximity_domain转换的NUMA node ID,同一NUMA node上的 * 多个内存控制器属于同一个component。每个component映射到一个resctrl域 * (schemata中的一行),是该域的最小配置单元。 */ +-> mpam_component_get
mpam_discovery_cpu_online +-> mpam_msc_hw_probe <-- probe MSC硬件 /* work queue里执行 */ +-> mpam_resctrl_setup +-> mpam_resctrl_resource_init <-- 创建mpam_resctrl_res数组 +-> resctrl_init <-- 创建resctrl相关文件
|
resctrl文件创建:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| /* fs/resctrl/rdtgroup.c */ resctrl_init +-> register_filesystem +-> rdt_init_fs_context /* 创建resctrl各个文件的逻辑在get_tree里 */ +-> rdt_fs_context_ops.get_tree
rdt_get_tree /* * 增加resctrl下创建/销毁目录的回调函数: rdtgroup_mkdir/rdtgroup_rmdir * * resctrl增加控制和监控项目的时候,需要在resctrl sysfs下新增目录,resctrl * 会在新增的目录中增加和顶层一样的目录和文件,相关代码逻辑就在rdtgroup_mkdir。 */ +-> rdtgroup_setup_root +-> rdt_enable_ctx +-> schemata_list_create +-> closid_init /* * resctrl把所有要增加的公共文件都定义在res_common_files数组里,每个文件一个 * 数组项,数组项中的fflags标记该文件增加到哪里。 */ +-> rdtgroup_add_files +-> rdtgroup_create_info_dir +-> mongroup_create_dir +-> mkdir_mondata_all
rdtgroup_mkdir +-> rdtgroup_mkdir_ctrl_mon +-> mkdir_rdt_prepare +-> rdtgroup_mkdir_mon
|
resctrl和驱动的接口是直接arch实现函数调用的… 如此粗暴…
MPAM资源配置的一般逻辑是,用户已经知道整个系统的cache和memory相关控制节点的拓扑,
相关控制节点直接呈现在resctrl文件系统中。用户实际上通过resctrl把特定CPU或线程和
partid绑定,用户通过在各个控制节点上配置partid对应的控制和监控信息达到控制和监控
的功能。partid最终呈现对应的可能是一个个独立的目录。