linux性能工具perf工作原理简析( 三 )


struct perf_event_context { struct pmu *pmu; struct list_head event_list; struct task_struct *task; ...}c. 把event与一个context进行关联,见perf_install_in_context();
d. 最后,把fd和perf_fops进行绑定:
static const struct file_operations perf_fops = { .llseek = no_llseek, .release = perf_release, .read = perf_read, .poll = perf_poll, .unlocked_ioctl = perf_ioctl, .compat_ioctl = perf_compat_ioctl, .mmap = perf_mmap, .fasync = perf_fasync,};perf系统调用大致的调用链如下:
sys_perf_event_open() get_unused_fd_flags()perf_event_alloc()find_get_context()alloc_perf_context()anon_inode_getfile()perf_install_in_context()add_event_to_ctx()fd_install(event_fd, event_file)内核态工作流
perf event有两种方式:计数(counting)和采样(sampled) 。计数方式会对发生在所有指定cpu和指定进程的事件次数进行求和,对事件数值通过read()获得 。而采样方式会周期性地把计数结果放在由mmap()创建的ring buffer中 。回到开始的简单perf-stat示例,用的是计数(counting)方式 。
接下来,我们主要了解这几个问题:

  1. 怎么enable和disable计数器?
  2. 进行计数的时机在哪里?
  3. 如何读取计数结果?
回答这些问题的入口,基本都在perf实现的文件操作集中:
static const struct file_operations perf_fops = { .read = perf_read, .unlocked_ioctl = perf_ioctl,...首先,我们看一下怎样enable计数器的,主要步骤如下:
perf_ioctl() __perf_event_enable()ctx_sched_out() IF ctx->is_activectx_resched()perf_pmu_disable()task_ctx_sched_out()cpu_ctx_sched_out()perf_event_sched_in()event_sched_in()event->pmu->add(event, PERF_EF_START)perf_pmu_enable()pmu->pmu_enable(pmu)这个过程有很多调度相关的处理,使整个逻辑显得复杂,我们暂且不关心太多调度细节 。硬件的PMU资源是有限的,当event数量多于可用的PMC时,多个virtual counter就会复用硬件PMC 。因此, PMU先把event添加到激活列表(pmu->add(event, PERF_EF_START)), 最后enable计数(pmu->pmu_enable(pmu) ) 。PMU是CPU体系结构相关的,可以想象它有一套为event分配具体硬件PMC的逻辑,我们暂不深究 。
我们继续了解如何获取计数器结果,大致的callchain如下:
perf_read() perf_read_one()perf_event_read_value()__perf_event_read()pmu->start_txn(pmu, PERF_PMU_TXN_READ)pmu->read(event)pmu->commit_txn(pmu)PMU最终会通过rdpmcl(counter, val)获得计数器的值,保存在perf_event::count中 。关于PMU各种操作说明,可以参考include/linux/perf_event.h:struct pmu{} 。PMU操作的实现是体系结构相关的,x86上的read()的实现是arch/x86/events/core.c:x86_pmu_read() 。
event可以设置限定条件,仅当指定的进程运行在指定的cpu上时,才能进行计数,这就是上面提到的计数时机问题 。很容易想到,这样的时机发生在进程切换的时候 。当目标进程切换出目标CPU时,PMU停止计数,并将硬件寄保存在内存变量中,反之亦然,这个过程类似进程切换时对硬件上下文的保护 。在kernel/sched/core.c, 我们能看到这些计数时机 。
在进程切换前:
prepare_task_switch() perf_event_task_sched_out()__perf_event_task_sched_out() // stop each event and update the event value in event->countperf_pmu_sched_task()pmu->sched_task(cpuctx->task_ctx, sched_in)进程切换后:
finish_task_switch() perf_event_task_sched_in()perf_event_context_sched_in()perf_event_sched_in()小结
通过对perf-list和perf-stat这两个基本的perf命令进行分析,引出了一些有意思的问题,在尝试回答这些问题的过程中,基本上总结了目前我对perf这个工具的认识 。但是,本文仅对perf的工作原理做了很粗略的梳理,也没有展开对PMU层,perf uncore等硬件相关代码进行分析,希望以后能补上这部分内容 。
最后,能坚持看到最后的亲们都是希望更深了解性能测试的,作为福利给大家推荐本书:《system performance: enterprise and the cloud》(https://pan.baidu.com/s/1yyPsJxi0XWSwIKOrAWm-Vg?errno=0&errmsg=Auth%20Login%20Sucess&&bduss=&ssnerror=0&traceid=) 书的作者是一位从事多年性能优化工作的一线工程师,想必大家都听说过他写的火焰图程序: perf Examples【http://www.brendangregg.com/perf.html】
Cheers!
参考索引
  1. Cycles per instruction: https://en.wikipedia.org/wiki/Cycles_per_instruction
  2. uncore: https://en.wikipedia.org/wiki/Uncore
  3. 《Intel® Xeon® Processor E5 and E7 v4 Product Families Uncore Performance Monitoring Reference Manual》
  4. 《Linux设备驱动程序》中第二章PCI驱动程序
  5. https://patchwork.kernel.org/patch/10412883/
  6. linux/tools/perf/design.txt




推荐阅读