厦门高端网站建设,大连网建会,个人网站备案核验单,最好用的免费空间目录 一、简介二、sys_perf_event_open1、参数 struct perf_event_attr2、参数 pid3、参数 cpu4、参数 group_fd5、参数 flags6、记录信息的环形缓冲区 三、计数器(组)的启用或禁用四、示例代码 一、简介 性能计数器是在大多数现代cpu上可用的特殊硬件寄存器。这些寄存器统计特… 目录 一、简介二、sys_perf_event_open1、参数 struct perf_event_attr2、参数 pid3、参数 cpu4、参数 group_fd5、参数 flags6、记录信息的环形缓冲区 三、计数器(组)的启用或禁用四、示例代码 一、简介 性能计数器是在大多数现代cpu上可用的特殊硬件寄存器。这些寄存器统计特定类型的hw事件的数量例如执行的指令、遭受的cachemisses或错误预测的分支而不会减慢内核或应用程序的速度。这些寄存器还可以在事件数量达到阈值时触发中断——因此可以用于分析在该CPU上运行的代码。 错误预测的分支是指处理器的分支预测机制在条件分支指令中错误地预测了分支的结果。分支预测
是现代处理器中使用的一种技术通过在实际解析之前猜测分支指令的结果来提高性能。当遇到分支指令时处理器会尝试预测分支是否会被执行条件为真或不会被执行条件为假。
基于这个预测处理器会推测性地执行预测的分支路径中的指令以保持流水线的正常运作并避免停顿。然而如果分支预测结果错误处理器需要清空错误执行的指令并重新启动正确的分支路径这会
导致性能下降称为分支预测错误。分支预测错误可能由多种原因引起包括复杂的分支条件、有限的历史信息和程序行为的变化。
现代处理器采用复杂的分支预测算法来最小化错误预测并提高整体性能。对于软件开发人员和编译器设计者来说了解分支预测的行为并考虑优化代码以减少错误预测
非常重要例如使用无分支的编程技巧或重新排列代码以提高分支预测的准确性。Linux性能计数器子系统提供了这些硬件功能的抽象。它提供每个任务和每个CPU的计数器、计数器组并在此基础上提供事件功能。它提供了“虚拟的”64位计数器与底层硬件计数器的宽度无关。 Linux性能计数器子系统Linux Performance Counter Subsystem是Linux内核中的一个
组件提供了对硬件性能计数器的访问和使用。Linux性能计数器子系统为用户空间提供了一组接口允许应用程序或工具通过编程方式访问和
配置硬件性能计数器。它包括以下主要组件1. perf_event这是Linux内核的核心组件负责与硬件性能计数器进行交互以收集和管理
性能事件数据。它提供了一组系统调用和配置选项允许用户空间应用程序注册事件、启动和停止计数
器以及读取计数器的值。2. perf tool这是一个基于命令行的实用工具利用perf_event接口来收集和分析性能事件
数据。它可以用于统计系统或应用程序的性能指标、检测瓶颈和分析性能问题。(kernel\tools\perf
目录下)通过Linux性能计数器子系统开发人员可以深入了解系统和应用程序的行为并通过优化算法、
调整参数或改进代码来提高性能。它在系统调优、性能分析和性能测试等领域有广泛的应用。必读perf_event框架之ARM PMU硬件
二、sys_perf_event_open
linux-4.19-kernel\tools\perf\perf-sys.h
static inline int sys_perf_event_open(struct perf_event_attr *attr,pid_t pid, int cpu, int group_fd,unsigned long flags)
{int fd;fd syscall(__NR_perf_event_open, attr, pid, cpu,group_fd, flags);#ifdef HAVE_ATTR_TESTif (unlikely(test_attr__enabled))test_attr__open(attr, pid, cpu, fd, group_fd, flags);
#endifreturn fd;
}性能计数器可以通过特殊的文件描述符访问。每个虚拟计数器使用一个文件描述符。这个特殊的文件描述符是通过sys_perf_event_open()系统调用打开的:
int sys_perf_event_open(struct perf_event_attr *hw_event_uptr, pid_t pid, \int cpu, int group_fd, unsigned long flags); 系统调用返回新的fd。fd可以通过普通的VFS系统调用来使用:read()可以用来读取计数器fcntl()可以用来设置阻塞模式等等。 可以同时打开多个计数器这些计数器可以使用poll()函数调用。
1、参数 struct perf_event_attr 当创建一个新的计数器fd时perf_event_attr是:
struct perf_event_attr { /*
/* config字的MSB表示剩余的位是否包含cpu特定的(原始)计数器配置数据如果未设置* 则接下来的7位是事件类型其余位是事件标识符。*/__u64 config;__u64 irq_period;__u32 record_type;__u32 read_format;__u64 disabled : 1, /* off by default */inherit : 1, /* children inherit it */pinned : 1, /* must always be on PMU */exclusive : 1, /* only group on PMU */exclude_user : 1, /* dont count user */exclude_kernel : 1, /* ditto kernel */exclude_hv : 1, /* ditto hypervisor */exclude_idle : 1, /* dont count when idle */mmap : 1, /* include mmap data */munmap : 1, /* include munmap data */comm : 1, /* include comm data */__reserved_1 : 52;__u32 extra_config_len;__u32 wakeup_events; /* wakeup every n events */__u64 __reserved_2;__u64 __reserved_3;
};config字段指定了计数器应该计数的内容。它分为3个位域:
Raw_type: 1位(最高位)0x8000_0000_0000_0000
type: 7位(次高位)0x7f00_0000_0000_0000
event_id: 56位(最低位)0x00ff_ffff_ffff_ffff如果 raw_type 为1则计数器将统计由event_config的剩余63位指定的硬件事件。编码是
某个机器特有的不同的CPU有不同的编码。如果 raw_type 为0那么 type 字段表示这是什么类型的计数器编码如下:
enum perf_type_id {PERF_TYPE_HARDWARE 0,PERF_TYPE_SOFTWARE 1,PERF_TYPE_TRACEPOINT 2,
}; PERF_TYPE_HARDWARE的计数器将对 event_id 指定的硬件事件进行计数:sys_perf_event_open
系统调用的Event_id参数如下:
enum perf_hw_id {/* 常见的硬件事件由内核概括:*/PERF_COUNT_HW_CPU_CYCLES 0,PERF_COUNT_HW_INSTRUCTIONS 1,PERF_COUNT_HW_CACHE_REFERENCES 2,PERF_COUNT_HW_CACHE_MISSES 3,PERF_COUNT_HW_BRANCH_INSTRUCTIONS 4,PERF_COUNT_HW_BRANCH_MISSES 5,PERF_COUNT_HW_BUS_CYCLES 6,
}; 这些都是标准化的事件类型在Linux下所有实现了性能计数器支持的cpu上的工作相对统一尽管可能会有变化(例如不同的cpu可能会对缓存层次结构的不同级别的缓存引用和缺失计数)。 如果CPU无法对所选事件进行计数则系统调用将返回-EINVAL。 它还支持更多的hw_event_types但它们是特定于某个cpu的可以作为原始事件访问。例如要在Intel Core cpu上统计“总线锁信号断言时外部总线周期”事件可以传入0x4064 event_id值并设置hw_event。Raw_type为1。
PERF_TYPE_SOFTWARE类型的计数器将对可用的软件事件之一进行计数通过 event_id 选择:/** 内核提供的特殊“软件”计数器即使是硬件不支持性能计数器。这些计数器测量内核的各种物理事件* 和软件事件(也允许对这些事件进行剖析):*/
enum perf_sw_ids {PERF_COUNT_SW_CPU_CLOCK 0,PERF_COUNT_SW_TASK_CLOCK 1,PERF_COUNT_SW_PAGE_FAULTS 2,PERF_COUNT_SW_CONTEXT_SWITCHES 3,PERF_COUNT_SW_CPU_MIGRATIONS 4,PERF_COUNT_SW_PAGE_FAULTS_MIN 5,PERF_COUNT_SW_PAGE_FAULTS_MAJ 6,PERF_COUNT_SW_ALIGNMENT_FAULTS 7,PERF_COUNT_SW_EMULATION_FAULTS 8,
};当ftrace事件跟踪器可用时PERF_TYPE_TRACEPOINT类型的计数器可用event_id的值可以从/debug/tracing/events///id中获取 计数器有两种类型计数计数器和采样计数器。“计数”计数器用于对发生的事件的数目进行计数其特征是irq_period 0。计数器的read()返回计数器的当前值和由read_format指定的可能的附加值每个值的大小为u64(8字节)。
/* * 可以在hw_event中设置的比特位。Read_format要求读取计数器时返回指定的数值* 按位值的递增顺序在计数器值之后。* /
enum perf_event_read_format {PERF_FORMAT_TOTAL_TIME_ENABLED 1,PERF_FORMAT_TOTAL_TIME_RUNNING 2,
}; 使用这些额外的值我们可以建立特定计数器的超额分配比率从而使我们能够将轮询调度效果考虑在内。 一个“采样”计数器被设置为每N个事件产生一个中断其中N由irq_period给出。采样计数器的irq_period 0。record_type控制在每个中断上记录的数据:
/** 可以在hw_event中设置的比特位*/
enum perf_event_record_format {PERF_RECORD_IP 1U 0Perf_record_tid 1Perf_record_time 1u 2Perf_record_addr 1u 3Perf_record_group 1u 4Perf_record_callchain 1u 5
};
这样(和其他)的事件将被记录在一个环形缓冲区中该缓冲区可以通过mmap()向用户空间提供。disabled位指定计数器一开始是禁用还是启用。如果它最初是禁用的那么可以通过ioctl或prctl启用它。 如果设置了inherit位则指定此计数器应该对后代任务以及指定任务的事件进行计数。这只适用于新创建的后代节点而不是计数器创建时已存在的后代节点(也不适用已存在后代节点的新后代节点)。 如果设置了pinning位则指定该计数器应尽可能始终在CPU上。它只适用于硬件计数器和计数器组领导。如果固定的计数器不能被放置到CPU上(例如因为没有足够的硬件计数器或因为与其他事件冲突)那么计数器将进入“错误”状态其中read返回文件结束符(即read()返回0)直到计数器随后被启用或禁用。 如果设置了exclusive位则指定当该计数器的组在CPU上时它应该是唯一使用CPU计数器的组。在未来这将允许复杂的监控程序通过extra_config_len提供额外的配置信息以利用CPU的性能监控单元(PMU)的高级特性这些特性无法通过其他方式访问并且可能会干扰其他硬件计数器。 exclude_user、exclude_kernel和exclude_hv位提供了一种方法可以要求将事件计数限制为CPU处于用户模式、内核模式 或hypervisor模式时的次数。 Hypervisor模式又称为虚拟化模式或监控模式是一种在计算机系统中实现虚拟化的技术。
它是一种软件或硬件层面的虚拟化技术允许多个虚拟机Virtual MachineVM共享同一台
物理主机Host的计算资源。在Hypervisor模式下虚拟机监控程序也称为Hypervisor或VMMVirtual Machine
Monitor作为一个中间层运行在物理主机的操作系统或硬件之上。它负责管理和调度虚拟机
的创建、运行和访问计算资源。Hypervisor模式有两种类型Type 1 Hypervisor裸机Hypervisor也称为本地Hypervisor或裸金属Hypervisor该类型
的Hypervisor直接运行在物理主机的硬件上独立于宿主操作系统。它提供了更高的性能和直接的硬
件访问能力因为它绕过了宿主操作系统的层。常见的Type 1 Hypervisor包括VMware ESXi、
Microsoft Hyper-V和Xen。Type 2 Hypervisor主机Hypervisor该类型的Hypervisor运行在宿主操作系统上作为一个
应用程序与宿主操作系统共享硬件资源。它通过宿主操作系统提供的接口来访问硬件并对虚拟机
进行管理。常见的Type 2 Hypervisor包括Oracle VirtualBox和VMware Workstation。Hypervisor模式使得在一台物理主机上同时运行多个虚拟机成为可能每个虚拟机可以独立运行
不同的操作系统和应用程序。这为资源利用率的提高、系统隔离和应用程序测试等场景提供了灵活性和
便利性。mmap和munmap位允许记录PROT_EXEC的mmap/munmap操作这些可以用于将用户空间IP地址与实际代码关联即使映射(甚至整个过程)消失后这些事件也被记录在环缓冲区中。 PROT_EXEC是一个用于内存保护的常量在Linux中与mmap内存映射系统调用以及内存页的
保护属性有关。PROT_EXEC表示页是可执行的。当创建或修改内存映射时可以使用这个标志来指示内核将相关
的内存页标记为可执行使得该页中的指令可以被处理器执行。当一个内存页被设置为PROT_EXEC时可以将其视为一个包含可执行代码的区域允许程序在该
内存页中运行代码。这个标志通常在加载可执行文件到内存时使用确保代码区域被标记为可执行以供程序执行。同时
它也用于共享库动态链接库以便允许多个进程共享同一库的可执行代码部分。总结来说PROT_EXEC常量用于指示内核将内存页标记为可执行以便程序可以在这些页中运行代码。comm位允许在进程创建时跟踪进程的comm数据。这也记录在环缓冲区中。 在Linux系统中每个进程都有一个名为comm的属性它代表进程的命令名或进程名。
它是进程在进程表中的一个字段用于标识进程所属的可执行文件或命令。comm属性通常通过读取进程的/proc/pid/comm文件来获取其中pid是进程
的ID。这个文件中只包含一个单行文本表示进程的命令名。例如假设进程ID为1234要获取它的comm数据可以使用以下命令cat /proc/1234/comm
这将输出进程1234的命令名。comm属性在系统监控、进程管理和调试等方面非常有用。它可以帮助用户了解系统中
运行的进程以及它们所关联的可执行文件或命令。2、参数 pid sys_perf_event_open()系统调用的pid参数允许计数器特定于一个任务:
Pid 0:如果Pid参数为0则将计数器附加到当前进程。
Pid 0:将计数器附加到特定进程(如果当前进程有足够的权限)
Pid 0:对所有进程计数(每个CPU计数器)3、参数 cpu cpu参数允许特定于cpu的计数器:
cpu 0:该计数器限制到特定的cpu
cpu -1:计数器对所有cpu计数(注意:pid -1和cpu -1的组合是无效的。) pid 0和cpu -1计数器是一个针对任务的计数器它对该任务的事件进行计数并跟随该任务到调度到的任何cpu。任何用户都可以为自己的任务创建每个任务的计数器。 pid -1和cpu x计数器是一个针对cpu的计数器对cpu -x上的所有事件计数。每个CPU计数器需要CAP_SYS_ADMIN权限。
4、参数 group_fd group_fd参数允许设置计数器“组”。一个计数器组有一个计数器是组的“领导”。首先创建leader在sys_perf_event_open调用中使用group_fd -1创建leader。接下来创建该组其他成员其中group_fd给出了该组领导的fd。接下来创建其他组成员其中group_fd给出了组长的fd。(用group_fd -1创建一个计数器它本身被认为是一个只有一个成员的组。) 计数器组是作为一个单位调度到CPU上的也就是说只有当组中的所有计数器都可以放置到CPU上时它才会被放置到CPU上。这意味着成员计数器的值可以相互比较、相加、除以(以获得比率)等因为它们对执行的同一组指令的事件进行了计数。
5、参数 flags flags参数当前未被使用并且必须为0。
6、记录信息的环形缓冲区 如前所述异步事件(如计数器溢出或PROT_EXEC mmap跟踪)被记录到环形缓冲区中。这个环形缓冲区是通过mmap()创建和访问的。 mmap的大小应该是12^n页其中第一页是元数据页(struct perf_event_mmap_page)包含了各种信息例如环缓冲区头的位置。
/** 可以通过mmap映射的页的结构*/
struct perf_event_mmap_page { __u32 version; /* version number of this structure */ __u32 compat_version; /* 这是兼容的最低版本 *//** Bits needed to read the hw counters in user-space.** u32 seq;* s64 count;** do {* seq pc-lock;** barrier()* if (pc-index) {* count pmc_read(pc-index - 1);* count pc-offset;* } else* goto regular_read;** barrier();* } while (pc-lock ! seq);** NOTE: for obvious reason this only works on self-monitoring* processes.*/ __u32 lock; /* 用于同步的Seqlock */__u32 index; /* 硬件计数器标识符 */ __s64 offset; /* 添加到硬件计数器值 *//** 控制mmap()数据缓冲区中的数据。** 在支持SMP的平台上读取这个值后用户空间应该发出一个rmb()——* 参见perf_event_wakeup()。*/ __u32 data_head; /* 数据部分的头 */
}; 注意:hw-counter用户空间位是特定于arch的目前只在powerpc上实现。
以下2^n页是环形缓冲区其中包含了这种形式的事件:
#define PERF_RECORD_MISC_KERNEL (1 0)
#define PERF_RECORD_MISC_USER (1 1)
#define PERF_RECORD_MISC_OVERFLOW (1 2)
struct perf_event_header {__u32 type;__u16 misc;__u16 size;
};enum perf_event_type {/** MMAP事件记录了PROT_EXEC映射这样我们就可以将用户空间的ip与代码关联起来。它们* 的结构如下:** struct {* struct perf_event_header header;** u32 pid, tid;* u64 addr;* u64 len;* u64 pgoff;* char filename[];* };*/ PERF_RECORD_MMAP 1,PERF_RECORD_MUNMAP 2,/** struct {* struct perf_event_header header;** u32 pid, tid;* char comm[];* };*/ PERF_RECORD_COMM 3,/** When header.misc PERF_RECORD_MISC_OVERFLOW the event_type field* will be PERF_RECORD_*** struct {* struct perf_event_header header;** { u64 ip; } PERF_RECORD_IP* { u32 pid, tid; } PERF_RECORD_TID* { u64 time; } PERF_RECORD_TIME* { u64 addr; } PERF_RECORD_ADDR** { u64 nr;* { u64 event, val; } cnt[nr]; } PERF_RECORD_GROUP** { u16 nr,* hv,* kernel,* user;* u64 ips[nr]; } PERF_RECORD_CALLCHAIN* };* 当 header 的 type 为 PERF_RECORD_IP 时有效。* 当 header 的 type 为 PERF_RECORD_TID 时有效。* ....*/
};
注意:PERF_RECORD_CALLCHAIN是特定于arch的目前只在x86上实现。可以通过poll()/select()/epoll()和fcntl()管理信号来通知新事件。通常为每个页面生成通知,但是可以另外设置 perf_event_attr.wakeup_events 生成一个每个计数器溢出事件。 未来的工作将包括一个到环形缓冲区的splice()接口。
三、计数器(组)的启用或禁用 计数器可以启用和禁用在两个方面:通过ioctl和通过通过prctl。当计数器被禁用时它不会计数或生成事件但会继续存在并保持其count值。 可以启用单个计数器
ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);可以禁用单个计数器
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);对于计数器组传递PERF_IOC_FLAG_GROUP作为第三个参数。启用或禁用一个组的leader使整个组启用或禁用;也就是说当组长被禁用时组中的任何计数器都不会计数。启用或禁用除leader以外的其他成员只会影响该计数器——禁用非leader会停止该计数器的计数但不会影响其他计数器。 此外还可以使用非继承的溢出计数器
ioctl(fd, PERF_EVENT_IOC_REFRESH, nr);为nr事件启用一个计数器之后它会再次被禁用。进程可以使用prctl启用或禁用所有附加到它的计数器组:
prctl(PR_TASK_PERF_EVENTS_ENABLE);
prctl(PR_TASK_PERF_EVENTS_DISABLE);这适用于当前进程上的所有计数器无论是由这个进程还是由另一个进程创建的并不影响该进程在其他进程上创建的任何计数器。它只启用或禁用组长而不是组中的任何其他成员。
架构要求 如果您的架构没有硬件性能指标您仍然可以使用基于hrtimers的通用软件计数器进行采样。 HrtimersHigh Resolution Timers是Linux内核中提供的一种机制用于实现对高精度时间
的处理和管理。它提供了比传统定时器更高的精度和灵活性。Hrtimers主要用于以下几个方面
1. 定时器精度Hrtimers允许用户程序和内核能够以纳秒级nanosecond的精度设置和处理定时任
务相较于传统定时器jiffies提供了更高的分辨率和精度。2. 定时器类型Hrtimers支持多种定时器类型包括相对定时器相对于当前时间的延时触发、绝
对定时器相对于特定时间点的延时触发以及周期性定时器以固定间隔触发。3. 定时器管理Hrtimers提供了管理定时器的API和回调函数可以创建、修改、取消和查询定时器
以及处理定时器触发时的回调函数。4. 用户空间接口作为系统调用的一部分Hrtimers还为用户空间程序提供了相关的API以便于用户
程序创建和管理定时器。Hrtimers在实时系统、高性能计算和嵌入式系统中广泛应用能够提供更精确的定时任务和事件
处理。通过使用Hrtimers开发者可以更好地控制和利用系统中的时间资源。因此为了将HAVE_PERF_EVENTS添加到Kconfig中我们至少需要这样做:
asm/perf_event.h—一个基本的存根就足够了支持atomic64类型(以及相关的辅助函数) HAVE_PERF_EVENTS是一个宏定义在Linux内核中用于判断系统是否支持性能事件子系统。该宏
定义用于编译时确定系统是否支持性能事件子系统。如果系统支持该子系统宏定义值为1表示开发者
可以使用perf_event_open()等相关函数来创建和管理性能事件。如果系统不支持该子系统宏定义值
通常为0表示不能使用性能事件相关的功能。在软件开发中存根Stub通常用于代替真正的实现或外部依赖以便进行测试、集成或模块间
的开发。存根是一个轻量级的替代品用于模拟或模仿某个组件的行为以便进行开发和测试而不必
依赖完整的实现或外部系统。存根通常简化了组件之间的依赖关系并且可以具有一些预定的行为或输出以便用于测试特定的
场景。它们可以通过编程手段直接创建也可以通过使用存根生成工具自动生成。在上下文中当提到一个基本的存根就足够时意味着在这个特定的场景中只需一个简单的替代
实现来模拟所需的行为。这个存根通常只需要提供所需的最小功能以满足测试或开发过程中的特定
需求而无需实现完整的功能或依赖于外部系统。如果您的架构确实具有硬件功能则可以覆盖弱存根hw_perf_event_init()以注册硬件计数器。 具有d-cache混迭问题的体系结构如Sparc和ARM内核配置应该选择PERF_USE_VMALLOC以避免使用perf mmap()时出现这些问题。
四、示例代码 下面是一个使用 perf_event_open 函数与 Linux内核perf event子系统 进行交互的基本的 Linux 应用程序示例的 C 代码
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include sys/ioctl.h
#include sys/syscall.h
#include linux/perf_event.h// 定义 perf event 变量
struct perf_event_attr pe;
int fd;int main() {memset(pe, 0, sizeof(struct perf_event_attr));pe.type PERF_TYPE_HARDWARE; // 选择硬件性能计数器类型pe.size sizeof(struct perf_event_attr);pe.config PERF_COUNT_HW_INSTRUCTIONS; // 统计指令数pe.disabled 1; // 初始化时禁用计数器pe.exclude_kernel 1; // 排除内核态的计数fd syscall(__NR_perf_event_open, pe, 0, -1, -1, 0);if (fd -1) {printf(Failed to open perf event!\n);exit(1);}ioctl(fd, PERF_EVENT_IOC_RESET, 0); // 重置计数器ioctl(fd, PERF_EVENT_IOC_ENABLE, 0); // 启用计数器// 执行需要统计的程序或代码ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); // 禁用计数器// 读取计数器的结果uint64_t count;read(fd, count, sizeof(uint64_t));printf(Instructions Executed: % PRIu64 \n, count);close(fd); // 关闭 perf event 文件描述符return 0;
}--------------------本文主要内容来自linux-4.19-kernel/tools/perf/design.txt-------------------- 欢迎大家指导和交流如果我有任何错误或遗漏请立即指正我愿意学习改进。期待与大家一起进步