核心调用-perf_event_open

821 阅读3分钟

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

本文是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开始,就是该函数相关的内容了:

/**
 * 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)

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

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

    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参数。这里也是一样:

    err = perf_copy_attr(attr_uptr, &attr); // L12337
    if (err)
        return err;

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

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

/* 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;
    }

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

// 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

/**
 * 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变量中:

    /*
     * 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和进程相关联起来:

    // 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将上下文和性能事件进行绑定,并调用相关函数访问性能计数器:

	 // 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的相关内容。

今天的小结:

小结

参考资料