perf事件的切换发生在函数perf_event_task_sched_in
finish_task_switch函数中调用perf_event_task_sche_in
prepare_task_switch ---> finish_task_switch
理一下发生进程切换时的行为,perfs是注册到每个cpu上的,这是就有一个问题了,对于非进程的级的事,他是yon停歇的,但是对于进程的事件,那是要发生qiehua的,需要把本进程的计数器给stop掉,zheyan就不对我这个进程进行统计了,进程级别其实是keyi分时的,
如果都是系统级别的事件,那肯定是不能发生分时复用的
如何查看机器上PMU寄存器的个数;
如何查看机器上PMU寄存器的个数;
一个事件被分配一个寄存器吗?
x86_pmu.num_counters
x86_pmu_hw_config --> x86_setup_perfctr
func(x86_setup_perfctr) hwConfig(0x130000) type(0) PERF_RAW(4)
0xffffffff81006170 : x86_setup_perfctr+0x0/0x170 [kernel]
0xffffffff81006396 : x86_pmu_hw_config+0xb6/0x1c0 [kernel]
0xffffffff8100b732 : intel_pmu_hw_config+0x12/0x130 [kernel]
0xffffffff8100b862 : hsw_hw_config+0x12/0xb0 [kernel]
0xffffffff81005e85 : x86_pmu_event_init+0xd5/0x1f0 [kernel]
0xffffffff8117b426 : perf_try_init_event+0x76/0x90 [kernel]
0xffffffff8117edb9 : perf_event_alloc+0x5a9/0x6d0 [kernel]
0xffffffff81181b88 : SYSC_perf_event_open+0x3c8/0xdb0 [kernel]
0xffffffff81184809 : sys_perf_event_open+0x9/0x10 [kernel]
0xffffffff81824626 : tracesys_phase2+0x88/0x8d [kernel]
硬件事件肯定在哪个地方会分配硬件PMU
x86_assign_hw_event
0xffffffff81006cd6 : x86_pmu_enable+0x116/0x300 [kernel]
0xffffffff8117a8f7 : perf_pmu_enable.part.90+0x7/0x10 [kernel]
0xffffffff8117f541 : perf_pmu_enable+0x21/0x30 [kernel]
0xffffffff810052d0 : x86_pmu_commit_txn+0xd0/0x130 [kernel]
0xffffffff8117cba7 : group_sched_in+0x1a7/0x1c0 [kernel]
0xffffffff8117d8fc : __perf_event_enable+0x25c/0x290 [kernel]
0xffffffff8117a3fa : remote_function+0x3a/0x40 [kernel]
0xffffffff811038c6 : generic_exec_single+0xb6/0x120 [kernel]
0xffffffff811039fe : smp_call_function_single+0xce/0x130 [kernel]
0xffffffff8117dabc : _perf_event_enable+0x12c/0x140 [kernel]
0xffffffff81178838 : perf_event_for_each_child+0x38/0xa0 [kernel]
0xffffffff8118269e : perf_ioctl+0x12e/0x4c0 [kernel]
0xffffffff812200ff : do_vfs_ioctl+0x29f/0x490 [kernel]
0xffffffff81220369 : sys_ioctl+0x79/0x90 [kernel]
0xffffffff81824626 : tracesys_phase2+0x88/0x8d [kernel]
1078 * step2: reprogram moved events into new counters
1079 */
1080 for (i = 0; i < cpuc->n_events; i++) {
1081 event = cpuc->event_list[i]; [又是啥时候把这个事件放到了全局的CPU->event_list里去的]
1082 hwc = &event->hw;
1083
1084 if (!match_prev_assignment(hwc, cpuc, i))
1085 x86_assign_hw_event(event, cpuc, i);
1086 else if (i < n_running)
1087 continue;
1088
1089 if (hwc->state & PERF_HES_ARCH)
1090 continue;
1091
1092 x86_pmu_start(event, PERF_EF_RELOAD);
1093 }
那什么时候会分配这个值呢?
在x86_pmu_start中会有
1249 static void x86_pmu_start(struct perf_event *event, int flags)
1250 {
1251 struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
1252 int idx = event->hw.idx;
1253
1254 if (WARN_ON_ONCE(!(event->hw.state & PERF_HES_STOPPED)))
1255 return;
1256
1257 if (WARN_ON_ONCE(idx == -1))
1258 return;
1259
1260 if (flags & PERF_EF_RELOAD) {
1261 WARN_ON_ONCE(!(event->hw.state & PERF_HES_UPTODATE));
1262 x86_perf_event_set_period(event);
1263 }
1264
1265 event->hw.state = 0;
1266
1267 cpuc->events[idx] = event;
1268 __set_bit(idx, cpuc->active_mask);
1269 __set_bit(idx, cpuc->running);
1270 x86_pmu.enable(event);
1271 perf_event_update_userpage(event);
1272 }
那又是啥时候给event->hw.idx 分配数值的呢?
x86_pmu_add
831 /*
832 * Assign a counter for each event.
833 */
834 int perf_assign_events(struct event_constraint **constraints, int n,
835 int wmin, int wmax, int gpmax, int *assign)
836 {
837 struct perf_sched sched;
838
839 perf_sched_init(&sched, constraints, n, wmin, wmax, gpmax);
840
841 do {
842 if (!perf_sched_find_counter(&sched))
843 break; /* failed */
844 if (assign)
845 assign[sched.state. ] = sched.state.counter;
846 } while (perf_sched_next_event(&sched));
847
848 return sched.state.unassigned;
849 }
850 EXPORT_SYMBOL_GPL(perf_assign_events);
x86_pmu_enable的时候会更新上这个值
看下event_constraints函数中event->hw.config中会有
pmu的初始化过程也是一个有意思的过程
event_constraint 结构体
45 struct event_constraint {
46 union {
47 unsigned long idxmsk[BITS_TO_LONGS(X86_PMC_IDX_MAX)];
48 u64 idxmsk64;
49 };
50 u64 code;
51 u64 cmask;
52 int weight; weight中是idxmsk64中1的个数,是事件数吧?
53 int overlap;
54 int flags;
55 };
下面是我机器上的intel_hsw_event_constraints中的
event_constraints In this station: intel_hsw_event_constraints
---------event_constraint---------
idxmask:1000000ff, code:c0, cmask:3ff84ffff, weight:9, overlap:0, flags:0
idxmask:2000000ff, code:3c, cmask:3ff84ffff, weight:9, overlap:0, flags:0
idxmask:400000000, code:300, cmask:3ff84ffff, weight:1, overlap:0, flags:0
idxmask:4, code:148, cmask:ffff, weight:1, overlap:0, flags:0
idxmask:2, code:1c0, cmask:ffff, weight:1, overlap:0, flags:0
idxmask:8, code:cd, cmask:ff, weight:1, overlap:0, flags:0
idxmask:4, code:8a3, cmask:ffff, weight:1, overlap:0, flags:0
idxmask:4, code:ca3, cmask:ffff, weight:1, overlap:0, flags:0
idxmask:f, code:4a3, cmask:ffff, weight:4, overlap:0, flags:0
idxmask:ff, code:0, cmask:0, weight:8, overlap:0, flags:0 [unconstriand的事件,就是系统原生支持的事件 ]
某个事件是如何分配寄存器的
idxmask:f, code:d0, cmask:ff, weight:4, overlap:0, flags:40
idxmask:f, code:d1, cmask:ff, weight:4, overlap:0, flags:40
idxmask:f, code:d2, cmask:ff, weight:4, overlap:0, flags:40
idxmask:f, code:d3, cmask:ff, weight:4, overlap:0, flags:40
不受约束的事件:
unconstrained = (struct event_constraint)
__EVENT_CONSTRAINT(0, (1ULL << x86_pmu.num_counters) - 1,
0, x86_pmu.num_counters, 0, 0);
下面再来看下,sys_perf_event_open传递进来的参数是怎么体现事件的
283 struct perf_event_attr {
284
285 /*
286 * Major type: hardware/software/tracepoint/etc.
287 */
288 __u32 type;
289
290 /*
291 * Size of the attr structure, for fwd/bwd compat.
292 */
293 __u32 size;
294
295 /*
296 * Type specific configuration information.
297 */
298 __u64 config; 具体的事件类型, 比如硬件事件 instructions/bus-cycles事件等,这个事件怎么和真正的PMU对应起来捏?
299
300 union {
301 __u64 sample_period;
302 __u64 sample_freq;
303 };
304
305 __u64 sample_type;
306 __u64 read_format;
307
308 __u64 disabled : 1, /* off by default */
309 inherit : 1, /* children inherit it */
310 pinned : 1, /* must always be on PMU */
311 exclusive : 1, /* only group on PMU */
312 exclude_user : 1, /* don't count user */
313 exclude_kernel : 1, /* ditto kernel */
314 exclude_hv : 1, /* ditto hypervisor */
315 exclude_idle : 1, /* don't count when idle */
316 mmap : 1, /* include mmap data */
317 comm : 1, /* include comm data */
318 freq : 1, /* use freq, not period */
319 inherit_stat : 1, /* per task counts */
320 enable_on_exec : 1, /* next exec enables */
321 task : 1, /* trace fork/exit */
322 watermark : 1, /* wakeup_watermark */
323 /*
发生关联的地方在:
func(x86_setup_perfctr) hwConfig(0x130000) type(0) PERF_RAW(4)
0xffffffff81006170 : x86_setup_perfctr+0x0/0x170 [kernel]
0xffffffff81006396 : x86_pmu_hw_config+0xb6/0x1c0 [kernel]
0xffffffff8100b732 : intel_pmu_hw_config+0x12/0x130 [kernel]
0xffffffff8100b862 : hsw_hw_config+0x12/0xb0 [kernel]
0xffffffff81005e85 : x86_pmu_event_init+0xd5/0x1f0 [kernel]
0xffffffff8117b426 : perf_try_init_event+0x76/0x90 [kernel]
0xffffffff8117edb9 : perf_event_alloc+0x5a9/0x6d0 [kernel]
0xffffffff81181b88 : SYSC_perf_event_open+0x3c8/0xdb0 [kernel]
0xffffffff81184809 : sys_perf_event_open+0x9/0x10 [kernel]
0xffffffff81824626 : tracesys_phase2+0x88/0x8d [kernel]
在这个函数中有操作: event->hw.config |= event->attr.config & (HSW_IN_TX|HSW_IN_TX_CHECKPOINTED);
在生成perf_event结构体时,perf_event->attr会初始化的
perf_event_alloc函数中有如下语句会初始化:
9103 event->attr = *attr;
然后,看下hw.config是怎么写到的寄存器里面
intel_pmu_enable_event在这里会把具体的config写入到寄存器中去
event.attr.config: 0x6
event.hw.config: 0x13013c
0xffffffff8100be20 : intel_pmu_enable_event+0x0/0x220 [kernel]
0xffffffff81006b3e : x86_pmu_start+0x7e/0x100 [kernel]
0xffffffff8117f921 : perf_event_task_tick+0x2a1/0x2d0 [kernel]
0xffffffff810ad31b : scheduler_tick+0x7b/0xd0 [kernel]
event.hw.config中本来就是有值的,但是
13412e
1300c0
109301c2
https://blog.csdn.net/edonlii/article/details/8686130
这篇文章中有介绍了MSR寄存器每个位的意义:
IA32_PERFEVTSELx寄存器的bit位布局如下:
0-7:Event select field,事件选择字段 (所以perf用户态的代码里也有大量的&255这样的操作 )在哪里能够体现
8-15:Unit mask (UMASK) field,事件检测掩码字段
16:USR (user mode) flag,设置仅对用户模式(privilege levels 1, 2 or 3)进行计数,可以和OS flag一起使用。
17:OS (operating system mode) flag,设置仅对内核模式(privilege levels 0)进行计数,可以和USR flag一起使用。
18:E (edge detect) flag
19:PC (pin control) flag,如果设置为1,那么当性能监视事件发生时,逻辑处理器就会增加一个计数并且“toggles the PMi pins”;如果清零,那么当性能计数溢出时,处理器就会“toggles the PMi pins”。“toggles the PMi pins”不好翻译,其具体定义为:“The toggling of a pin is defined as assertion of the pin for a single bus clock followed by deassertion.”,对于此处,我的理解也就是把PMi针脚激活一下,从而触发一个PMI中断。
20:INT (APIC interrupt enable) flag,如果设置为1,当性能计数溢出时,就会通过local APIC来触发逻辑处理器产生一个异常。
21:保留
22:EN (Enable Counters) Flag,如果设置为1,性能计数器生效,否则被禁用。
23:INV (invert) flag,控制是否对Counter mask结果进行反转。
24-31:Counter mask (CMASK) field,如果该字段不为0,那么只有在单个时钟周期内发生的事件数大于等于该值时,对应的计数器才自增1。这就可以用于统计每个时钟周期内发生多次的事件。如果该字段为0,那么计数器就以每时钟周期按具体发生的事件数进行增长。
32-63:保留
比如,如果我要监控6号事件,那么我只能去填充
看下x86_setup_perfctr
config = x86_pmu.event_map(attr->config); // intel_pmu_event_map
在这里会对事件做一个映射的。。。。
原来perf attr。config都是intel_perfmon_event_map中的索引。。。。。
27 static u64 intel_perfmon_event_map[PERF_COUNT_HW_MAX] __read_mostly =
28 {
29 [PERF_COUNT_HW_CPU_CYCLES] = 0x003c,
30 [PERF_COUNT_HW_INSTRUCTIONS] = 0x00c0,
31 [PERF_COUNT_HW_CACHE_REFERENCES] = 0x4f2e,
32 [PERF_COUNT_HW_CACHE_MISSES] = 0x412e,
33 [PERF_COUNT_HW_BRANCH_INSTRUCTIONS] = 0x00c4,
34 [PERF_COUNT_HW_BRANCH_MISSES] = 0x00c5,
35 [PERF_COUNT_HW_BUS_CYCLES] = 0x013c,
36 [PERF_COUNT_HW_REF_CPU_CYCLES] = 0x0300, /* pseudo-encoding */
37 };
于是这么看,事件应该是占用16个bit了呀
这个是事件寄存
x86_pmu_hw_cofig --> x86_setup_perfctr --> event_map 设置事件的路径;
那么下个问题就是,我执行perf top -e cycles -e instructions 这个在内核里是几个事件?
使用systemtap的guru模式,想在中断处理函数x86_pmu_handle_irq中查看到底有多少个事件挂在这个CPU上
头文件
在函数intel_pmu_handle_irq中抓取事件,然后用perf去抓取所有的硬件事件,intel上有一个size是64的数组,数组的每一个槽能容纳一个事件,然后就等着事件发中断去接受事件,看代码是这样的. 所以向MSR寄存器去注册事件的时候,除了要注册具体的事件类型,还要注册这个事件的idx,要不然驱动怎么知道去找哪个perf_event呢?那这样就还有一个问题了,总共有64个槽,那么如果事件多于64个咋办?比如,我有128个进程都去申请instructions事件咋办?
写个程序测试一下:
如果进程总共的个数
问题来了,可否两个进程共享一个perf_event事件?
一个cgroup组的进程是贡献一个cgroup组的事件么?
64个,刚才用perf直接生成了84个,也是可以的,也就是说在同一个cpu上挂了84个事件也是可以的
是怎么完成的?
只有perf top才会触发intel_pmu_handle_irq中断的,我们通过perf_event_open打开根本就不能所以intel_pmu_handle_irq的中断还是在特定时候开启的,
所以现在的问题就是了
和抓取的时间就对上了
所以到这里也不难理解,所以这就是perf的采样的功能了
采样的功能,看下内核中到底是怎么处理的sample_freq, PMU寄存器的值
这是MSR寄存器的溢出时间,
采样的周期放在了sample_period变量中
在x86_perf_event_set_period函数中会不断设置