核心调用-perf_event_open

2023-11-01 17:01:01 浏览数 (1)

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

本文是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以及CPU利用率/

可以看到,我们在用户态中触发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内核源码解析/)
  • 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以及CPU利用率/)

0 人点赞