当前位置:首页>Linux>Linux程序性能分析(一)---perf原理分析

Linux程序性能分析(一)---perf原理分析

  • 2026-08-20 13:07:33
Linux程序性能分析(一)---perf原理分析

PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

环境说明

  无

前言


  前段时间做了很多的基于sdk/demo的大模型程序性能评估,例如一个大模型运行的瓶颈是什么?这里的评估要分为两个部分:

  • • 异构设备运行性能评估:异构设备的执行时间,例如高通的NSP设备的运行一个模型的性能评估。
  • • cpu运行性能评估:sdk/demo执行一次模型推理的性能评估。

  对于异构设备来说(例如高通dsp,运行高通自己的rtos),有他专门的profile工具。对于cpu设备来说(Linux系统),有一个常用的性能评估工具,就是perf。

  对于perf来说,我相信只要是做开发的,多多少少都听过火焰图这个概念,而构建火焰图的一个重要前提是,我们需要通过perf来对目标进程进行cpu cycle采样,然后基于这个采样获取ip/tid/cpu核/callchain等信息。perf除了支持cpu cycle采样外,还支持其他的各种各样的性能参数采样。

  以前,用perf来做性能分析,只知道能获取这些信息,但是怎么获取的,一知半解。因此,趁着这个机会,基于linux-7.1.6的内核源码,以采集cpu cycle为例,本文尝试从用户态、内核态两个角度来分析perf实现的基本原理。





perf 用户态实现原理


  这里我在AI的辅助下,实现了一个和perf工具非常相似的例子,可以传入pid,对目标进程进行采样,获取ip/tid/cpu核/callchain等信息。

  下面是一些代码片段分析:
perf_event.c

#include"perf_event.h"

#include<linux/perf_event.h>
#include<sys/ioctl.h>
#include<sys/syscall.h>
#include<unistd.h>
#include<errno.h>

/* ------------------------------------------------------------------ */
/*  low-level syscall wrapper                                         */
/* ------------------------------------------------------------------ */
staticlong
_perf_event_open(conststruct perf_event_attr *attr,
pid_t                         pid,
int                           cpu,
int                           group_fd,
unsignedlong                 flags)
{
return syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags);
}

/* ------------------------------------------------------------------ */
/*  public API                                                        */
/* ------------------------------------------------------------------ */

int
perf_open(conststruct perf_event_attr *attr,
pid_t                         pid,
int                           cpu,
int                           group_fd,
unsignedlong                 flags)

{
if (attr == NULL) {
        errno = EINVAL;
return-1;
    }
return (int) _perf_event_open(attr, pid, cpu, group_fd, flags);
}

void
perf_close(int fd)
{
if (fd >= 0) {
        close(fd);
    }
}

int
perf_start(int fd)
{
if (fd < 0) {
        errno = EINVAL;
return-1;
    }
if (ioctl(fd, PERF_EVENT_IOC_RESET, 0) < 0) {
return-1;
    }
if (ioctl(fd, PERF_EVENT_IOC_ENABLE, 0) < 0) {
return-1;
    }
return0;
}

int
perf_stop(int fd, uint64_t *count)
{
if (fd < 0) {
        errno = EINVAL;
return-1;
    }
if (ioctl(fd, PERF_EVENT_IOC_DISABLE, 0) < 0) {
return-1;
    }

uint64_t val = 0;
ssize_t  n   = read(fd, &val, sizeof(val));
if (n != sizeof(val)) {
return-1;
    }
if (count != NULL) {
        *count = val;
    }
return0;
}

perf_event.h

#ifndef PERF_EVENT_H
#define PERF_EVENT_H

#include<linux/perf_event.h>
#include<stdint.h>
#include<unistd.h>

#ifdef __cplusplus
extern"C" {
#endif

/*
 * perf_open  –  open a perf event counter.
 *
 * Direct wrapper for the perf_event_open(2) syscall — all parameters are
 * passed through unchanged.
 *
 *   attr     : fully initialised perf_event_attr.
 *   pid      : pid to monitor (0 = calling thread, -1 = all threads).
 *   cpu      : cpu to monitor (-1 = any cpu).
 *   group_fd : group leader fd (-1 to create a new group).
 *   flags    : PERF_FLAG_FD_NO_GROUP, PERF_FLAG_FD_OUTPUT, etc. (0 for none).
 *
 *   Returns fd >= 0 on success, -1 on failure (errno is set).
 */

intperf_open(conststruct perf_event_attr *attr,
pid_t                         pid,
int                           cpu,
int                           group_fd,
unsignedlong                 flags)
;

/*
 * perf_close –  close a perf event fd.  No-op when fd < 0.
 */

voidperf_close(int fd);

/*
 * perf_start –  reset counter to zero and enable.  Returns 0 / -1.
 */

intperf_start(int fd);

/*
 * perf_stop  –  disable counter and read accumulated value into *count.
 *               Returns 0 / -1.
 */

intperf_stop(int fd, uint64_t *count);

#ifdef __cplusplus
}
#endif

#endif/* PERF_EVENT_H */

perf_ringbuf.c

#include"perf_ringbuf.h"

#include<linux/perf_event.h>
#include<sys/mman.h>
#include<unistd.h>
#include<stdint.h>
#include<stddef.h>

int
perf_ringbuf_open(int fd,
int nr_pages,
void **base,
struct perf_event_mmap_page **meta,
size_t *data_size)

{
if (base == NULL || meta == NULL || data_size == NULL || nr_pages < 1) {
return-1;
    }

long   page_size = sysconf(_SC_PAGESIZE);
size_t mmap_size = (1 + (size_t) nr_pages) * page_size;

void *addr = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE,
                      MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
return-1;
    }

    *base      = addr;
    *meta      = (struct perf_event_mmap_page *) addr;
    *data_size = (size_t) nr_pages * page_size;
return0;
}

void
perf_ringbuf_head_tail(conststruct perf_event_mmap_page *meta,
uint64_t *head,
uint64_t *tail)

{
uint64_t h = meta->data_head;
    __sync_synchronize();
uint64_t t = meta->data_tail;
if (head != NULL) {
        *head = h;
    }
if (tail != NULL) {
        *tail = t;
    }
}

void
perf_ringbuf_close(void *base, size_t data_size)
{
if (base == NULL) {
return;
    }
long page_size  = sysconf(_SC_PAGESIZE);
long mmap_size  = page_size + (long) data_size;
    munmap(base, mmap_size);
}

perf_ringbuf.h

#ifndef PERF_RINGBUF_H
#define PERF_RINGBUF_H

#include<linux/perf_event.h>
#include<stddef.h>
#include<stdint.h>

