前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >核心调用-perf_event_open

核心调用-perf_event_open

作者头像
AshinZ
发布2023-11-01 17:01:01
3140
发布2023-11-01 17:01:01
举报

大家好,我是程栩,一个专注于性能的大厂程序员,分享包括但不限于计算机体系结构、性能优化、云原生的知识。

本文是perf系列的第六篇文章,后续会继续介绍perf,包括用法、原理和相关的经典文章。

今天我们会介绍perf内核源码中很重要的数据结构perf_event及其相关内容。今天阅读的代码主要位于include/linux/perf_event.h/kernel/events/core.c文件内。本次阅读基于v6.3-rc7版本。今天主要是大概介绍流程,还需要仔细阅读和代码才能了解整体的实现。

perf_event_open

在开始之前,我们先给出一个perf的调用原理图:

perf原理图:https://plantegg.github.io/2021/05/16/Perf_IPC%E4%BB%A5%E5%8F%8ACPU%E5%88%A9%E7%94%A8%E7%8E%87/

可以看到,我们在用户态中触发sys_perf_event_open系统调用,内核陷入中断以后会调用perf_event_open来处理,该函数位于kernel/events/core.c文件下。perf_event_open会负责初始化计数器相关,并去获取相关的数据。这些数据会被放到ring-buffer中等待用户态来读取。

从core.c L12305开始,就是该函数相关的内容了:

代码语言:javascript
复制
/**
 * sys_perf_event_open - open a performance event, associate it to a task/cpu
 *
 * @attr_uptr: event_id type attributes for monitoring/sampling
 * @pid:  target pid
 * @cpu:  target cpu
 * @group_fd:   group leader event fd
 * @flags:  perf event open flags
 */
SYSCALL_DEFINE5(perf_event_open,
  struct perf_event_attr __user *, attr_uptr,
  pid_t, pid, int, cpu, int, group_fd, unsigned long, flags)

这里的参数我们已经在上篇文章中聊过它们的作用了,这里就不回顾了。

首先我们会看到一些后面需要用到的变量:

代码语言:javascript
复制
 struct perf_event *group_leader = NULL, *output_event = NULL;
 struct perf_event_pmu_context *pmu_ctx;
 struct perf_event *event, *sibling;
 struct perf_event_attr attr;
 struct perf_event_context *ctx;
 struct file *event_file = NULL;
 struct fd group = {NULL, 0};
 struct task_struct *task = NULL;
 struct pmu *pmu;
 int event_fd;
 int move_group = 0;
 int err;
 int f_flags = O_RDWR;
 int cgroup_fd = -1;

由于perf_event_open是一个系统调用,我们传入给系统调用的参数是不会直接传递过来的。例如在xv6中,需要通过argint来获取传递的int参数。这里也是一样:

代码语言:javascript
复制
 err = perf_copy_attr(attr_uptr, &attr); // L12337
 if (err)
  return err;

这里的perf_copy_attr就是负责从用户态的参数拷贝到内核态。

那么作为一个系统调用,而且是非常关键、可能有追踪的系统调用,有必要进行一些权限检查:

代码语言:javascript
复制
/* Do we allow access to perf_event_open(2) ? */
// L12341
 err = security_perf_event_open(&attr, PERF_SECURITY_OPEN);
 if (err)
  return err;

 if (!attr.exclude_kernel) {
  err = perf_allow_kernel(&attr);
  if (err)
   return err;
 }

 if (attr.namespaces) {
  if (!perfmon_capable())
   return -EACCES;
 }

 if (attr.freq) {
  if (attr.sample_freq > sysctl_perf_event_sample_rate)
   return -EINVAL;
 } else {
  if (attr.sample_period & (1ULL << 63))
   return -EINVAL;
 }

 /* Only privileged users can get physical addresses */
 if ((attr.sample_type & PERF_SAMPLE_PHYS_ADDR)) {
  err = perf_allow_kernel(&attr);
  if (err)
   return err;
 }

这之后,我们就需要分配和初始化事件结构体:

代码语言:javascript
复制
// L 12423 
event = perf_event_alloc(&attr, cpu, task, group_leader, NULL,
     NULL, NULL, cgroup_fd);
 if (IS_ERR(event)) {
  err = PTR_ERR(event);
  goto err_task;
 }

 if (is_sampling_event(event)) {
  if (event->pmu->capabilities & PERF_PMU_CAP_NO_INTERRUPT) {
   err = -EOPNOTSUPP;
   goto err_alloc;
  }
 }

这里的eventperf_event类型的,具体定义在/include/linux/perf_event.h目录下,链接L665

代码语言:javascript
复制
/**
 * struct perf_event - performance event kernel representation:
 */
struct perf_event {
#ifdef CONFIG_PERF_EVENTS
 /*
  * entry onto perf_event_context::event_list;
  *   modifications require ctx->lock
  *   RCU safe iterations.
  */
 struct list_head  event_entry;

 /*
  * Locked for modification by both ctx->mutex and ctx->lock; holding
  * either sufficies for read.
  */
 struct list_head  sibling_list;
 struct list_head  active_list;
 /*
  * Node on the pinned or flexible tree located at the event context;
  */
 ...

接着,就会去获取目标上下文信息并保存到类型为perf_event_contextperf_event_pmu_context变量中:

代码语言:javascript
复制
 /*
  * Get the target context (task or percpu):
  */
 ctx = find_get_context(task, event); // L12468
 if (IS_ERR(ctx)) {
  err = PTR_ERR(ctx);
  goto err_cred;
 }

 pmu_ctx = find_get_pmu_context(pmu, ctx, event); // L 12568
 if (IS_ERR(pmu_ctx)) {
  err = PTR_ERR(pmu_ctx);
  goto err_locked;
 }

之后,通过anon_inode_getfile创建文件,该文件会通过最后的fd_install和进程相关联起来:

代码语言:javascript
复制
 // L 12602
    event_file = anon_inode_getfile("[perf_event]", &perf_fops, event, f_flags);
 if (IS_ERR(event_file)) {
  err = PTR_ERR(event_file);
  event_file = NULL;
  goto err_context;
 }

 //L 12676
 /*
  * Drop the reference on the group_event after placing the
  * new event on the sibling_list. This ensures destruction
  * of the group leader will find the pointer to itself in
  * perf_group_detach().
  */
 fdput(group);
 fd_install(event_fd, event_file);
 return event_fd;

最后,函数会通过perf_install_in_context将上下文和性能事件进行绑定,并调用相关函数访问性能计数器:

代码语言:javascript
复制
  // L 12651
     /*
  * Precalculate sample_data sizes; do while holding ctx::mutex such
  * that we're serialized against further additions and before
  * perf_install_in_context() which is the point the event is active and
  * can use these values.
  */
 perf_event__header_size(event);
 perf_event__id_header_size(event);

 event->owner = current;

 perf_install_in_context(ctx, event, event->cpu);
 perf_unpin_context(ctx);

 mutex_unlock(&ctx->mutex);

 if (task) {
  up_read(&task->signal->exec_update_lock);
  put_task_struct(task);
 }

 mutex_lock(&current->perf_event_mutex);
 list_add_tail(&event->owner_entry, &current->perf_event_list);
 mutex_unlock(&current->perf_event_mutex);

小结

与其说今天是阅读源码,倒不如说是略过了一遍源码,大概了解了一些内容,产生的不理解的更多,还需要仔细钻研。考虑到一些原因,后续可能会多个系列穿插更新,目前已确定的是会有一个讲CPU架构的系列文章,主要以risc-v为主,也会有x86arm的相关内容。

今天的小结:

小结

参考资料

  • perf源码解析(https://liujunming.top/2018/05/10/perf%E5%86%85%E6%A0%B8%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/)
  • perf_event.h(https://github.com/torvalds/linux/blob/v6.3-rc7/include/linux/perf_event.h)
  • Perf_IPC以及CPU利用率(https://plantegg.github.io/2021/05/16/Perf_IPC%E4%BB%A5%E5%8F%8ACPU%E5%88%A9%E7%94%A8%E7%8E%87/)
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-04-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程栩的性能优化笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • perf_event_open
  • 小结
  • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档