大数跨境
0
0

内存基础知识(十一)-- 查询内存通道编号

内存基础知识(十一)-- 查询内存通道编号 IT知识刺客
2025-10-23
1
导读:得到编号很简单,但要得到UNC_M_RD_CAS_RANK0.ALLBANKS的type,就比较复杂了。CPU手册中没告诉你,你在其他地方也很难找到(AI也不知道)。我是翻了perf的源码才找到的。
内存基础知识系列1~10,前十篇是基础:
不能说的秘密--内存竟然有隐藏带宽
内存基础知识(二)
内存基础知识(三)-- BANK
DDR5核心频率只有200MHz(兆赫兹):真的假的
内存基础知识(五)-- BankGroup前传
内存基础知识(六)-- BankGroup登场
内存基础知识(七)-- BankGroup正传
内存基础知识(八)-- 如何统计内存流量
内存基础知识(九)-- 内存通道(Channel)详解
内存基础知识(十)-- 内存控制器是怎么完成读操作的
原理层面的东西,已经描述的差不多了。从第8篇开始,本来就准备开始实操,但发现“内存通道”这个概念不说清楚还是不行。因为我们要使用的内存相关计数器,是在内存通道中的。
内存系列后面的内容,我们将逐渐转到“实操”,聚焦“如何用CPU中内存相关的计数器,观察研究内存”。
这些计数器是啥我再说一下,它们是CPU在流水线当中内置的各种各样计数器。它首次出场是在第8篇:内存基础知识(八)-- 如何统计内存流量,忘记了的可以去复习下。
本篇仍先从内存通道开始,注意前几篇提到过了,内存通道并不只是通道,它是通道+控制。每个内存通道就像一块大CPU中的Core一样,一个MC中,有多个内存通道(就像一块CPU中有多个Core)。每个内存通道有自己独立的控制电路和总线,以及计数器。
在第8篇内存基础知识(八)-- 如何统计内存流量中,以perf为例,描述了统计内存流量的方法。perf中包含了几乎所有CPU中计数器的映像,使用perf打开这些计数器非常方便。
但perf中总有遗漏,而且版本越早的perf,遗漏的越多。
我测试服务器的Linux、Perf版本都有点老,而且我不能擅自升级。
这种情况下就只能使用perf的底层系统调用perf_event_open(),自己打开、关闭计数器了。
perf_event_open()的具体用法,可以自行man一下。如果不想麻烦,也可以升级perf至最新版试试,用perf命令更简单。
使用perf_event_open()也更零活,掌握了它,你完全可以自行开发个内存流量监控工具。
我这里说一下perf_event_open()的几个要点。
我们前文所说CPU中的计数器,正式的名字是PMC,Performance Monitor Counter(性能监控计数器)。所有的PMC合起来,也被称为PMU,性能监控单元。

PMC计数器统一使用(type, 编号)这样的二元组标识。打开、关闭,读取结果,都要使用这样的二元组,明确的告诉CPU是针对那个PMC计数器的。

这其中,编号要到CPU手册中查找。

图1
以UNC_M_RD_CAS_RANK0.ALLBANKS为例,看图1,“EventSel=B0H UMask=10H”,把这里的UMask放前面、EventSel放后面,组合成的十六进制数,就是编号:0x10b0。
(图1中的表格来自于CPU手册,在内存基础知识(八)-- 如何统计内存流量中有链接和描述)
得到编号很简单,但要得到UNC_M_RD_CAS_RANK0.ALLBANKS的type,就比较复杂了。
CPU手册中没告诉你,你在其他地方也很难找到(AI也不知道)。我是翻了perf的源码才找到的。
首先,对于内存相关的PMC,其type就是通道号。“通道号”这个信息如何得到呢?
Linux把CPU中的很多东西,都映射到/sys/devices目录中,我们要进到这个目录中自己查:
[root@oracle devices]#ls -lFrttotal 0。。。。。。drwxr-xr-x  5 root root 0 Oct  8 11:19 uncore_imc_4/drwxr-xr-x  5 root root 0 Oct  8 11:19 uncore_imc_3/drwxr-xr-x  5 root root 0 Oct  8 11:19 uncore_imc_2/drwxr-xr-x  5 root root 0 Oct  8 11:19 uncore_imc_1/drwxr-xr-x  5 root root 0 Oct  8 11:19 uncore_imc_0/。。。。。。
这里的子目录uncore_imc_0、uncore_imc_1,……,就是内存控制器中的通道号。
注意,虽然刺客我经常打错字,但这里并没有打错。
我敢保证,其他地方从来没有告诉过你这一点。由于Intel的内存控制器(MC)就叫IMC,从名字上,imc 0、imc 1,……,你会觉得它们分别对应0号MC和1号MC等等,实际上并不是。
它们是内存通道号
我这款CPU中,IMC只有一个。每个IMC有多条内存通道,但只有两条通道上插了内存条:uncore_imc_0 和 uncore_imc_1。
其实我也不确定有几个内存通道上插了内存条,dmidecode 命令没有得到通道信息。
我使用perf_event_open()打开通道中关于流量的PMC计数器,直接查看看内存流量就知道了,除 uncore_imc_0 和 uncore_imc_1 外,其他通道没有流量。
怎么打开通道中关于流量的PMC计数器?还差type(内存通道号)呢,它是这样查看的,如下:
[root@oracle ~]#cd /sys/devices[root@oracle devices]#cat uncore_imc_0/type17[root@oracle devices]#cat uncore_imc_1/type18