#ifdef __cplusplus
extern"C" {
#endif

/*
 * perf_ringbuf_open – mmap the perf event ring buffer.
 *
 *   fd        : perf event file descriptor.
 *   nr_pages  : number of data pages (must be power-of-2, usually 8).
 *   base      : [out] mmap base address (metadata page).
 *   meta      : [out] pointer to struct perf_event_mmap_page.
 *   data_size : [out] size of data area in bytes (= nr_pages * page_size).
 *
 *   Returns 0 on success, -1 on failure.
 */

intperf_ringbuf_open(int fd,
int nr_pages,
void **base,
struct perf_event_mmap_page **meta,
size_t *data_size)
;

/*
 * perf_ringbuf_head_tail – read head and tail with correct memory barrier.
 *   Caller must pass the meta pointer from perf_ringbuf_open.
 *   head is written by the kernel, tail is written by userspace.
 */

voidperf_ringbuf_head_tail(conststruct perf_event_mmap_page *meta,
uint64_t *head,
uint64_t *tail)
;

/*
 * perf_ringbuf_close – munmap the ring buffer.
 *
 *   base      : the base address returned by perf_ringbuf_open.
 *   data_size : the data_size value returned by perf_ringbuf_open.
 */

voidperf_ringbuf_close(void *base, size_t data_size);

#ifdef __cplusplus
}
#endif

#endif/* PERF_RINGBUF_H */

demo_sample.c

#define _GNU_SOURCE

#include"perf_event.h"
#include"perf_ringbuf.h"

#include<linux/perf_event.h>
#include<sys/mman.h>
#include<sys/ioctl.h>

#include<unistd.h>
#include<stdint.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<dlfcn.h>
#include<poll.h>
#include<time.h>

#define SAMPLE_COUNT   10
#define MAX_MAPS       512

