1. 綜述
本文乃內核 perf 框架解構系列文章第三篇。 《[perf 2] perf 后端:硬件 PMU(上)》一文,我們討論了 PMU 硬件的基本使用范式,架構相關的概念,以及寄存器層面的基本操作及編程。 本文在上文基礎上進行編碼實踐,目的是展示 Intel x86 架構下硬件 PMU 的編程操作,并從實踐中提煉 perf 框架所面臨和要解決的問題。 本文在 4.9 內核、skylake 超線程平臺上編寫一個內核模塊,模塊的功能是使用硬件 PMU 采集相應事件。本文代碼與 OS、處理器平臺關系不是很大,可以移植到其他平臺。 閱讀本文之前,強烈建議先閱讀《[perf 2] perf 后端:硬件 PMU(上)》。 本文所有代碼在第 7 章附錄中,讀者自行編譯成內核模塊運行驗證(請不要直接在生產環境中干這事)。
2. 事件設計
本文通過硬件 PMU,采集 CPU 3 上的 'Instruction Retired' 及 'UnHalted Core Cycles' 事件。 之所以選擇此二者事件,是因為這二者不僅可以通過 generic PMC(指定 umask + eventsel 的方式)采集,亦可通過 fixed PMC 采集,如此我們可以同時利用兩種 PMC 來對此二者事件進行監控,通過相互對比采集的事件數值確認程序的正確性(符合預期的情況是,兩種 PMC 采集出來的事件數值幾乎相等)。 根據 Intel SDM Chapter 18 "Performance Monitoring",此二者事件是 architecture 的。所謂 architecture,指的是在不同架構之間皆相同,其反義詞是 model specific。 'Instruction Retired' 事件的 umask 和 eventsel 分別是 00H 和 C0H;'UnHalted Core Cycles' 事件的 umask 和 eventsel 分別是 00H 和 3CH:
圖 1 同時,此二者還可以通過第 0 和 第 1 個 fixed PMC 采集:
圖 2 若要查詢非 architecture 事件的 umask、eventsel,自行參閱 Intel SDM 19 "Performance Monitoring Events",不在本文話題范圍內。
3. 編碼設計
按說事件設計好,知道怎么用內核接口做 MSR 讀寫、發起 smp_call 以及起 hrtimer,基本上就可以開干了。 但為了讓代碼看起來不是這么的 naive,我們稍微做一點點設計。
3.1 PMC ID 編碼空間劃分
如你所知,generic PMC 和 fixed PMC,在實際編程中都是通過 ID 來索引的,也就是第幾個 generic/fixed PMC。而此二者 ID 編碼都是從 0 開始。 為了不讓二者的 ID 編碼混淆,我們對這二者 PMC 的 ID 做編碼空間劃分。根據《[perf 2] perf 后端:硬件 PMU(上)》“4.3 PMU 的配置及使能”一節,“至少從目前的 Intel CPU 設計來說,其為 generic PMC 預留的數量為 32”,一個合理的編碼空間劃分方案如下:
0 - 31:用于 generic PMC 的 ID 編碼。
32 - :用于 fixed PMC 的 ID 編碼。
我們參考內核的實現,這里將 32 這個魔術字定義為宏:
#define INTEL_PMC_IDX_FIXED 32所以,編碼為 32 的 fixed PMC,對應的是第 0 個 fixed PMC,以此類推。
3.2 PMU 抽象
數據結構如下(之所以加個 my_ 前綴,是因為內核中也有 x86_pmu 的數據結構,雖然本人很反感 my_ 前綴的 naming,但先將就著。下文的數據結構同理。):
struct my_x86_pmu { int version; unsigned eventsel; unsigned perfctr; int num_counters; int num_counters_fixed; int cntval_bits; int (*rdpmc_index)(int index); void (*enable)(struct my_perf_event *); void (*disable)(struct my_perf_event *); void (*read)(struct my_perf_event *event); void (*hw_config)(struct my_perf_event *event); };
version:PMU 的版本。
eventsel:PMU IA32_PERFEVTSELx 寄存器起始地址。實際上,eventsel 是寄存器組,起始地址為 186H,一個 IA32_PERFEVTSELx 對應一個 generic PMC,用于對此 generic PMC 進行配置。
perfctl:PMU 的 IA32_PMCx 寄存器起始地址。實際上,perfctl 是一個寄存器組,起始地址為 0C1H,可以通過 IA32_PMCx 讀出一個 generic PMC 的數值(本文采用與內核相同的方式,使用 RDPMC 指令,而不是從 IA32_PMCx 中讀?。?/p>
num_counters:generic PMC 數量。
num_counters_fixed:fixed PMC 數量。
cntval_bits:PMC 數據讀出寬度。不同 PMU 實現,其 PMC 數據寬度可能是 48 bit 或其他,但內核的 perf 框架側,會統一將其轉成 64 bit 輸出,方法是符號擴展,具體符號擴展算法需要依賴 ?PMC 的實際數據寬度,也就是 cntval_bits。
rdpmc_index:如前文所言,內核 perf 框架中讀取 PMC 數據時,使用的是 RDPMC 指令,此指令需要傳入一個 index,rdpmc_index 函數將 PMC 的 ID 轉成 RDPMC 指令所需的 index。實際上,此函數并未實現,Intel 平臺下,讀取 generic PMC 數值的 RDPMC index 參數,就是此 PMC 的 ID。fixed PMC index 的計算略有不同,參考下文“4.4.2 x86_assign_hw_event”一節。
enable/disable/read:使能、禁能、讀取一個 event。
hw_config:此函數獲取一個事件在寄存器層面的配置,參考下文“4.3.3 intel_pmu_hw_config”一節。
3.3 事件抽象
3.3.1 業務層抽象
所謂的業務層,指的是用戶通過 perf 前端接口傳進來的事件屬性。
struct my_perf_event_attr { u64 config; }; struct my_perf_event { local64_t count; struct my_perf_event_attr attr; struct my_hw_perf_event hw; };
my_perf_event_attr.config:這是業務層傳入的事件屬性,是業務層語義,注意與下一小節中硬件層抽象 config 的區別。
count:該事件的具體數值,用戶讀取一個事件的具體數值時,返回的就是這個值,參考下文“4.5 事件讀取”一節。
attr:事件中保存的業務層所傳入的屬性。
hw:一個業務層的事件,其具體采集任務需要一個具體的硬件 PMC,struct my_hw_perf_event 即是對硬件 PMC 的抽象。參考下一小節。
3.3.2 硬件層抽象
struct my_hw_perf_event { struct { /* hardware */ u64 config; unsigned long config_base; unsigned long event_base; int event_base_rdpmc; int idx; }; local64_t prev_count; };
config:事件的硬件層配置,對應一個 PMC 配置寄存器的配置,比如是否使能用戶態(USER)下的事件采集。
config_base:此 PMC 的配置寄存器,對于 generic PMC,其是 IA32_PERFEVTSELx,對于 fixed PMC,其是 IA32_FIXED_CTR_CTRL 。
event_base:此 PMC 的讀數寄存器,對于 generic PMC,其是 IA32_PMCx,對于 fixed PMC,其是 IA32_FIXED_CTRx。
event_base_rdpmc:使用 RDPMC 指令讀取此 PMC 數值時,應該傳入的 index 參數。
idx:此 PMC 的 ID 編碼,注意,這是經過 ID 編碼空間劃分后的 ID。
prev_count:上一次從該 PMC 讀出的事件計數值。
3.4 設計大圖
圖 3
4. 工程實踐
4.1 模型
代碼的模型,是在一個 kthread 中,監控 3 號 CPU 的 instruction 和 cycles 事件,并同時采用 generic 和 fixed PMC 兩種方案:
static int test_loop(void *__) { unsigned long long prev_count[4] = { 0 }, now_count[4]; struct my_perf_event events[4]; int target_cpu = 3, i; // suppose we are using the #2 generic pmc for 'Instruction Retired', umask 00h, event C0h // suppose we are using the #3 generic pmc for 'UnHalted Core Cycles', umask 00h, event 3Ch my_perf_event_init(&events[0], 0x00, 0xc0, 2, false); my_perf_event_init(&events[1], 0x00, 0x3c, 3, false); // suppose we are using the #0 fixed pmc for 'Instruction Retired', event C0h // suppose we are using the #1 fixed pmc for 'UnHalted Core Cycles', event 3Ch my_perf_event_init(&events[2], 0x00, 0xc0, 0, true); my_perf_event_init(&events[3], 0x00, 0x3c, 1, true); for (i = 0; i < ARRAY_SIZE(events); ++i) my_perf_event_enable(target_cpu, &events[i]); for (i = 0; i < ARRAY_SIZE(events); ++i) prev_count[i] = my_perf_event_read(target_cpu, &events[i]); while (!kthread_should_stop()) { for (i = 0; i < ARRAY_SIZE(events); ++i) { now_count[i] = my_perf_event_read(target_cpu, &events[i]); printk("event %d: %llu ", i, now_count[i] - prev_count[i]); prev_count[i] = now_count[i]; } msleep_interruptible(5000); } for (i = 0; i < ARRAY_SIZE(events); ++i) my_perf_event_disable(target_cpu, &events[i]); return 0; } static int __init test_module_init(void) { intel_pmu_init(); loop_task = kthread_run(test_loop, NULL, "test_loop"); return 0; } static void __exit test_module_exit(void) { kthread_stop(loop_task); }模型總體邏輯如下:
事件初始化(7 - 15 行):調用 my_perf_event_init 接口初始化四個 PMC 監控事件,前兩個采用 generic PMC 來監控,后兩個采用 fixed PMC 來監控。
事件使能(17 - 18 行):調用 my_perf_event_enable 接口使能上一步初始化的四個事件。
事件初始值讀?。?0 - 21 行):調用 my_perf_event_read 接口讀出四個事件的初始值。注意,PMC 第一次使用時,其中可能有殘留的值(上一次被使用后殘留的值),為了獲取準確的讀數,我們先讀出其初始值,后續取每兩次讀數的差值,也即凈增長值。
事件凈增長值讀?。?3 - 31 行):調用 my_perf_event_read 接口讀出四個事件的當前值,并與上一次的讀數做差獲取凈增長值。本文模型下,每 5 秒采集一次。
事件禁能(33 - 34 行):事件禁能。
PMU 初始化(41 行):module 的 init 函數中,調用 intel_pmu_init 初始化 PMU。
下面,逐個拆解函數的具體實現。
4.2 PMU 初始化
my_x86_pmu 是對 x86 體系結構 PMU 的抽象,其各項回調函數,由具體處理器平臺實現。 本文實驗是在 Intel x86 平臺上,底層函數是 intel_pmu_ 前綴的 Intel PMU 實現。
static struct my_x86_pmu x86_pmu = { .eventsel = MSR_ARCH_PERFMON_EVENTSEL0, .perfctr = MSR_ARCH_PERFMON_PERFCTR0, .enable = intel_pmu_enable_event, .disable = intel_pmu_disable_event, .read = intel_pmu_read_event, .hw_config = intel_pmu_hw_config, };intel_pmu_init 中做 PMU 的基本信息獲?。?
void intel_pmu_init(void) { union cpuid10_edx edx; union cpuid10_eax eax; union cpuid10_ebx ebx; unsigned int unused; int version; cpuid(10, &eax.full, &ebx.full, &unused, &edx.full); version = eax.split.version_id; x86_pmu.version = version; x86_pmu.num_counters = eax.split.num_counters; x86_pmu.cntval_bits = eax.split.bit_width; if (version > 1) { int assume = 3 * !boot_cpu_has(X86_FEATURE_HYPERVISOR); x86_pmu.num_counters_fixed = max((int)edx.split.num_counters_fixed, assume); } printk("... version: %d ", x86_pmu.version); printk("... bit width: %d ", x86_pmu.cntval_bits); printk("... generic registers: %d ", x86_pmu.num_counters); printk("... fixed-purpose events: %d ", x86_pmu.num_counters_fixed); printk("... x86_model: %d ", boot_cpu_data.x86_model); }簡單講一下:
9 行:通過 cpuid 獲取 PMU 基本信息。
17 - 22 行:根據 PMU 版本信息,計算 num_counters_fixed,也就是 fixed PMC 的數量。具體算法有點 specific,不必深究。
24 - 28 行:打印 PMU 信息概覽。
在我的機器上:
... version: 4 ... bit width: 48 ... generic registers: 4 ... fixed-purpose events: 3 ... x86_model: 85
4.3 事件初始化
總體邏輯:
static void intel_pmu_hw_config(struct my_perf_event *event) { event->hw.config = ARCH_PERFMON_EVENTSEL_INT; event->hw.config |= ARCH_PERFMON_EVENTSEL_USR; event->hw.config |= ARCH_PERFMON_EVENTSEL_OS; event->hw.config |= event->attr.config & X86_RAW_EVENT_MASK; } static void x86_pmu_event_init(struct my_perf_event *event) { x86_pmu.hw_config(event); } static void perf_init_event(struct my_perf_event *event) { x86_pmu_event_init(event); } static void my_sys_perf_event_open(struct my_perf_event *event, struct my_perf_event_attr *attr, int idx, bool fixed) { memset(event, 0, sizeof(*event)); event->attr = *attr; if (fixed) event->hw.idx = idx + INTEL_PMC_IDX_FIXED; else event->hw.idx = idx; perf_init_event(event); } static void my_perf_event_init(struct my_perf_event *event, u64 umask, u64 eventsel, int idx, bool fixed) { struct my_perf_event_attr attr; memset(&attr, 0, sizeof(attr)); attr.config = (umask << 4) | eventsel; my_sys_perf_event_open(event, &attr, idx, fixed); }下面逐個講解重要的函數。
4.3.1 my_perf_event_init
此函數接受 4 個參數,事件的抽象數據結構、事件的 umask/eventsel、事件底層 PMC ID 編號,此事件底層 PMC 是否是 fixed。 框架側調用點的注釋寫的很清楚了,使用第 2、3 個 generic PMC,分別對 instruction、cycles 兩個事件做監控;同時使用第 0、1 個 fixed PMC,分別對 instruction、cycles 兩個事件做監控:
// suppose we are using the #2 generic pmc for 'Instruction Retired', umask 00h, event C0h // suppose we are using the #3 generic pmc for 'UnHalted Core Cycles', umask 00h, event 3Ch my_perf_event_init(&events[0], 0x00, 0xc0, 2, false); my_perf_event_init(&events[1], 0x00, 0x3c, 3, false); // suppose we are using the #0 fixed pmc for 'Instruction Retired', event C0h // suppose we are using the #1 fixed pmc for 'UnHalted Core Cycles', event 3Ch my_perf_event_init(&events[2], 0x00, 0xc0, 0, true); my_perf_event_init(&events[3], 0x00, 0x3c, 1, true);這里要注意,兩個 generic PMC 的 ID 是我隨便選的(我的機器上不大于 generic PMC 的最大編號,也就是 3 即可),兩個 fixed PMC 的 ID 需要嚴格與 SDM 上的一致,也就是 0、1。參考本文“2. 事件設計”一節。 my_perf_event_init 函數中將 umask、eventsel 按照 architecture specific 的約束,構造成事件屬性的 config 域,最后調用 my_sys_perf_event_open 接口打開此事件。
4.3.2 my_sys_perf_event_open
此接口:
將事件屬性保存到事件數據結構中。
根據是否是 fixed PMC,對 PMC 的 ID 做編碼空間上的隔離,注意這里的編碼信息記錄到事件的硬件抽象層,也就是 my_hw_perf_event 中。
最后調用 perf_init_event,此函數最終調用到 intel_pmu_hw_config,對事件的硬件層抽象做初始化。
4.3.3 intel_pmu_hw_config
為什么要配置成這樣,參考下面兩個寄存器:
圖 4
圖 5 在我們的 intel_pmu_hw_config 實現中,使能了如下 bit 位:
INT:如果 PMC overflow,則處理器通過其 LAPIC 觸發一個 exception。
USR:使能 CPU 在 ring 1、2、3 特權級下的事件計數(簡單理解為,使能 CPU 運行在用戶態時的事件計數)。
OS:使能 CPU 在 ring 0 特權級下的事件計數(簡單理解為,使能 CPU 運行在內核態時的事件計數)。
UMASK + EventSelect:將業務層傳入的事件 umask、eventsel,轉成硬件層 PMC 的配置。
此寄存器 bit 位詳解,參考 SDM "18.2.1.1 Architectural Performance Monitoring Version 1 Facilities"。 注意:我們的代碼模型下,hardcode 了寄存器的這幾個 bit,實際的內核 perf 框架中,當然不是 hardcode 的,而是根據 perf 前端接口所傳入的事件屬性,決定對應 bit 位是否置上。 比如 USER、OS bit 位是否置上,取決于 perf_event_attr 中的 exclude_user、exclude_kernel 配置:
圖 6
4.4 事件使能
總體邏輯:
static void __x86_pmu_enable_event(struct my_hw_perf_event *hwc, u64 enable_mask) { wrmsrl(hwc->config_base, hwc->config | enable_mask); } static void intel_pmu_enable_fixed(struct my_perf_event *event) { u64 ctrl_val, mask, bits = 0; struct my_hw_perf_event *hwc = &event->hw; int idx = hwc->idx; bits |= 0x8; if (hwc->config & ARCH_PERFMON_EVENTSEL_USR) bits |= 0x2; if (hwc->config & ARCH_PERFMON_EVENTSEL_OS) bits |= 0x1; idx -= INTEL_PMC_IDX_FIXED; bits <<= (idx * 4); mask = 0xfULL << (idx * 4); rdmsrl(hwc->config_base, ctrl_val); ctrl_val &= ~mask; ctrl_val |= bits; wrmsrl(hwc->config_base, ctrl_val); } static void intel_pmu_enable_event(struct my_perf_event *event) { struct my_hw_perf_event *hwc = &event->hw; switch (hwc->idx) { case 0 ... INTEL_PMC_IDX_FIXED - 1: __x86_pmu_enable_event(hwc, ARCH_PERFMON_EVENTSEL_ENABLE); break; case INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED + 15: intel_pmu_enable_fixed(event); break; }; } static void x86_pmu_enable(struct my_perf_event *event) { x86_assign_hw_event(event); x86_pmu.enable(event); } static void __my_perf_event_enable(void *info) { struct my_perf_event *event = info; x86_pmu_enable(event); } static void my_perf_event_enable(int target_cpu, struct my_perf_event *event) { (void)smp_call_function_single(target_cpu, __my_perf_event_enable, event, 1); }下面逐個講解重要的函數。
4.4.1 my_perf_event_enable
因為 PMU 是 per-cpu 的,要使能目標 CPU 上的 event(本質就是使能目標 CPU 上的某個 PMC),需要通過 smp_call 跨核調過去,回調是 __my_perf_event_enable,此回調中干了兩件事:
x86_assign_hw_event:為 event 分配一個 PMC,并初始化此 PMC 的寄存器信息。在咱們的模型下,event 的 PMC 是代碼中手動指定的(my_perf_event_init 中指定了 PMC 的 ID)。
intel_pmu_enable_event:寫配置寄存器的 enable bit,從硬件上使能 PMC。
4.4.2 x86_assign_hw_event
根據事件硬件層抽象中的 idx,區分其是 generic 還是 fixed PMC,并分別調用相應邏輯:
如果是 generic PMC(0 ... INTEL_PMC_IDX_FIXED - 1):config_base 是 PMC 對應的 IA32_PERFEVTSELx 寄存器,event_base 是 PMC 對應的 IA32_PMCx 寄存器(實際上此寄存器壓根沒用到),event_base_rdpmc 就是 PMC 的編號(generic PMC ID 編址空間下)。
如果是 fixed PMC(INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED + 15):config_base 是 IA32_FIXED_CTR_CTRL 寄存器(所有 fixed PMC 皆是通過這一個寄存器來配置),event_base 是 PMC 對應的 IA32_FIXED_CTRx 寄存器(實際上此寄存器壓根沒用到),event_base_rdpmc 的計算算法見代碼。
如果你對這些寄存器感到困惑,請參閱上一篇文章 “4.2 事件(PMC)的配置及讀取”一節。 此函數說白了,就是將一個事件與一個硬件 PMC 建立關系(assign)
static inline unsigned int x86_pmu_config_addr(int index) { return x86_pmu.eventsel + index; } static inline unsigned int x86_pmu_event_addr(int index) { return x86_pmu.perfctr + index; } static inline int x86_pmu_rdpmc_index(int index) { return x86_pmu.rdpmc_index ? x86_pmu.rdpmc_index(index) : index; } static void x86_assign_hw_event(struct my_perf_event *event) { struct my_hw_perf_event *hwc = &event->hw; switch (hwc->idx) { case 0 ... INTEL_PMC_IDX_FIXED - 1: hwc->config_base = x86_pmu_config_addr(hwc->idx); hwc->event_base = x86_pmu_event_addr(hwc->idx); hwc->event_base_rdpmc = x86_pmu_rdpmc_index(hwc->idx); break; case INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED + 15: hwc->config_base = MSR_ARCH_PERFMON_FIXED_CTR_CTRL; hwc->event_base = MSR_ARCH_PERFMON_FIXED_CTR0; hwc->event_base_rdpmc = (hwc->idx - INTEL_PMC_IDX_FIXED) | INTEL_PMC_FIXED_RDPMC_BASE; break; default: return; }; }
4.4.3 intel_pmu_enable_event
x86_assign_hw_event 函數中,為事件分配了一個硬件 PMC,并獲取了 PMC 的配置、讀數寄存器。 intel_pmu_enable_event 函數,基于上一步成果,真正地寫入寄存器,讓 PMC 硬件實際生效。
對于 generic PMC,使能 PMC 的代碼(__x86_pmu_enable_event)非常簡單,將 config 帶上 EN bit,然后寫入 IA32_PERFEVTSELx 寄存器,參考圖 4。
對于 fixed PMC,略顯復雜,但實際上也很簡單,其本質就是寫入 IA32_FIXED_CTR_CTRL 寄存器,參考圖 5。
4.5 事件讀取
my_perf_event_read 接口做了兩件事:
perf_event_read:此函數通過 smp_call,一路調用到 PMC 底層寄存器讀操作(x86_perf_event_update),通過 rdpmcl(底層是 RDPMC 指令),根據事件的 event_base_rdpmc,讀出 PMC 硬件中的值,并將讀出的硬件數據,做符號擴展至 64 bit,并將數值存到事件的 count 域中。
perf_event_count:基于上一步成果,將 event 的 count 域返回。
u64 x86_perf_event_update(struct my_perf_event *event) { struct my_hw_perf_event *hwc = &event->hw; int shift = 64 - x86_pmu.cntval_bits; u64 prev_raw_count, new_raw_count; u64 delta; again: prev_raw_count = local64_read(&hwc->prev_count); rdpmcl(hwc->event_base_rdpmc, new_raw_count); if (local64_cmpxchg(&hwc->prev_count, prev_raw_count, new_raw_count) != prev_raw_count) goto again; /* * Now we have the new raw value and have updated the prev * timestamp already. We can now calculate the elapsed delta * (event-)time and add that to the generic event. * * Careful, not all hw sign-extends above the physical width * of the count. */ delta = (new_raw_count << shift) - (prev_raw_count << shift); delta >>= shift; local64_add(delta, &event->count); return new_raw_count; } static void intel_pmu_read_event(struct my_perf_event *event) { x86_perf_event_update(event); } static void x86_pmu_read(struct my_perf_event *event) { x86_pmu.read(event); } static void __perf_event_read(void *info) { struct my_perf_event *event = info; x86_pmu_read(event); } static void perf_event_read(int target_cpu, struct my_perf_event *event) { (void)smp_call_function_single(target_cpu, __perf_event_read, event, 1); } static u64 perf_event_count(struct my_perf_event *event) { return local64_read(&event->count); } u64 my_perf_event_read(int target_cpu, struct my_perf_event *event) { perf_event_read(target_cpu, event); return perf_event_count(event); } 4.6 事件禁能
重點看 x86_pmu_disable_event(generic PMC)、intel_pmu_disable_fixed(fixed PMC),主體邏輯與事件使能類似,只不過是反著來的,操作的是同一個寄存器。讀者自行理解,不啰嗦了。
?
static inline void x86_pmu_disable_event(struct my_perf_event *event) { struct my_hw_perf_event *hwc = &event->hw; wrmsrl(hwc->config_base, hwc->config); } static void intel_pmu_disable_fixed(struct my_perf_event *event) { struct my_hw_perf_event *hwc = &event->hw; u64 ctrl_val, mask; int idx = hwc->idx; mask = 0xfULL << ((idx - INTEL_PMC_IDX_FIXED) * 4); rdmsrl(hwc->config_base, ctrl_val); ctrl_val &= ~mask; wrmsrl(hwc->config_base, ctrl_val); } static void intel_pmu_disable_event(struct my_perf_event *event) { struct my_hw_perf_event *hwc = &event->hw; switch (hwc->idx) { case 0 ... INTEL_PMC_IDX_FIXED - 1: x86_pmu_disable_event(event); break; case INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED + 15: intel_pmu_disable_fixed(event); break; }; } static void x86_pmu_disable(struct my_perf_event *event) { x86_pmu.disable(event); } static void __perf_event_disable(void *info) { struct my_perf_event *event = info; x86_pmu_disable(event); } static void my_perf_event_disable(int target_cpu, struct my_perf_event *event) { smp_call_function_single(target_cpu, __perf_event_disable, event, 1); }
5. 總結
我們的模型下,通過查閱 SDM,獲取所要監控事件的 umask、eventsel,并手動指定通過哪個 generic/fixed PMC 來監控對應事件。
模塊初始化函數中,獲取 PMU 信息,比較重要的是 PMU 的數據寬度(cntval_bits)。
事件初始化邏輯,將業務層傳入的事件屬性做記錄,并生成事件硬件層抽象的基本配置(基于 umask、eventsel),并將 PMC 的 id 做編碼空間隔離。
事件使能邏輯,根據 PMC 的 id,進一步設置事件硬件層抽象(本質上對應的就是一個 PMC)的寄存器信息,并寫入硬件寄存器,實際使能此 PMC。
事件讀取邏輯,通過 RDPMC 讀出事件對應 PMC 的數值,并做 64 bit 符號擴展。
事件禁能邏輯,與事件使能邏輯相反,寫入硬件寄存器禁能此 PMC。
6. 伏筆
行文至此,本文不僅沒有解答上一篇文章 “5. 伏筆”一節的任何問題,反而引入了更多問題:
我們所監控事件的 umask、eventsel 都是自己查 SDM 得來的,內核還支持通過事件的通用 ID 來指定,它是怎么實現的(《[perf 1] perf 前端》“3.2 事件類型”)?
我們對事件的 PMC 分配采用的是手動方式(手動指定 ID 以及是否使用 fixed PMC),內核 perf 框架顯然不可能也這么干,它是怎么實現的?
本文指定的兩個事件,即可以通過 generic PMC,也可以通過 fixed PMC,如果是內核 perf 框架,它會怎么做選擇?
我們指定的事件之間并無事件組關系,而我們知道內核 perf 框架還支持事件組的采集,它是怎么實現的?
我們的模型只是采集某個 cpu 的 PMU 事件,perf 框架還支持 per-task 的 PMU 事件采集,它是怎么實現的?
這些問題的解答留待內核 perf 框架的后續解構文章。 實際上,搞清楚了問題是什么,就已經搞清楚了問題總體的百分之八十。
7. 附錄
#include#include #include #include static struct task_struct *loop_task; /* * Performance event hw details: */ #define MSR_ARCH_PERFMON_EVENTSEL0 0x186 #define MSR_ARCH_PERFMON_PERFCTR0 0xc1 #define ARCH_PERFMON_EVENTSEL_EVENT 0x000000FFULL #define ARCH_PERFMON_EVENTSEL_UMASK 0x0000FF00ULL #define ARCH_PERFMON_EVENTSEL_USR (1ULL << 16) #define ARCH_PERFMON_EVENTSEL_OS (1ULL << 17) #define ARCH_PERFMON_EVENTSEL_EDGE (1ULL << 18) #define ARCH_PERFMON_EVENTSEL_PIN_CONTROL (1ULL << 19) #define ARCH_PERFMON_EVENTSEL_INT (1ULL << 20) #define ARCH_PERFMON_EVENTSEL_ANY (1ULL << 21) #define ARCH_PERFMON_EVENTSEL_ENABLE (1ULL << 22) #define ARCH_PERFMON_EVENTSEL_INV (1ULL << 23) #define ARCH_PERFMON_EVENTSEL_CMASK 0xFF000000ULL #define X86_RAW_EVENT_MASK (ARCH_PERFMON_EVENTSEL_EVENT | ARCH_PERFMON_EVENTSEL_UMASK | ARCH_PERFMON_EVENTSEL_EDGE | ARCH_PERFMON_EVENTSEL_INV | ARCH_PERFMON_EVENTSEL_CMASK) /* * All the fixed-mode PMCs are configured via this single MSR: */ #define MSR_ARCH_PERFMON_FIXED_CTR_CTRL 0x38d #define MSR_ARCH_PERFMON_FIXED_CTR0 0x309 /* RDPMC offset for Fixed PMCs */ #define INTEL_PMC_FIXED_RDPMC_BASE (1 << 30) #define INTEL_PMC_IDX_FIXED 32 struct my_hw_perf_event { struct { /* hardware */ u64 config; unsigned long config_base; unsigned long event_base; int event_base_rdpmc; int idx; }; local64_t prev_count; }; struct my_perf_event_attr { u64 config; }; struct my_perf_event { local64_t count; struct my_perf_event_attr attr; struct my_hw_perf_event hw; }; struct my_x86_pmu { int version; unsigned eventsel; unsigned perfctr; int num_counters; int num_counters_fixed; int cntval_bits; int (*rdpmc_index)(int index); void (*enable)(struct my_perf_event *); void (*disable)(struct my_perf_event *); void (*read)(struct my_perf_event *event); void (*hw_config)(struct my_perf_event *event); }; static void intel_pmu_enable_event(struct my_perf_event *event); static void intel_pmu_disable_event(struct my_perf_event *event); static void intel_pmu_read_event(struct my_perf_event *event); static void intel_pmu_hw_config(struct my_perf_event *event); static struct my_x86_pmu x86_pmu = { .eventsel = MSR_ARCH_PERFMON_EVENTSEL0, .perfctr = MSR_ARCH_PERFMON_PERFCTR0, .enable = intel_pmu_enable_event, .disable = intel_pmu_disable_event, .read = intel_pmu_read_event, .hw_config = intel_pmu_hw_config, }; static inline unsigned int x86_pmu_config_addr(int index) { return x86_pmu.eventsel + index; } static inline unsigned int x86_pmu_event_addr(int index) { return x86_pmu.perfctr + index; } static inline int x86_pmu_rdpmc_index(int index) { return x86_pmu.rdpmc_index ? x86_pmu.rdpmc_index(index) : index; } static void x86_assign_hw_event(struct my_perf_event *event) { struct my_hw_perf_event *hwc = &event->hw; switch (hwc->idx) { case 0 ... INTEL_PMC_IDX_FIXED - 1: hwc->config_base = x86_pmu_config_addr(hwc->idx); hwc->event_base = x86_pmu_event_addr(hwc->idx); hwc->event_base_rdpmc = x86_pmu_rdpmc_index(hwc->idx); break; case INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED + 15: hwc->config_base = MSR_ARCH_PERFMON_FIXED_CTR_CTRL; hwc->event_base = MSR_ARCH_PERFMON_FIXED_CTR0; hwc->event_base_rdpmc = (hwc->idx - INTEL_PMC_IDX_FIXED) | INTEL_PMC_FIXED_RDPMC_BASE; break; default: return; }; } void intel_pmu_init(void) { union cpuid10_edx edx; union cpuid10_eax eax; union cpuid10_ebx ebx; unsigned int unused; int version; cpuid(10, &eax.full, &ebx.full, &unused, &edx.full); version = eax.split.version_id; x86_pmu.version = version; x86_pmu.num_counters = eax.split.num_counters; x86_pmu.cntval_bits = eax.split.bit_width; if (version > 1) { int assume = 3 * !boot_cpu_has(X86_FEATURE_HYPERVISOR); x86_pmu.num_counters_fixed = max((int)edx.split.num_counters_fixed, assume); } printk("... version: %d ", x86_pmu.version); printk("... bit width: %d ", x86_pmu.cntval_bits); printk("... generic registers: %d ", x86_pmu.num_counters); printk("... fixed-purpose events: %d ", x86_pmu.num_counters_fixed); printk("... x86_model: %d ", boot_cpu_data.x86_model); } /////////////////////////////// // read logic u64 x86_perf_event_update(struct my_perf_event *event) { struct my_hw_perf_event *hwc = &event->hw; int shift = 64 - x86_pmu.cntval_bits; u64 prev_raw_count, new_raw_count; u64 delta; again: prev_raw_count = local64_read(&hwc->prev_count); rdpmcl(hwc->event_base_rdpmc, new_raw_count); if (local64_cmpxchg(&hwc->prev_count, prev_raw_count, new_raw_count) != prev_raw_count) goto again; /* * Now we have the new raw value and have updated the prev * timestamp already. We can now calculate the elapsed delta * (event-)time and add that to the generic event. * * Careful, not all hw sign-extends above the physical width * of the count. */ delta = (new_raw_count << shift) - (prev_raw_count << shift); delta >>= shift; local64_add(delta, &event->count); return new_raw_count; } static void intel_pmu_read_event(struct my_perf_event *event) { x86_perf_event_update(event); } static void x86_pmu_read(struct my_perf_event *event) { x86_pmu.read(event); } static void __perf_event_read(void *info) { struct my_perf_event *event = info; x86_pmu_read(event); } static void perf_event_read(int target_cpu, struct my_perf_event *event) { (void)smp_call_function_single(target_cpu, __perf_event_read, event, 1); } static u64 perf_event_count(struct my_perf_event *event) { return local64_read(&event->count); } u64 my_perf_event_read(int target_cpu, struct my_perf_event *event) { perf_event_read(target_cpu, event); return perf_event_count(event); } /////////////////////////////// // disable logic static inline void x86_pmu_disable_event(struct my_perf_event *event) { struct my_hw_perf_event *hwc = &event->hw; wrmsrl(hwc->config_base, hwc->config); } static void intel_pmu_disable_fixed(struct my_perf_event *event) { struct my_hw_perf_event *hwc = &event->hw; u64 ctrl_val, mask; int idx = hwc->idx; mask = 0xfULL << ((idx - INTEL_PMC_IDX_FIXED) * 4); rdmsrl(hwc->config_base, ctrl_val); ctrl_val &= ~mask; wrmsrl(hwc->config_base, ctrl_val); } static void intel_pmu_disable_event(struct my_perf_event *event) { struct my_hw_perf_event *hwc = &event->hw; switch (hwc->idx) { case 0 ... INTEL_PMC_IDX_FIXED - 1: x86_pmu_disable_event(event); break; case INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED + 15: intel_pmu_disable_fixed(event); break; }; } static void x86_pmu_disable(struct my_perf_event *event) { x86_pmu.disable(event); } static void __perf_event_disable(void *info) { struct my_perf_event *event = info; x86_pmu_disable(event); } static void my_perf_event_disable(int target_cpu, struct my_perf_event *event) { smp_call_function_single(target_cpu, __perf_event_disable, event, 1); } /////////////////////////////// // enable logic static void __x86_pmu_enable_event(struct my_hw_perf_event *hwc, u64 enable_mask) { wrmsrl(hwc->config_base, hwc->config | enable_mask); } static void intel_pmu_enable_fixed(struct my_perf_event *event) { u64 ctrl_val, mask, bits = 0; struct my_hw_perf_event *hwc = &event->hw; int idx = hwc->idx; bits |= 0x8; if (hwc->config & ARCH_PERFMON_EVENTSEL_USR) bits |= 0x2; if (hwc->config & ARCH_PERFMON_EVENTSEL_OS) bits |= 0x1; idx -= INTEL_PMC_IDX_FIXED; bits <<= (idx * 4); mask = 0xfULL << (idx * 4); rdmsrl(hwc->config_base, ctrl_val); ctrl_val &= ~mask; ctrl_val |= bits; wrmsrl(hwc->config_base, ctrl_val); } static void intel_pmu_enable_event(struct my_perf_event *event) { struct my_hw_perf_event *hwc = &event->hw; switch (hwc->idx) { case 0 ... INTEL_PMC_IDX_FIXED - 1: __x86_pmu_enable_event(hwc, ARCH_PERFMON_EVENTSEL_ENABLE); break; case INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED + 15: intel_pmu_enable_fixed(event); break; }; } static void x86_pmu_enable(struct my_perf_event *event) { x86_assign_hw_event(event); x86_pmu.enable(event); } static void __my_perf_event_enable(void *info) { struct my_perf_event *event = info; x86_pmu_enable(event); } static void my_perf_event_enable(int target_cpu, struct my_perf_event *event) { (void)smp_call_function_single(target_cpu, __my_perf_event_enable, event, 1); } /////////////////////////////// // init logic static void intel_pmu_hw_config(struct my_perf_event *event) { event->hw.config = ARCH_PERFMON_EVENTSEL_INT; event->hw.config |= ARCH_PERFMON_EVENTSEL_USR; event->hw.config |= ARCH_PERFMON_EVENTSEL_OS; event->hw.config |= event->attr.config & X86_RAW_EVENT_MASK; } static void x86_pmu_event_init(struct my_perf_event *event) { x86_pmu.hw_config(event); } static void perf_init_event(struct my_perf_event *event) { x86_pmu_event_init(event); } static void my_sys_perf_event_open(struct my_perf_event *event, struct my_perf_event_attr *attr, int idx, bool fixed) { memset(event, 0, sizeof(*event)); event->attr = *attr; if (fixed) event->hw.idx = idx + INTEL_PMC_IDX_FIXED; else event->hw.idx = idx; perf_init_event(event); } static void my_perf_event_init(struct my_perf_event *event, u64 umask, u64 eventsel, int idx, bool fixed) { struct my_perf_event_attr attr; memset(&attr, 0, sizeof(attr)); attr.config = (umask << 4) | eventsel; my_sys_perf_event_open(event, &attr, idx, fixed); } static int test_loop(void *__) { unsigned long long prev_count[4] = { 0 }, now_count[4]; struct my_perf_event events[4]; int target_cpu = 3, i; // suppose we are using the #2 generic pmc for 'Instruction Retired', umask 00h, event C0h // suppose we are using the #3 generic pmc for 'UnHalted Core Cycles', umask 00h, event 3Ch my_perf_event_init(&events[0], 0x00, 0xc0, 2, false); my_perf_event_init(&events[1], 0x00, 0x3c, 3, false); // suppose we are using the #0 fixed pmc for 'Instruction Retired', event C0h // suppose we are using the #1 fixed pmc for 'UnHalted Core Cycles', event 3Ch my_perf_event_init(&events[2], 0x00, 0xc0, 0, true); my_perf_event_init(&events[3], 0x00, 0x3c, 1, true); for (i = 0; i < ARRAY_SIZE(events); ++i) my_perf_event_enable(target_cpu, &events[i]); for (i = 0; i < ARRAY_SIZE(events); ++i) prev_count[i] = my_perf_event_read(target_cpu, &events[i]); while (!kthread_should_stop()) { for (i = 0; i < ARRAY_SIZE(events); ++i) { now_count[i] = my_perf_event_read(target_cpu, &events[i]); printk("event %d: %llu ", i, now_count[i] - prev_count[i]); prev_count[i] = now_count[i]; } msleep_interruptible(5000); } for (i = 0; i < ARRAY_SIZE(events); ++i) my_perf_event_disable(target_cpu, &events[i]); return 0; } static int __init test_module_init(void) { intel_pmu_init(); loop_task = kthread_run(test_loop, NULL, "test_loop"); return 0; } static void __exit test_module_exit(void) { kthread_stop(loop_task); } module_init(test_module_init); module_exit(test_module_exit); MODULE_LICENSE("GPL");
編輯:黃飛
?
評論
查看更多