evpipe性能事件循环

上面一篇文章我们主要介绍了性能事件管道的初始化函数,这一篇文章我们主要介绍如何将性能事件给拉取出来。

signal(SIGINT, term);

signal(SIGINT, term); 这行代码的意图是设置当进程接收到 SIGINT 信号时调用 term 函数。term 应该是一个用户定义的函数,用来处理 SIGINT 信号,例如清理资源或优雅地退出程序。

int evpipe_loop(evpipe_t *evp, int *sig, int strict) {
	int cpu, err, ready;
	//性能事件循环
	for (;!(*sig);) {
		//监测性能事件
		ready = poll(evp->poll, evp->ncpus, -1);
		if (ready <= 0) return ready ? : 0;
        //如果准备好了, 我们直接读取对应的性能事件
		for (cpu = 0; ready && (cpu < evp->ncpus); cpu++) {
			if (!(evp->poll[cpu].revents & POLLIN))
				continue;
             //不断的提取
			err = evqueue_drain(&evp->q[cpu], strict);
			ready--;
		}
	}

	return 0;
}

上面我们使用了evqueue_drain 这个函数,这个函数主要的作用是从我们之前在init函数中初始化的mem中读取对应的数据。

我们首先设置三个参数,分别是内存大小,初始地址, 偏移量,

size = q->mem->data_size;
offs = q->mem->data_offset;
base = (uint8_t *)q->mem + offs;

首先我们设置一下对应的结构体:

struct perf_event_mmap_page {
   __u32 version;     //版本号码
    __u32 compat_version; /* lowest version this is compat with */
    __u32 lock;           /* seqlock for synchronization */
    __u32 index;          /* hardware counter identifier */
    __s64 offset;         /* add to hardware counter value */
    __u64 time_enabled;   /* time event active */
    __u64 time_running;   /* time event on CPU */
    union {
         __u64   capabilities;
         struct {
             __u64 cap_usr_time / cap_usr_rdpmc / cap_bit0 : 1,
                   cap_bit0_is_deprecated : 1,
                   cap_user_rdpmc         : 1,
                   cap_user_time          : 1,
                   cap_user_time_zero     : 1,
         };
     };
   __u16 pmc_width;
   __u16 time_shift;
   __u32 time_mult;
   __u64 time_offset;
   __u64 __reserved[120];   /* Pad to 1 k */
   __u64 data_head;         /* head in the data section */
   __u64 data_tail;         /* user-space written tail */
   __u64 data_offset;       /* where the buffer starts */
   __u64 data_size;         /* data buffer size */
   __u64 aux_head;
   __u64 aux_tail;
   __u64 aux_offset;
   __u64 aux_size;

}

这个结构体是perf工具的一部分,perf是Linux内核提供的一个强大的性能分析工具,用于监控和测量系统和应用程序的性能。通过这个结构体,应用程序可以高效地访问硬件性能计数器的数据,进行性能分析和优化。

然后我们遍历这段内存, 我们主要使用到下面两个辅助函数:

  • __get_head 函数用于获取perf_event_mmap_page结构体中data_head字段的值。这是一个指向下一个可读数据块的指针。函数首先通过volatile关键字声明的指针来读取data_head,确保编译器不会对读取操作进行优化,从而保证每次都是直接从内存中读取最新的值。然后使用asm volatile语句来创建一个内存屏障,防止编译器和处理器对代码进行重排序,确保在读取data_head之前的所有操作都已经完成。

  • __set_tail 函数用于设置perf_event_mmap_page结构体中的data_tail字段的值。data_tail通常用于指向下一个可写入的数据块。同样地,这里使用asm volatile语句来创建一个内存屏障,确保在设置data_tail之前的所有操作都已经完成,防止重排序。

static inline uint64_t __get_head(struct perf_event_mmap_page *mem)
{
	uint64_t head = *((volatile uint64_t *)&mem->data_head);
	asm volatile("" ::: "memory");
	return head;
}

static inline void __set_tail(struct perf_event_mmap_page *mem, uint64_t tail)
{
	asm volatile("" ::: "memory");
	mem->data_tail = tail;
}

然后就是通过这两个辅助函数进行遍历对应的内存空间:

for (head = __get_head(q->mem);  //首先获取对应内存地址的头
	q->mem->data_tail != head;  //判断是否已经到了结尾
	//更新我们的head
	__set_tail(q->mem, q->mem->data_tail + ev->hdr.size)) 

我们下面一步做的事情是从对应的地址空间中读取出对应的数据,

首先将队列当前的尾部位置赋值给局部变量tail, 并且根据尾部的值计算出当前的位置, 然后我们得到当前的事件。然后我们继续计算next的值。

tail = q->mem->data_tail;
this = base + (tail % size);
ev   = (void *)this;
next = base + ((tail + ev->hdr.size) % size);

但是我们需要注意的一点是, 如果下一个位置next在当前位置this之前,说明已经绕到了队列的开头,需要处理内存的环绕情况。

我们需要去处理对应的内存环绕的问题,首先我们需要计算计算从当前位置this到队列末尾的距离,这个距离表示数据项在队列末尾的部分的长度。

然后重新分配q->buf的内存,使其大小等于数据项的总大小ev->hdr.size,并且将从当前位置this到队列末尾的数据复制到新分配的缓冲区q->buf的前left个字节。

将队列开头到数据项开始前的部分复制到q->buf的剩余部分。ev->hdr.size - left是这部分数据的长度。

q->buf = realloc(q->buf, ev->hdr.size);
memcpy(q->buf, this, left);
memcpy(q->buf + left, base, ev->hdr.size - left);
ev = q->buf;

然后我们根据对应的handle钩子函数去处理对应的事件, 分别有两种对应的事件, 第一种是PERF_RECORD_SAMPLE, 我们直接调用对应的钩子函数。

switch (ev->hdr.type) {
case PERF_RECORD_SAMPLE:
	err = event_handle(ev, ev->hdr.size);
	break;
case PERF_RECORD_LOST:
	lost = (void *)ev;
	if (strict) {
		_e("lost %"PRId64" events", lost->lost);
		err = -EOVERFLOW;
	} else {
		_w("lost %"PRId64" events", lost->lost);
	}
	break;
default:
	_e("unknown perf event %#"PRIx32, ev->hdr.type);
	err = -EINVAL;
	break;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值