/* ── memory map entry ─────────────────────────────────────────────── */
typedefstruct {
uint64_t start;
uint64_t end;
char     path[256];
mem_map_t;

staticmem_map_t maps[MAX_MAPS];
staticint       nr_maps;

/* ── helpers ──────────────────────────────────────────────────────── */
/* read value at ptr, then advance ptr by sizeof(raw_type) */
#define NEXT_FIELD(ptr, raw_type) \
    (*(raw_type *)(ptr)); (ptr) = (void *)((char *)(ptr) + sizeof(raw_type))


/* ── parse /proc/<pid>/maps ───────────────────────────────────────── */
staticint
parse_maps(pid_t pid)
{
char path[64];
snprintf(path, sizeof(path), "/proc/%d/maps", pid);
    FILE *f = fopen(path, "r");
if (f == NULL) {
        perror("fopen /proc/.../maps");
return-1;
    }

    nr_maps = 0;
char line[512];
while (fgets(line, sizeof(line), f) != NULL && nr_maps < MAX_MAPS) {
uint64_t start, end;
char perms[8], fpath[256] = {0};

/* format: start-end perms offset dev inode path */
int n = sscanf(line, "%lx-%lx %7s %*s %*s %*u %255s",
                       &start, &end, perms, fpath);

if (n < 2continue;   /* skip malformed lines */

        maps[nr_maps].start = start;
        maps[nr_maps].end   = end;

if (n >= 4 && fpath[0] != '\0') {
/* keep only the basename */
constchar *name = strrchr(fpath, '/');
snprintf(maps[nr_maps].path, sizeof(maps[nr_maps].path),
"%s", name ? name + 1 : fpath);
        } else {
/* anonymous mapping – try to identify by permissions */
if (strchr(perms, 'x')) {
snprintf(maps[nr_maps].path, sizeof(maps[nr_maps].path),
"[anon_exec:%lx]", start);
            } else {
snprintf(maps[nr_maps].path, sizeof(maps[nr_maps].path),
"[anon]");
            }
        }
        nr_maps++;
    }
    fclose(f);
return0;
}

/* ── look up which mapping an address falls into ──────────────────── */
staticconstmem_map_t *
find_map(uint64_t addr)
{
for (int i = 0; i < nr_maps; i++) {
if (addr >= maps[i].start && addr < maps[i].end) {
return &maps[i];
        }
    }
returnNULL;
}

/* ── resolve symbol name ──────────────────────────────────────────── */
/*
 * When pid == our own process  →  use dladdr(3) to get function names.
 * When pid != self              →  fall back to /proc/<pid>/maps and
 *                                    print <binary>+<offset>.
 */

staticvoid
resolve_symbol(pid_t my_pid, pid_t target_pid, uint64_t addr,
char *buf, size_t bufsz)

{
/* dladdr only works for addresses in our own address space */
if (target_pid == my_pid || target_pid == 0) {
        Dl_info info;
if (dladdr((void *) (uintptr_t) addr, &info) != 0) {
constchar *name = info.dli_sname ? info.dli_sname : "???";
uint64_t   off   = addr - (uint64_t) (uintptr_t) info.dli_saddr;
constchar *file = info.dli_fname ? info.dli_fname : "???";
constchar *base = strrchr(file, '/');
snprintf(buf, bufsz, "%s+0x%lx (%s)",
                     name, off, base ? base + 1 : file);
return;
        }
/* dladdr failed – fall through to maps-based lookup */
    }

/* kernel-space address */
if (addr >= 0xffff000000000000ULL) {
snprintf(buf, bufsz, "[kernel]");
return;
    }

/* maps-based fallback for cross-process or dladdr failure */
constmem_map_t *m = find_map(addr);
if (m != NULL) {
uint64_t off = addr - m->start;
snprintf(buf, bufsz, "%s+0x%lx", m->path, off);
    } else {
snprintf(buf, bufsz, "[nomap:0x%lx]", addr);
    }
}

/* ── usage ────────────────────────────────────────────────────────── */
staticvoid
usage(constchar *prog)
{
fprintf(stderr"Usage: %s [pid]\n", prog);
fprintf(stderr"  pid  – monitor <pid> (default: 0 = calling thread)\n");
}

/* =================================================================== */
int
main(int argc, char *argv[])
{
pid_t target_pid = 0;    /* default: own thread */
pid_t my_pid     = getpid();

if (argc > 1) {
if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
            usage(argv[0]);
return0;
        }
        target_pid = (pid_t) atoi(argv[1]);
    }

/* ── parse /proc/<pid>/maps for symbol resolution ── */
pid_t maps_pid = (target_pid == 0) ? my_pid : target_pid;
if (parse_maps(maps_pid) != 0) {
return-1;
    }
printf("loaded %d memory mappings for pid %d\n", nr_maps, maps_pid);

/* ── perf event attr ──────────────────────────────────────────── */
structperf_event_attrattr;
memset(&attr, 0sizeof(attr));

    attr.size           = sizeof(attr);
    attr.type           = PERF_TYPE_HARDWARE;
    attr.config         = PERF_COUNT_HW_CPU_CYCLES;
    attr.sample_period  = 10000;
    attr.disabled       = 1;
    attr.exclude_kernel = 0;
    attr.exclude_hv     = 1;
    attr.wakeup_events  = 1;
/* Note: inherit=1 would cover forked children but prevents mmap on
     *       older ARM kernels.  Use pid=<tgid> to cover all threads. */


    attr.sample_type =
        PERF_SAMPLE_IP |
        PERF_SAMPLE_TID |
        PERF_SAMPLE_TIME |
        PERF_SAMPLE_CPU |
        PERF_SAMPLE_CALLCHAIN;

    attr.sample_max_stack = 20;

/* ── 1. perf_open ─────────────────────────────────────────────── */
int fd = perf_open(&attr, target_pid, -1-10);
if (fd < 0) {
        perror("perf_open");
return-1;
    }
printf("perf fd=%d  target_pid=%d\n", fd, target_pid);

/* ── 2. perf_ringbuf_open ─────────────────────────────────────── */
void                          *base;
structperf_event_mmap_page   *meta;
size_t                         data_size;

if (perf_ringbuf_open(fd, 8, &base, &meta, &data_size) != 0) {
        perror("perf_ringbuf_open");
        perf_close(fd);
return-1;
    }

long page_size = sysconf(_SC_PAGESIZE);
char *data = (char *) base + page_size;

printf("page_size=%ld  data_size=%zu\n", page_size, data_size);
printf("start sampling (%d samples)...\n", SAMPLE_COUNT);

/* ── 3. perf_start ────────────────────────────────────────────── */
if (perf_start(fd) != 0) {
        perror("perf_start");
        perf_ringbuf_close(base, data_size);
        perf_close(fd);
return-1;
    }

/* ── produce workload and collect N samples ───────────────────── */
int sample_count = 0;
int is_self = (target_pid == 0 || target_pid == my_pid);

structpollfdpfd = { .fd = fd, .events = POLLIN };

/*
     * Self-monitoring: we must burn CPU ourselves to generate PMU events.
     *   → poll with short timeout, burn CPU in between.
     *
     * Cross-process: the target generates events; we just wait passively.
     *   → poll blocks until data arrives or total timeout expires.
     */

int   poll_timeout = is_self ? 10 : 5000;   /* ms */
structtimespect_startt_now;
    clock_gettime(CLOCK_MONOTONIC, &t_start);

volatileunsignedlong x = 0;

while (sample_count < SAMPLE_COUNT) {
if (is_self) {
/* burn CPU while waiting for poll — generates our own samples */
for (int i = 0; i < 200000; i++) {
                x += i;
            }
        }

int ret = poll(&pfd, 1, poll_timeout);
if (ret < 0) {
            perror("poll");
break;
        }

if (ret == 0) {
/* timeout: check total elapsed (safety against infinite hang) */
            clock_gettime(CLOCK_MONOTONIC, &t_now);
double elapsed = (t_now.tv_sec  - t_start.tv_sec) +
                             (t_now.tv_nsec - t_start.tv_nsec) / 1e9;
if (elapsed >= 30.0) {
printf("\ntimeout after %.0fs, collected %d samples\n",
                       elapsed, sample_count);
break;
            }
continue;
        }

/* fd is readable — drain the ring buffer */
uint64_t head, tail;
        perf_ringbuf_head_tail(meta, &head, &tail);

while (tail < head) {
size_t  offset = tail & (data_size - 1);
structperf_event_header *header =
                (struct perf_event_header *) (data + offset);

if (header->type == PERF_RECORD_SAMPLE) {
                sample_count++;

void *p = (char *) header +
sizeof(struct perf_event_header);

/* kernel output order (fixed, not sorted by bit):
                 *   IP, TID, TIME, ID, CPU, CALLCHAIN(no nr, fills rest)
                 */

uint64_t ip   = NEXT_FIELD(p, uint64_t);
uint32_t pid  = NEXT_FIELD(p, uint32_t);
uint32_t tid  = NEXT_FIELD(p, uint32_t);
uint64_t time = NEXT_FIELD(p, uint64_t);
uint64_t id   = NEXT_FIELD(p, uint64_t);
uint32_t cpu  = NEXT_FIELD(p, uint32_t);
          (void) NEXT_FIELD(p, uint32_t);  /* reserved */

char   *record_end = (char *) header + header->size;
uint64_t nr = ((uint64_t)(record_end - (char *) p))
                              / sizeof(uint64_t);
uint64_t *ips = (uint64_t *) p;

char sym[256];

printf("\n--- sample %d/%d ---\n", sample_count, SAMPLE_COUNT);
                resolve_symbol(my_pid, target_pid, ip, sym, sizeof(sym));
printf("IP       = 0x%lx  %s\n", ip, sym);
printf("PID      = %u\n",       pid);
printf("TID      = %u\n",       tid);
printf("TIME     = %lu\n",      time);
printf("ID       = %lu\n",      id);
printf("CPU      = %u\n",       cpu);
printf("CALLCHAIN (%lu frames):\n", nr);
for (uint64_t i = 0; i < nr; i++) {
                    resolve_symbol(my_pid, target_pid, ips[i], sym, sizeof(sym));
printf("  [%lu] 0x%lx  %s\n", i, ips[i], sym);
                }

if (sample_count >= SAMPLE_COUNT) {
                    tail += header->size;
                    meta->data_tail = tail;
goto done;
                }
            }

            tail += header->size;
            meta->data_tail = tail;
        }
    }
done:

/* ── 4. perf_stop ─────────────────────────────────────────────── */
uint64_t count;
    perf_stop(fd, &count);
printf("\nfinal count = %lu\n", count);

/* ── 5,6. cleanup ─────────────────────────────────────────────── */
    perf_ringbuf_close(base, data_size);
    perf_close(fd);

return0;
}

上面的代码有几个重要的关注点:

  1. 1. 首先,perf的核心就是调用syscall __NR_perf_event_open(perf_event.c),调用的时候,传入对应的想采样的数据类型、采样模式等参数即可,然后得到了一个fd。
  2. 2. 在我们开始采样之前,我们还需要通过上面的fd+mmap申请一片空间来作为ring buf,方便perf驱动写入采样数据到我们申请的mmap空间
  3. 3. 开始采样,然后等待ringbuf有数据,触发我们来读取perf+pmu驱动写入的采样数据。
  4. 4. 我们获取的数据有ip/tid/cpu核/callchain等,然后callchain就是调用堆栈的地址信息,通过/proc/target_pid/maps,我们可以解析出这些callchain地址属于哪个地址模块,以及这个地址模块的偏移
  5. 5. 上面的地址模块+地址偏移,根据符号表,可以解析出是哪个函数,这样就得到了调用堆栈(这部分不了解不重要,这部分不是本文重点)




perf 内核态实现原理


   一个常见的linux的驱动,基本都是在vfs上的基础上暴露相关的接口给用户使用。下面我们通过从open/mmap/ioctl等常见的接口来分析perf驱动。



__NR_perf_event_open 实现基本原理

  perf驱动稍微特殊一点,其不是通过vfs暴露设备节点到用户态,直接调用open来获取资源,他是直接提供了一个__NR_perf_event_open系统调用。下面简单介绍一下这个syscall的实现:

  代码是linux-7.1.6/kernel/events/core.c

staticconststructfile_operationsperf_fops = {
    .release        = perf_release,
    .read            = perf_read,
    .poll            = perf_poll,
    .unlocked_ioctl        = perf_ioctl,
    .compat_ioctl        = perf_compat_ioctl,
    .mmap            = perf_mmap,
    .fasync            = perf_fasync,
};


/**
 * 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, unsignedlong, flags)
{
structperf_event *group_leader =NULL, *output_event = NULL;
structperf_event_pmu_context *pmu_ctx;
structperf_event *event, *sibling;
structperf_event_attrattr;
structperf_event_context *ctx;
structfile *event_file =NULL;
structtask_struct *task =NULL;
structpmu *pmu;
int event_fd;
int move_group = 0;
int err;
int f_flags = O_RDWR;
int cgroup_fd = -1;

// ... ...
// 这个地方先在当前进程申请一个没有使用的fd,后面将perf的数据挂在fd上面
    event_fd = get_unused_fd_flags(f_flags);
if (event_fd < 0)
return event_fd;

// ... ...
// 这个地方根据pid得到重要的进程结构体:task
if (pid != -1 && !(flags & PERF_FLAG_PID_CGROUP)) {
        task = find_lively_task_by_vpid(pid);
if (IS_ERR(task)) {
            err = PTR_ERR(task);
goto err_fd;
        }
    }

// ... ...


/*
        这个地方是核心,后续的所有操作都是访问的这个核心结构体struct perf_event,主要做的事情是:
        1 通过    struct perf_event *event __free(__free_event) = kmem_cache_alloc_node(perf_event_cache, GFP_KERNEL | __GFP_ZERO, node);  申请内存
        2 初始化一些结构体, 设置我们传递的cpu/attr等给event保存
        3 将目标进程的task结构体保存到event->hw.target
        4 设置事件溢出时的回调:event->overflow_handler = perf_event_output_forward;(这个就是真正的写数据到ring buf的地方,后面还会提到)
        5 通过pmu = perf_init_event(event);-> pmu = idr_find(&pmu_idr, PERF_TYPE_RAW) 查询到对应的pmu驱动,并挂在perf_event里面。

        第5点非常重要,这里的pmu_idr是一个全局的注册表,然后可以遍历查询到对应的pmu驱动

        pmu_idr 是通过perf_pmu_register来注册的pmu驱动的,本文将以armv8 pmu驱动为例来说明
    */


    event = perf_event_alloc(&attr, cpu, task, group_leader, NULL,
NULLNULL, cgroup_fd);

// ... ...

// 这个地方:1 申请一个匿名的文件节点event_file 2 给这个event_file填写文件操作ops 3 给这个event_file填写文件private_data为event
//              这里的作用就是,等会儿后面我们返回的fd作为句柄,然后可以通过fd来访问perf_fops里面的方法,以及前面我们构造好的event
    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;
    }