uncore_imc_N目录下,有一个type文件,就是Linux映射出的type。uncore_imc_0的type是17,uncore_imc_1的type是18。

也就是说,我这里两个内存通道的编号分别是17、18。

如果我指定打开type为17,编号为0x10b0的计数器,就是打开内存通道uncore_imc_0中的UNC_M_RD_CAS_RANK0.ALLBANKS计数器。它将统计经由0号内存通道,发向RANK0的读内存流量
perf_event_open()第一个参数是struct perf_event_attr *attr,我们需要如下设值,就能打开uncore_imc_0中的
UNC_M_RD_CAS_RANK0.ALLBANKS计数器:
struct perf_event_attr {    __u32 type;                 /* 这个值定为 17 */    __u32 size;                 /* Size of attribute structure */    __u64 config;               /* 这个值定为 0x10b0 */    ......

你man一下perf_event_open(),翻到最后,有个perf_event_open的例子:

[root@el8-168 ~]# man perf_event_openPERF_EVENT_OPEN(2)                                  Linux Programmer's Manual                                 PERF_EVENT_OPEN(2)......EXAMPLE       The following is a short example that measures the total instruction count of a call to printf(3).              #include <stdlib.h>       #include <stdio.h>       #include <unistd.h>       #include <string.h>       #include <sys/ioctl.h>       #include <linux/perf_event.h>       #include <asm/unistd.h>       static long       perf_event_open(struct perf_event_attr *hw_event, pid_t pid,                       int cpu, int group_fd, unsigned long flags)       {           int ret;           ret = syscall(__NR_perf_event_open, hw_event, pid, cpu,                          group_fd, flags);           return ret;       }       int       main(int argc, char **argv)       {           struct perf_event_attr pe;           long long count;           int fd;           memset(&pe, 0sizeof(struct perf_event_attr));           pe.type = 《《这里改为上面得到的通道号:type》》           pe.size = sizeof(struct perf_event_attr);           pe.config = 《《这里改为PMC编号》》;           pe.disabled = 1;           pe.exclude_kernel = 1;           pe.exclude_hv = 1;。。。。。。

把这个例子粘贴到编辑器中,修改一下pe.type和pe.cofig,就可以了。下面是我的程序的输出结果:

[root@oracle ff]#./memtest 1 1 64 1 163840==========Parent PID is 11423==========PID is 11425 -----------SUB Process at CPU: 1----------                   PERF_COUNT_HW_CACHE_L1D_RD (1):       164342.00 (100.00%)          164342                          L2_RQSTS.REFERENCES (1):       163905.00 (100.00%)          163905                                LLC Reference (1):       163855.00 (100.00%)          163855                                   LLC Misses (1):       163840.00 (100.00%)          163840    uncore_imc_0/UNC_M_RD_CAS_RANK0.ALLBANKS (1):        43345.00 (100.00%)           43345    uncore_imc_0/UNC_M_RD_CAS_RANK4.ALLBANKS (1):        41115.00 (100.00%)           41115    uncore_imc_1/UNC_M_RD_CAS_RANK0.ALLBANKS (1):        41383.00 (100.00%)           41383    uncore_imc_1/UNC_M_RD_CAS_RANK4.ALLBANKS (1):        41105.00 (100.00%)           41105

我发起了163840次内存读,可以看到,流量被均分到0号通道的RANK0、RANK4和1号通道的RANK0、RANK4。

好了,本篇就到这里。基础原理有其复杂性,我尽量详细点讲述。基础原理相当于内功。内功越深厚,“招式”理解的越多、越准确。

【声明】内容源于网络
0
0
IT知识刺客
基础软件开发 HPC(高性能计算) HPC数据库研发 数据库内核 向量计算 计算机体系结构 数据库 DBA CPU原理
内容 83
粉丝 0
IT知识刺客 基础软件开发 HPC(高性能计算) HPC数据库研发 数据库内核 向量计算 计算机体系结构 数据库 DBA CPU原理
总阅读38
粉丝0
内容83