1. 墊話
本文標題叫“硬件 PMU”操作,而不是“PMU 硬件”操作,是為了有意強調上一篇文章所申明的概念:PMU 只是一種抽象,其可以是純軟件實現的,也可以是硬件實現的。所以本文分析“硬件 PMU”,是有其推廣意義的:“硬件 PMU”是 PMU 的一個特殊解,清楚了“硬件 PMU”的抽象及操作,基本也就清楚了 perf 框架對其他 PMU(軟件 PMU、trace point PMU)的抽象及操作。 軟件架構的設計無非就是前后端的雙向打通,前端是面向用戶的 user friendly 接口,考驗的是程序員對業務的理解與設計;后端是實現功能所依賴的基礎能力,考驗的是程序員對底層技術的掌握與應用;而前后端的打通,考驗的是程序員的能力、經驗與審美。 上一篇文章介紹的是 perf 的前端,本文介紹的是 perf 的后端,也就是硬件事件監控功能所仰仗的底層能力。
2. 綜述
本文乃內核 perf 框架解構系列文章第二篇。 PMU 的底層操作是很枯燥(architectural specific)的,但卻是繞不過去的一個話題,因為對 PMU 的抽象及操作,皆是 perf 框架設計的一部分。如果不弄清楚底層,代碼解構起來會非常吃力。 對硬件 PMU 的解構,我們進一步細分為若干文章,本文介紹硬件 PMU 的基本概念,后續文章會介紹具體的代碼編程,故本文是后續文章的前導。 本文所涉及 PMU 相關知識的細節,可參閱本號《Intel SDM 之 Performance Monitoring》,或 Intel SDM。 本文中的 “PMU” 皆指代硬件實現的 PMU,“事件”皆指代硬件事件。 本文代碼基于 4.9 內核。
3. 基本概念
3.1 PMU 是什么
所謂 PMU,就是 performance monitoring unit,顧名思義,其功能就是做性能(CPU 指標)的監控。PMU 是實現在 CPU 中的硬件,通過 MSR 接口操作。 PMU 可以監控 CPU core 上的性能數據,比如 instructions、cycles 等,還可以監控 CPU 與 uncore 之間交互的性能數據(如 L3 讀寫 miss)。所謂 uncore,就是 CPU package 中 CPU core 之外的部分,也就是 off-core sub-system,uncore 被一個 package 中的多個 core 所共享,典型如 L3 cache、Intel QuickPath Interconnect link logic 以及 integrated memory controller 等。
3.2 PMU 與 PMC
一個 HT(hyper threading)通常包含一個 PMU,而一個 PMU 中包含多個 PMC,所謂 PMC,就是 performance monitoring counter,一個 PMC 經過編程(MSR 接口)后,可以對一個指定的事件進行監控。換句話說,同一時刻,PMU 可以同時對多個事件進行監控。若要獲取性能數據,讀取指定 PMC 的計數即可(實際上 PMC 有計數和中斷兩種工作模式,但本文只討論簡單的計數模式)。 受限于硬件設計,一個 PMU 中 PMC 數量是有限的,在筆者的機器上,一個 HT(hyper threading)中只有 7 個 PMC。
3.3 PMC 使用范式
類似《淺度剖析內核 RDT 框架》一文 3.1.1 節中探討的 RDT 硬件使用范式,PMU 的常見用法亦有如下三種:
per-cpu 的性能監控
監控指定 CPU 的指定事件,該用法比較簡單。底層實現原理是,在指定 CPU 運行期間,無論其上運行的是什么 task,始終保持其某 PMC 被編程為監控指定事件即可。前后兩次讀取指定事件的計數值,即可得出這兩次采樣之間指定 CPU 指定事件的計數。
per-task 的性能監控
監控指定 task 的指定事件,該用法需要 OS 的支持。底層實現原理是,在指定 task 被調度到 CPU 上運行時(sched_in),將 PMC 編程為指定事件,在 task 被調度出 CPU 時(sched_out),disable 此 PMC,并將 PMC 的計數值保存到事件中。前后兩次讀取指定事件的計數值,即可得出這兩次采樣之間指定 task 指定事件的計數。
per-cgroup 的性能監控
監控指定 cgroup 的指定事件,該用法需要 OS 的支持。實現原理同 per-task,不贅述。
3.4 perf 框架對 PMC 使用范式的增強
前文 4.2 節中提到,perf 框架允許用戶指定一次性監控一個事件組。 所謂事件組,就是一組事件,用戶通過 perf 前端接口指定組的 leader(group leader,本質上就是 perf_event_open 打開事件后返回的一個 fd),并通過前端接口向這個組內添加成員。一個組內的所有事件,必須被同時“調度”到 CPU 上,這里事件的“調度”,就是為事件分配、使能相應的 PMC;并且在讀 group leader 時,會將整組事件的計數同時讀出。 前一篇文章有粗略提過,但又言之未盡。perf 前端允許對指定 cpu 的指定事件(組)、指定 task 的指定事件(組)、指定 cgroup 的指定事件(組)進行監控。不同的前端接口調用者(應用),其監控需求不盡相同,而 perf 框架通過其設計與實現,兼容了不同應用的不同需求。
3.5 PMU version
本文主要基于 Intel x86 架構的 PMU 操作展開。Intel 的 PMU 架構,隨著 PMU version 的演進,又有著不同的 feature 增強,與本文強相關是 version 2 相對 version 1 引入的 fixed-function PMC,本文稱其為 fixed PMC。 因為本人機器的 PMU version 是 4,所以其必然是支持 fixed PMC feature 的。非 fixed PMC 本文稱之為 generic PMC。
3.6 fixed 與 generic PMC
所謂 fixed PMC,是指這類 PMC 只能監控其對應的特定事件。如 圖 1:
圖 1 圖 1 蘊含的信息有:
version 2 定義了 4 個 fixed PMC(機器上實際 fixed PMC 個數取決于具體實現,在我的機器上是 3 個,后面會介紹如何獲取這些信息),分別為 IA32_FIXED_CTR[0-3]。
IA32_FIXED_CTR0,也即 0 號 fixed PMC,其地址為 309H,其可以且只可以監控 INST_RETIRED.ANY 事件。其他 PMC 類推。
具體 INST_RETIRED.ANY 事件:
圖 2 而 generic PMC,其可以通過編程(也即指定 umask、event)監控 CPU 手冊中所規定的任意事件,也包括 fixed PMC 所能監控的事件。
4. 基本操作及編程
4.1 PMU 信息獲取
PMU 信息包括但不限于 version、generic PMC 數量、fixed PMC 數量、PMC 寄存器的數據長度等。 通過 CPUID.0AH 獲取。具體代碼沒有貼的意義,后續文章會有,一看就明白。
4.2 事件(PMC)的配置及讀取
4.2.1 generic PMC
1. 配置
通過 IA32_PERFEVTSELx 寄存器(event select 寄存器)對 generic PMC 進行設置,x 為 PMC 的編號。 IA32_PERFEVTSELx 寄存器的基地址為 186H(內核代碼:MSR_ARCH_PERFMON_EVENTSEL0),編號為 x 的 PMC,其 event select 寄存器地址為 MSR_ARCH_PERFMON_EVENTSEL0 + x。 IA32_PERFEVTSELx 格式如下:
圖 3
通過 umask + event select 域指定所要監控的事件。
USR、OS 位,分別表示是否使能處理器用戶模式及內核模式下的監控。perf 框架下,是否使能此二者 bit,取決于前端(perf_event_open 接口)傳入 perf_event_attr 的 exclude_user、exclude_kernel 參數。
EN 位,表示是否使能此 PMC。
其他位,參考 SDM Section 18.2.1.1。
generic PMC 配置及使能代碼,參考內核 x86_pmu_hw_config、__x86_pmu_enable_event、x86_pmu_disable_event(不看亦無妨,后續會有代碼實現的文章)。
2. 讀取
根據 SDM Section 18.2.1.1,通過 generic PMC 對應的 IA32_PMCx 寄存器來獲取 PMC 的計數值,x 為 generic PMC 的編號,這組寄存器基地址為 0C1H(內核代碼:MSR_ARCH_PERFMON_PERFCTR0)。 內核中對 PMC 的讀取不是通過 IA32_PMCx 寄存器實現,而是通過 rdpmc 指令,rdpmc 的入參是 PMC 的編號,如果是讀 0 號 generic PMC,則傳入 0 即可。 generic PMC 的讀取代碼,參考內核 x86_assign_hw_event、x86_perf_event_update。
4.2.2 fixed PMC
1. 配置
通過 IA32_FIXED_CTR_CTRL ?寄存器,該寄存器地址為 38DH(內核代碼:MSR_ARCH_PERFMON_FIXED_CTR_CTRL)。 不同于 generic PMC 的編程,每個 generic PMC 都有一個各自對應的 event select 寄存器。而所有 fixed PMC 皆通過 IA32_FIXED_CTR_CTRL 寄存器完成:
圖 4 可以看到,從 bit 0 到 bit 12,每 4 個 bit 控制一個 fixed PMC,編號為 x 的 fixed PMC,其在 IA32_FIXED_CTR_CTRL 中配置 bit 的偏移為 1 << (x * 4)。 你可能會問,該寄存器為啥沒有類似 generic PMC event select 寄存器的 umask 和 event select?因為它們是 fixed 的,fixed PMC 的編號與事件的對應關系見 圖 1。 fixed PMC 配置及使能代碼,參考內核 x86_pmu_hw_config、intel_pmu_enable_fixed、intel_pmu_disable_fixed。
2. 讀取
根據 SDM Section 18.2.2,通過 fixed PMC 對應的 IA32_FIXED_CTRx 寄存器來獲取 PMC 的計數值,x 為 PMC 的編號。如 圖 1,這組寄存器基地址為 309H(內核代碼:MSR_ARCH_PERFMON_FIXED_CTR0)。 內核中對 PMC 的讀取不是通過 IA32_FIXED_CTRx 寄存器實現,而是通過 rdpmc 指令,rdpmc 的入參是 (x - INTEL_PMC_IDX_FIXED) | 1<<30,其中 x 為 fixed PMC 的編號,INTEL_PMC_IDX_FIXED 為 32。 fixed PMC 的讀取,同 generic PMC,參考內核 x86_assign_hw_event、x86_perf_event_update。
4.3 PMU 的配置及使能
所謂使能/禁能 PMU,其本質是一次性使能/禁能 PMU 的所有 PMC。一個自然而然的想法是對上述提到的所有寄存器分別進行相應使能/禁能配置。 但 Intel 提供了更方便的總控寄存器 IA32_PERF_GLOBAL_CTRL(內核代碼:MSR_CORE_PERF_GLOBAL_CTRL):
圖 5 參考內核 __intel_pmu_enable_all、__intel_pmu_disable_all。 值得注意的是:IA32_PERF_GLOBAL_CTRL 寄存器中,為 fixed PMC 預留的偏移是從 32 開始的,換句話說,至少從目前的 Intel CPU 設計來說,其為 generic PMC 預留的數量為 32。這也是 4.2.2 中提到的,內核 INTEL_PMC_IDX_FIXED 宏為 32 的原因。
5. 伏筆
上一章就 PMU 的最基本操作進行了闡述,實際上 perf 框架中對 PMU 的管理、操作遠超這些范疇。 本章意在拋出問題,為后續文章埋個伏筆:
PMC 的分時復用問題:如你所知,PMC 的數量有限,如果要監控的事件數超出 PMC 數(具體來說,對某個 CPU 有多個監控事件組,所有事件組的事件數總和完全有可能超過 PMC 總數),必然要求 perf 框架有 PMC 分時復用的機制。
事件的 PMC 分配問題:當用戶指定要監控某個事件時,到底應該為其分配哪個 PMC?如果是 fixed 事件,是否要傾向于優先為其分配 fixed PMC?
事件的 PMC 寄存器配置生成問題:拿 generic PMC 來說,其 event select 寄存器的配置,是如何從前端接口的參數配置生成出來的(以在合適的時機寫入硬件寄存器)?尤其是 hardware、hw_cache 這類采用通用 ID(前文 3.2 節)的事件,是如何轉成底層的 umask 和 event select 的?
6. 總結
本文探討了 PMU 的基本概念,并討論了 PMU 的基本操作及編程,文章的最后拋出了 perf 框架實現中所面臨的若干實際問題。 編輯:黃飛
?
評論