// ... ...

// 将perf event 挂到目标进程
    perf_install_in_context(ctx, event, event->cpu);

// ... ...

/*
     * File reference in group guarantees that group_leader has been
     * kept alive until we place the new event on the sibling_list.
     * This ensures destruction of the group leader will find
     * the pointer to itself in perf_group_detach().
     */

/* 注意这个地方: 1 首先通过全局变量current获取进程对应的文件current->files。 
                    2 通过files_struct 获取fdtable(current->files->fdt)   
                    3 将event_file挂在到当前进程的fdt表中(类似:current->files->fdt[event_fd] = event_file)

        这样,当这个fd返回给用户态的程序后,我们的程序可以通过fd来调用event_file里面的perf_fops里面定义的接口。
    */

    fd_install(event_fd, event_file);
return event_fd;

// ... ...
}

  总的来说,这个syscall返回了一个fd,这个fd绑定了一个struct file 对象,这个对象的private数据域就是我们初始化的perf_event *event。这个event中包含了:与pmu驱动的关联,perf采用属性,目标进程的重要结构struct task_struct *task,perf的fops(ioctl/mmap等)等。

  其实这就是一个普通的驱动申请fd绑定一个私有数据、和fops的常见操作,这里绑定的就是perf_event,细节的内容见上面代码中的注释。



perf_mmap 实现基本原理

  在上面的demo中,我们可以知道,当perf_open完成后,我们先在需要通过mmap来申请ringbuf空间,然后我们就直接调用mmap指定空间,经过文件系统调度后,也就是上文我们的event_file中的fops中的perf_mmap.

   我们先看一下其实现的源码:
linux-7.1.6/kernel/events/core.c

staticintperf_mmap(struct file *file, struct vm_area_struct *vma)
{
structperf_event *event = file->private_data;
unsignedlong vma_size, nr_pages;
    mapped_f mapped;
int ret;

/*
     * Don't allow mmap() of inherited per-task counters. This would
     * create a performance issue due to all children writing to the
     * same rb.
     */

if (event->cpu == -1 && event->attr.inherit)
return -EINVAL;

if (!(vma->vm_flags & VM_SHARED))
return -EINVAL;

    ret = security_perf_event_read(event);
if (ret)
return ret;

    vma_size = vma->vm_end - vma->vm_start;
    nr_pages = vma_size / PAGE_SIZE;

if (nr_pages > INT_MAX)
return -ENOMEM;

if (vma_size != PAGE_SIZE * nr_pages)
return -EINVAL;

    scoped_guard (mutex, &event->mmap_mutex) {
/*
         * This relies on __pmu_detach_event() taking mmap_mutex after marking
         * the event REVOKED. Either we observe the state, or __pmu_detach_event()
         * will detach the rb created here.
         */

if (event->state <= PERF_EVENT_STATE_REVOKED)
return -ENODEV;

if (vma->vm_pgoff == 0)
            ret = perf_mmap_rb(vma, event, nr_pages);
else
            ret = perf_mmap_aux(vma, event, nr_pages);
if (ret)
return ret;

/*
         * Since pinned accounting is per vm we cannot allow fork() to copy our
         * vma.
         */

        vm_flags_set(vma, VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP);
        vma->vm_ops = &perf_mmap_vmops;

        mapped = get_mapped(event, event_mapped);
if (mapped)
            mapped(event, vma->vm_mm);

/*
         * Try to map it into the page table. On fail undo the above,
         * as the callsite expects full cleanup in this case and
         * therefore does not invoke vmops::close().
         */

        ret = map_range(event->rb, vma);
if (likely(!ret))
return0;

/* Error path */

/*
         * If this is the first mmap(), then event->mmap_count should
         * be stable at 1. It is only modified by:
         * perf_mmap_{open,close}() and perf_mmap().
         *
         * The former are not possible because this mmap() hasn't been
         * successful yet, and the latter is serialized by
         * event->mmap_mutex which we still hold (note that mmap_lock
         * is not strictly sufficient here, because the event fd can
         * be passed to another process through trivial means like
         * fork(), leading to concurrent mmap() from different mm).
         *
         * Make sure to remove event->rb before releasing
         * event->mmap_mutex, such that any concurrent mmap() will not
         * attempt use this failed buffer.
         */

if (refcount_read(&event->mmap_count) == 1) {
/*
             * Minimal perf_mmap_close(); there can't be AUX or
             * other events on account of this being the first.
             */

            mapped = get_mapped(event, event_unmapped);
if (mapped)
                mapped(event, vma->vm_mm);
            perf_mmap_unaccount(vma, event->rb);
            ring_buffer_attach(event, NULL);    /* drops last rb->refcount */
            refcount_set(&event->mmap_count, 0);
return ret;
        }

/*
         * Otherwise this is an already existing buffer, and there is
         * no race vs first exposure, so fall-through and call
         * perf_mmap_close().
         */

    }

    perf_mmap_close(vma);
return ret;
}

   上面这个的核心就是通过mmap系统调用,然后调用perf_mmap,分配一片空间给perf_event,后续会使用到这片空间,其挂在event->rb中,后续根据这个来操作。

   注意,全程通过fd访问struct file *file,然后访问file->private_data来得到struct perf_event *event。



