大家好,我是程栩,一个专注于性能的大厂程序员,分享包括但不限于计算机体系结构、性能优化、云原生的知识。
本文是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
参数。这里也是一样:
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;
}
}
这里的event
是perf_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_context
、perf_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(¤t->perf_event_mutex);
list_add_tail(&event->owner_entry, ¤t->perf_event_list);
mutex_unlock(¤t->perf_event_mutex);
小结
与其说今天是阅读源码,倒不如说是略过了一遍源码,大概了解了一些内容,产生的不理解的更多,还需要仔细钻研。考虑到一些原因,后续可能会多个系列穿插更新,目前已确定的是会有一个讲CPU架构的系列文章,主要以risc-v
为主,也会有x86
和arm
的相关内容。
今天的小结:
小结
参考资料
- 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利用率/)