armv8_pmuv3_pmu 驱动简介

  首先我们介绍一下pmu是什么,pmu是Performance Monitoring Unit的简称,是可以监测硬件芯片的一些指标的功能。本文用的监测事件就是cpu执行的周期计数器。

  对应pmu硬件,有一个叫做pmu的驱动,这里我以我手里的设备为例,我们先来看看dts定义(从/sys/firmware/fdt解析出来的,可能和基线源码定义不一样):

{
    soc{
cpu-pmu{
compatible="arm,armv8-pmuv3";
interrupts=<0x010x070x04>;
phandle=<0x365>;
};
}
}

  然后我们来看看pmu驱动对应的初始化部分
linux-7.1.6/drivers/perf/arm_pmuv3.c

staticstructplatform_driverarmv8_pmu_driver = {
    .driver        = {
        .name    = ARMV8_PMU_PDEV_NAME,
        .of_match_table = armv8_pmu_of_device_ids,
        .suppress_bind_attrs = true,
    },
    .probe        = armv8_pmu_device_probe,
};

staticint __init armv8_pmu_driver_init(void)
{
int ret;

if (acpi_disabled)
        ret = platform_driver_register(&armv8_pmu_driver);
else
        ret = arm_pmu_acpi_probe(armv8_pmuv3_pmu_init);

if (!ret)
        lockup_detector_retry_init();

return ret;
}
device_initcall(armv8_pmu_driver_init)

  当内核启动后,platform_driver会根据dts的定义,探测相关的设备,这里调用的就是armv8_pmu_device_probe,也就是说armv8_pmu_device_probe是pmu驱动的初始化函数。

linux-7.1.6/drivers/perf/arm_pmuv3.c

staticconststructof_device_idarmv8_pmu_of_device_ids[] = {
    {.compatible = "arm,armv8-pmuv3",    .data = armv8_pmuv3_pmu_init},
// ... ...
}
staticint armv8_pmu_device_probe(struct platform_device *pdev)
{
return arm_pmu_device_probe(pdev, armv8_pmu_of_device_ids, NULL);
}
staticintarmv8_pmu_init(struct arm_pmu *cpu_pmu, char *name,
int (*map_event)(struct perf_event *event))

{
int ret = armv8pmu_probe_pmu(cpu_pmu);
if (ret)
return ret;

    cpu_pmu->handle_irq        = armv8pmu_handle_irq;
    cpu_pmu->enable            = armv8pmu_enable_event;
    cpu_pmu->disable        = armv8pmu_disable_event;
    cpu_pmu->read_counter        = armv8pmu_read_counter;
    cpu_pmu->write_counter        = armv8pmu_write_counter;
    cpu_pmu->get_event_idx        = armv8pmu_get_event_idx;
    cpu_pmu->clear_event_idx    = armv8pmu_clear_event_idx;
    cpu_pmu->start            = armv8pmu_start;
    cpu_pmu->stop            = armv8pmu_stop;
    cpu_pmu->reset            = armv8pmu_reset;
    cpu_pmu->set_event_filter    = armv8pmu_set_event_filter;

    cpu_pmu->pmu.event_idx        = armv8pmu_user_event_idx;
if (brbe_num_branch_records(cpu_pmu))
        cpu_pmu->pmu.sched_task        = armv8pmu_sched_task;

    cpu_pmu->name            = name;
    cpu_pmu->map_event        = map_event;
    cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_EVENTS] = &armv8_pmuv3_events_attr_group;
    cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_FORMATS] = &armv8_pmuv3_format_attr_group;
    cpu_pmu->attr_groups[ARMPMU_ATTR_GROUP_CAPS] = &armv8_pmuv3_caps_attr_group;
    armv8_pmu_register_sysctl_table();
return0;
}

linux-7.1.6/drivers/perf/arm_pmu.c

struct arm_pmu *armpmu_alloc(void)
{
structarm_pmu *pmu;
int cpu;

    pmu = kzalloc_obj(*pmu);
if (!pmu)
goto out;

    pmu->hw_events = alloc_percpu_gfp(struct pmu_hw_events, GFP_KERNEL);
if (!pmu->hw_events) {
        pr_info("failed to allocate per-cpu PMU data.\n");
goto out_free_pmu;
    }

    pmu->pmu = (struct pmu) {
        .pmu_enable    = armpmu_enable,
        .pmu_disable    = armpmu_disable,
        .event_init    = armpmu_event_init,
        .add        = armpmu_add,
        .del        = armpmu_del,
        .start        = armpmu_start,
        .stop        = armpmu_stop,
        .read        = armpmu_read,
        .filter        = armpmu_filter,
        .attr_groups    = pmu->attr_groups,
/*
         * This is a CPU PMU potentially in a heterogeneous
         * configuration (e.g. big.LITTLE) so
         * PERF_PMU_CAP_EXTENDED_HW_TYPE is required to open
         * PERF_TYPE_HARDWARE and PERF_TYPE_HW_CACHE events on a
         * specific PMU.
         */

        .capabilities    = PERF_PMU_CAP_EXTENDED_REGS |
                  PERF_PMU_CAP_EXTENDED_HW_TYPE,
    };

    pmu->attr_groups[ARMPMU_ATTR_GROUP_COMMON] =
        &armpmu_common_attr_group;

    for_each_possible_cpu(cpu) {
structpmu_hw_events *events;

        events = per_cpu_ptr(pmu->hw_events, cpu);
        events->percpu_pmu = pmu;
    }

return pmu;

out_free_pmu:
    kfree(pmu);
out:
returnNULL;
}

intarmpmu_register(struct arm_pmu *pmu)
{
int ret;

    ret = cpu_pmu_init(pmu);
if (ret)
return ret;

/*
     * By this stage we know our supported CPUs on either DT/ACPI platforms,
     * detect the SMT implementation.
     */

    pmu->has_smt = topology_core_has_smt(cpumask_first(&pmu->supported_cpus));

if (!pmu->set_event_filter)
        pmu->pmu.capabilities |= PERF_PMU_CAP_NO_EXCLUDE;

    ret = perf_pmu_register(&pmu->pmu, pmu->name, -1);
if (ret)
goto out_destroy;

    pr_info("enabled with %s PMU driver, %d (%*pb) counters available%s\n",
        pmu->name, bitmap_weight(pmu->cntr_mask, ARMPMU_MAX_HWEVENTS),
        ARMPMU_MAX_HWEVENTS, &pmu->cntr_mask,
        has_nmi ? ", using NMIs" : "");

    kvm_host_pmu_init(pmu);

return0;

out_destroy:
    cpu_pmu_destroy(pmu);
return ret;
}

linux-7.1.6/drivers/perf/arm_pmu_platform.c


intarm_pmu_device_probe(struct platform_device *pdev,
conststruct of_device_id *of_table,
conststruct pmu_probe_info *probe_table)

{
    armpmu_init_fn init_fn;
structdevice *dev = &pdev->dev;
structarm_pmu *pmu;
int ret = -ENODEV;

    pmu = armpmu_alloc();
if (!pmu)
return -ENOMEM;

    pmu->pmu.parent = &pdev->dev;
    pmu->plat_device = pdev;

    ret = pmu_parse_irqs(pmu);
if (ret)
goto out_free;

    init_fn = of_device_get_match_data(dev);
if (init_fn) {
        pmu->secure_access = of_property_read_bool(dev->of_node,
"secure-reg-access");

/* arm64 systems boot only as non-secure */
if (IS_ENABLED(CONFIG_ARM64) && pmu->secure_access) {
            dev_warn(dev, "ignoring \"secure-reg-access\" property for arm64\n");
            pmu->secure_access = false;
        }

        ret = init_fn(pmu);
    } elseif (probe_table) {
        cpumask_setall(&pmu->supported_cpus);
        ret = probe_current_pmu(pmu, probe_table);
    }

if (ret) {
        dev_err(dev, "failed to probe PMU!\n");
goto out_free;
    }

    ret = armpmu_request_irqs(pmu);
if (ret)
goto out_free_irqs;

    ret = armpmu_register(pmu);
if (ret) {
        dev_err(dev, "failed to register PMU devices!\n");
goto out_free_irqs;
    }

return0;

out_free_irqs:
    armpmu_free_irqs(pmu);
out_free:
    armpmu_free(pmu);
return ret;
}

  注意上面的probe函数,有几个比较重要的内容:

  1. 1. 在armpmu_alloc中,内核会申请pmu结构体,并填写几个重要的接口(struct arm_pmu->struct pmu pmu;),比如上面我们在ioctl里面调用的pmu-add()/pmu-enable()等
  2. 2. of_device_get_match_data 获取设备表中的初始化函数:armv8_pmu_init,里面最重要的就是armv8pmu_handle_irq(这个其实就是最终的中断服务响应程序)
  3. 3. armpmu_request_irqs注册pmu中断服务程序armpmu_dispatch_irq(后面这个地方是重点,perf的采样数据就是这个地方写到mmap的空间)
  4. 4. 通过armpmu_register调用perf_pmu_register注册pmu到上面的pmu_idr中。

  到了这里,通过perf驱动中的pmu_idr全局变量,我们就将 armv8_pmuv3_pmu 与 perf envent驱动建立了联系。后续就可以在perf的open,ioctl等接口里面访问armv8_pmuv3_pmu。



perf_ioctl 与 PERF_EVENT_IOC_ENABLE

  在上一个小节,我们已经为perf_event建立了一片空间,用于输出采样数据,先在我们启动采样。和上面的mmap同样的操作,我们直接看对应的fops的ioctl,代码如下:

linux-7.1.6/kernel/events/core.c

staticlongperf_ioctl(struct file *file, unsignedint cmd, unsignedlong arg)
{
structperf_event *event = file->private_data;
structperf_event_context *ctx;
long ret;

/* Treat ioctl like writes as it is likely a mutating operation. */
    ret = security_perf_event_write(event);
if (ret)
return ret;

    ctx = perf_event_ctx_lock(event);
    ret = _perf_ioctl(event, cmd, arg);
    perf_event_ctx_unlock(event, ctx);

return ret;
}

staticlong _perf_ioctl(struct perf_event *event, unsignedint cmd, unsignedlong arg)
{
void (*func)(struct perf_event *);
    u32 flags = arg;

if (event->state <= PERF_EVENT_STATE_REVOKED)
return -ENODEV;

switch (cmd) {
case PERF_EVENT_IOC_ENABLE:
        func = _perf_event_enable;
break;
case PERF_EVENT_IOC_DISABLE:
        func = _perf_event_disable;
break;

// ... ...
    }

// ... ... 
}

staticvoid _perf_event_enable(struct perf_event *event)
{
structperf_event_context *ctx = event->ctx;

// ... ...

    event_function_call(event, __perf_event_enable, NULL);
}

staticvoid __perf_event_enable(struct perf_event *event,
struct perf_cpu_context *cpuctx,
struct perf_event_context *ctx,
void *info)
{
structperf_event *leader = event->group_leader;
structperf_event_context *task_ctx;

// ... ...

    ctx_resched(cpuctx, task_ctx, event->pmu_ctx->pmu, get_event_type(event));
}

// 注意ctx_resched经过多层调用后,到了event_sched_in,开启pmu采样
// ctx_resched->perf_event_sched_in-> ... .... -> event_sched_in

staticint
event_sched_in(struct perf_event *event, struct perf_event_context *ctx)
{
structperf_event_pmu_context *epc = event->pmu_ctx;
structperf_cpu_pmu_context *cpc = this_cpc(epc->pmu);
int ret = 0;

    WARN_ON_ONCE(event->ctx != ctx);

    lockdep_assert_held(&ctx->lock);

if (event->state <= PERF_EVENT_STATE_OFF)
return0;

    WRITE_ONCE(event->oncpu, smp_processor_id());
/*
     * Order event::oncpu write to happen before the ACTIVE state is
     * visible. This allows perf_event_{stop,read}() to observe the correct
     * ->oncpu if it sees ACTIVE.
     */

    smp_wmb();
    perf_event_set_state(event, PERF_EVENT_STATE_ACTIVE);

/*
     * Unthrottle events, since we scheduled we might have missed several
     * ticks already, also for a heavily scheduling task there is little
     * guarantee it'll get a tick in a timely manner.
     */

if (unlikely(event->hw.interrupts == MAX_INTERRUPTS))
        perf_event_unthrottle(event, false);

    perf_pmu_disable(event->pmu);

    perf_log_itrace_start(event);

if (event->pmu->add(event, PERF_EF_START)) {
        perf_event_set_state(event, PERF_EVENT_STATE_INACTIVE);
        event->oncpu = -1;
        ret = -EAGAIN;
goto out;
    }

if (!is_software_event(event))
        cpc->active_oncpu++;
if (is_event_in_freq_mode(event)) {
        ctx->nr_freq++;
        epc->nr_freq++;
    }
if (event->attr.exclusive)
        cpc->exclusive = 1;

out:
    perf_pmu_enable(event->pmu);

return ret;
}

  这个ioctl + PERF_EVENT_IOC_ENABLE 主要是为了调用pmu驱动里面对应的add+enable回调。注意这里的struct pmu,在上面的perf open操作中提到了,在open中查询到对应的pmu驱动(通过perf_pmu_register来注册,本文以armv8 pmu驱动为例)。所以,这里的pmu->add(), pmu->enable()其实都是调用的对应pmu驱动的方法。



armv8_pmuv3_pmu 中断及采样数据写入 (本文核心)

  上文,我们提到了pmu是一个硬件单元,当我们通过ioctl + PERF_EVENT_IOC_ENABLE后,pmu硬件单元就开始工作了,当采样的数据达到一定的数量是,就会触发中断。上面我们在armpmu_request_irqs中提到,中断服务程序是armpmu_dispatch_irq,我们先来看看这部分代码:

linux-7.1.6/drivers/perf/arm_pmu.c

staticirqreturn_tarmpmu_dispatch_irq(int irq, void *dev)
{
structarm_pmu *armpmu;
int ret;
    u64 start_clock, finish_clock;

/*
     * we request the IRQ with a (possibly percpu) struct arm_pmu**, but
     * the handlers expect a struct arm_pmu*. The percpu_irq framework will
     * do any necessary shifting, we just need to perform the first
     * dereference.
     */

    armpmu = *(void **)dev;
if (WARN_ON_ONCE(!armpmu))
return IRQ_NONE;

    start_clock = sched_clock();
    ret = armpmu->handle_irq(armpmu);
    finish_clock = sched_clock();

    perf_sample_event_took(finish_clock - start_clock);
return ret;
}

  这里的armpmu->handle_irq就是armv8pmu_handle_irq,也就是中断服务程序。
linux-7.1.6/drivers/perf/arm_pmuv3.c

staticirqreturn_tarmv8pmu_handle_irq(struct arm_pmu *cpu_pmu)
{
    u64 pmovsr;
structperf_sample_datadata;
structpmu_hw_events *cpuc = this_cpu_ptr(cpu_pmu->hw_events);
structpt_regs *regs;
int idx;

/*
     * Get and reset the IRQ flags
     */

    pmovsr = armv8pmu_getreset_flags();

/*
     * Did an overflow occur?
     */

if (!armv8pmu_has_overflowed(pmovsr))
return IRQ_NONE;

/*
     * Handle the counter(s) overflow(s)
     */

    regs = get_irq_regs();

/*
     * Stop the PMU while processing the counter overflows
     * to prevent skews in group events.
     */

    armv8pmu_stop(cpu_pmu);
    for_each_set_bit(idx, cpu_pmu->cntr_mask, ARMPMU_MAX_HWEVENTS) {
structperf_event *event = cpuc->events[idx];
structhw_perf_event *hwc;

/* Ignore if we don't have an event. */
if (!event)
continue;

/*
         * We have a single interrupt for all counters. Check that
         * each counter has overflowed before we process it.
         */

if (!armv8pmu_counter_has_overflowed(pmovsr, idx))
continue;

        hwc = &event->hw;
        armpmu_event_update(event);
        perf_sample_data_init(&data, 0, hwc->last_period);
if (!armpmu_event_set_period(event))
continue;

if (has_branch_stack(event))
            read_branch_records(cpuc, event, &data);

/*
         * Perf event overflow will queue the processing of the event as
         * an irq_work which will be taken care of in the handling of
         * IPI_IRQ_WORK.
         */

        perf_event_overflow(event, &data, regs);
    }
    armv8pmu_start(cpu_pmu);

return IRQ_HANDLED;
}

  这个中断服务程序的核心就是,先关闭pmu硬件功能,然后根据当前的pmu的寄存器数据,初始化perf_sample_data,然后调用perf_event_overflow进行处理。
linux-7.1.6/kernel/events/core.c

intperf_event_overflow(struct perf_event *event,
struct perf_sample_data *data,
struct pt_regs *regs)

{
/*
     * Entry point from hardware PMI, interrupts should be disabled here.
     * This serializes us against perf_event_remove_from_context() in
     * things like perf_event_release_kernel().
     */

    lockdep_assert_irqs_disabled();

return __perf_event_overflow(event, 1, data, regs);
}

staticint __perf_event_overflow(struct perf_event *event,
int throttle, struct perf_sample_data *data,
struct pt_regs *regs)
{
int events = atomic_read(&event->event_limit);
int ret = 0;

/*
     * Non-sampling counters might still use the PMI to fold short
     * hardware counters, ignore those.
     */

if (unlikely(!is_sampling_event(event)))
return0;

    ret = __perf_event_account_interrupt(event, throttle);

if (event->attr.aux_pause)
        perf_event_aux_pause(event->aux_event, true);

if (event->prog && event->prog->type == BPF_PROG_TYPE_PERF_EVENT &&
        !bpf_overflow_handler(event, data, regs))
goto out;

/*
     * XXX event_limit might not quite work as expected on inherited
     * events
     */


    event->pending_kill = POLL_IN;
if (events && atomic_dec_and_test(&event->event_limit)) {
        ret = 1;
        event->pending_kill = POLL_HUP;
        perf_event_disable_inatomic(event);
        event->pmu->stop(event, 0);
    }

if (event->attr.sigtrap) {
/*
         * The desired behaviour of sigtrap vs invalid samples is a bit
         * tricky; on the one hand, one should not loose the SIGTRAP if
         * it is the first event, on the other hand, we should also not
         * trigger the WARN or override the data address.
         */

bool valid_sample = sample_is_allowed(event, regs);
unsignedint pending_id = 1;
enumtask_work_notify_modenotify_mode;

if (regs)
            pending_id = hash32_ptr((void *)instruction_pointer(regs)) ?: 1;

        notify_mode = in_nmi() ? TWA_NMI_CURRENT : TWA_RESUME;

if (!event->pending_work &&
            !task_work_add(current, &event->pending_task, notify_mode)) {
            event->pending_work = pending_id;
            local_inc(&event->ctx->nr_no_switch_fast);
            WARN_ON_ONCE(!atomic_long_inc_not_zero(&event->refcount));

            event->pending_addr = 0;
if (valid_sample && (data->sample_flags & PERF_SAMPLE_ADDR))
                event->pending_addr = data->addr;

        } elseif (event->attr.exclude_kernel && valid_sample) {
/*
             * Should not be able to return to user space without
             * consuming pending_work; with exceptions:
             *
             *  1. Where !exclude_kernel, events can overflow again
             *     in the kernel without returning to user space.
             *
             *  2. Events that can overflow again before the IRQ-
             *     work without user space progress (e.g. hrtimer).
             *     To approximate progress (with false negatives),
             *     check 32-bit hash of the current IP.
             */

            WARN_ON_ONCE(event->pending_work != pending_id);
        }
    }

    READ_ONCE(event->overflow_handler)(event, data, regs);

if (*perf_event_fasync(event) && event->pending_kill) {
        event->pending_wakeup = 1;
        irq_work_queue(&event->pending_irq);
    }
out:
if (event->attr.aux_resume)
        perf_event_aux_pause(event->aux_event, false);

return ret;
}

  然后通过event->overflow_handler写入采样数据到mmap申请的内存空间(我们上面在open的时候提到了,这里的溢出处理handler就是perf_event_output_forward)。

void
perf_event_output_forward(struct perf_event *event,
struct perf_sample_data *data,
struct pt_regs *regs)

{
    __perf_event_output(event, data, regs, perf_output_begin_forward);
}

static __always_inline int
__perf_event_output(struct perf_event *event,
struct perf_sample_data *data,
struct pt_regs *regs,
int (*output_begin)(struct perf_output_handle *,
struct perf_sample_data *,
struct perf_event *,
unsignedint))
{
structperf_output_handlehandle;
structperf_event_headerheader;
int err;

/* protect the callchain buffers */
    rcu_read_lock();

    perf_prepare_sample(data, event, regs);
    perf_prepare_header(&header, data, event, regs);

    err = output_begin(&handle, data, event, header.size);
if (err)
gotoexit;

    perf_output_sample(&handle, &header, data, event);

    perf_output_end(&handle);

exit:
    rcu_read_unlock();
return err;
}

  在__perf_event_output中:

  1. 1. perf_prepare_sample 从寄存器中准备采样数据
  2. 2. perf_prepare_header 准备采样数据的头。
  3. 3. 根据上面的header,通过output_begin 在event->rb的ringbuf中申请当前这个样本的数据空间
  4. 4. 然后perf_output_sample开始写入。

  在perf_output_end中,我们需要通知用户态的poll可以返回了,现在有数据可以返回了。

linux-7.1.6/kernel/events/ring_buffer.c

voidperf_output_end(struct perf_output_handle *handle)
{
    perf_output_put_handle(handle);
    rcu_read_unlock();
}


staticvoidperf_output_put_handle(struct perf_output_handle *handle)
{
structperf_buffer *rb = handle->rb;
unsignedlong head;
unsignedint nest;

// ... ...

if (handle->wakeup != local_read(&rb->wakeup))
        perf_output_wakeup(handle);

out:
    preempt_enable();
}

staticvoidperf_output_wakeup(struct perf_output_handle *handle)
{
atomic_set(&handle->rb->poll, EPOLLIN | EPOLLRDNORM);

    handle->event->pending_wakeup = 1;

if (*perf_event_fasync(handle->event) && !handle->event->pending_kill)
        handle->event->pending_kill = POLL_IN;

    irq_work_queue(&handle->event->pending_irq);
}

  注意,perf_output_end 还是在pmu中断服务程序内部调用的,为了异步交付数据给用户态,这里给软中断发送一个中断请求,中断服务地址是:event->pending_irq ,也就是perf_pending_irq,这个是在open的过程中, 初始化指定的。

linux-7.1.6/kernel/events/ring_buffer.c

staticvoidperf_pending_irq(struct irq_work *entry)
{
structperf_event *event = container_of(entry, struct perf_event, pending_irq);
int rctx;

/*
     * If we 'fail' here, that's OK, it means recursion is already disabled
     * and we won't recurse 'further'.
     */

    rctx = perf_swevent_get_recursion_context();

/*
     * The wakeup isn't bound to the context of the event -- it can happen
     * irrespective of where the event is.
     */

if (event->pending_wakeup) {
        event->pending_wakeup = 0;
        perf_event_wakeup(event);
    }

if (rctx >= 0)
        perf_swevent_put_recursion_context(rctx);
}

voidperf_event_wakeup(struct perf_event *event)
{
    ring_buffer_wakeup(event);

if (event->pending_kill) {
        kill_fasync(perf_event_fasync(event), SIGIO, event->pending_kill);
        event->pending_kill = 0;
    }
}

staticvoidring_buffer_wakeup(struct perf_event *event)
{
structperf_buffer *rb;

if (event->parent)
        event = event->parent;

    rcu_read_lock();
    rb = rcu_dereference(event->rb);
if (rb) {
        list_for_each_entry_rcu(event, &rb->event_list, rb_entry)
            wake_up_all(&event->waitq);
    }
    rcu_read_unlock();
}


static__poll_tperf_poll(struct file *file, poll_table *wait)
{
structperf_event *event = file->private_data;
structperf_buffer *rb;
__poll_t events = EPOLLHUP;

if (event->state <= PERF_EVENT_STATE_REVOKED)
return EPOLLERR;

    poll_wait(file, &event->waitq, wait);

if (event->state <= PERF_EVENT_STATE_REVOKED)
return EPOLLERR;

if (is_event_hup(event))
return events;

if (unlikely(READ_ONCE(event->state) == PERF_EVENT_STATE_ERROR &&
             event->attr.pinned))
return EPOLLERR;

/*
     * Pin the event->rb by taking event->mmap_mutex; otherwise
     * perf_event_set_output() can swizzle our rb and make us miss wakeups.
     */

    mutex_lock(&event->mmap_mutex);
    rb = event->rb;
if (rb)
        events = atomic_xchg(&rb->poll, 0);
    mutex_unlock(&event->mmap_mutex);
return events;
}

  注意,通过perf_pending_irq,我们调用到了ring_buffer_wakeup,我们可以wake_up_all(&event->waitq)所有等待队列上面的进程,每个进程自己检查,是否有poll数据了,我们在软中断中标记了handle->rb->poll为有数据。。





后记


  总的来说,perf的工作原理(以采集cpu cycle为例)大概如下:

  1. 1. cpu有一个叫做pmu的硬件监测单元,因此设备平台编写了一个叫做pmu的平台驱动,本文是:armv8_pmuv3_pmu驱动。在内核启动的时候,根据dts的定义,自动探测到armv8_pmuv3_pmu驱动,并将pmu驱动注册到perf驱动中的pmu_idr全局变量中。
  2. 2. 用户通过__NR_perf_event_open 系统调用创建一个struct perf_event,绑定armv8_pmuv3_pmu驱动,然后绑定给一个struct file,并给struct file装载各种fops的实现(例如:ioctl/mmap/poll等)。
  3. 3. 用户通过mmap访问perf_mmap,然后将申请的vm挂在到struct perf_event->rb中
  4. 4. 用户通过ioctl+PERF_EVENT_IOC_ENABLE,通过struct perf_event->pmu里面的相关ops,设置pmu,然后开启pmu中断
  5. 5. pmu中断开始响应,将采样数据写入到struct perf_event->rb中,标记了struct perf_event->rb->poll有数据,然后发出一个新的irq软中断,pmu中断结束。
  6. 6. 在perf_pending_irq中,唤醒struct perf_event->waitq上面的所有进程,irq软中断结束。
  7. 7. 在perf_poll中,由于vfs poll的实现原理,在反复调用perf_poll,当我们通过软中断唤醒所有等待队列的进程后,再次调用perf_poll,然后检查struct perf_event->rb->poll,然后返回用户态,用户态开始处理新的采样数据,然后返回。

  完结散花。

参考文献

  • • 无



打赏、订阅、收藏、丢香蕉、硬币,请关注公众号

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-08-21 14:35:26 HTTP/2.0 GET : https://f.mffb.com.cn/a/511399.html
  2. 运行时间 : 0.461823s [ 吞吐率:2.17req/s ] 内存消耗:4,947.56kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=b877d7c441c800bfbb883efa1fe51ccc
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000870s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001253s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000622s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000695s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001557s ]
  6. SELECT * FROM `set` [ RunTime:0.000552s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001343s ]
  8. SELECT * FROM `article` WHERE `id` = 511399 LIMIT 1 [ RunTime:0.002781s ]
  9. UPDATE `article` SET `lasttime` = 1787294127 WHERE `id` = 511399 [ RunTime:0.004368s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000782s ]
  11. SELECT * FROM `article` WHERE `id` < 511399 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.010666s ]
  12. SELECT * FROM `article` WHERE `id` > 511399 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.021740s ]
  13. SELECT * FROM `article` WHERE `id` < 511399 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.026693s ]
  14. SELECT * FROM `article` WHERE `id` < 511399 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.087193s ]
  15. SELECT * FROM `article` WHERE `id` < 511399 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.133383s ]
0.465800s