0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux TraceEvent - 史上最长宏定义

Linux阅码场 来源:Linuxer 2020-06-28 09:34 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

TraceEvent是内核中一种探测的机制,据说在不使能的时候是没有损耗的。据说使用起来挺简单,但是要看懂着实需要花些力气。

例子

从例子中学习,一般都是比较好的方法。内核开发者也比较nice,在内核源码samples/trace_events目录下就有这么一个例子。

其中文件一共有三个:

这个例子以内核模块的形式存在,所以只要执行make就可以编译完成。

总的来说,要定义和使用tracepoint,只要做两点。

用TRACE_EVENT来定义一个新的tracepoint

在需要的地方,使用函数trace_XXX打印输出

有了例子我们就要跑一跑,来看看如何使用的。

首先我们要编译出我们的例子,这时候需要加上打开两个编译配置

CONFIG_SAMPLES

CONFIGSAMPLETRACE_EVENTS

编译

make M=samples/trace_events

然后加载这个例子模块

modprobe trace-events-sample

因为用户接口在debugfs上,所以还要确保debugfs挂载了。

mount -t debugfs none /sys/kernel/debug/

此时我们就能在 /sys/kernel/debug/tracing/events/sample-trace/ 目录下看到该模块创建好的trace event了。

接下来,我们就可以打开这个探测时间,并且查看探测的输出了。

cd /sys/kernel/debug/tracing

echo 1 > events/sample-trace/enable

cat trace

echo 0 > events/sample-trace/enable

通过cat trace观察,可以看出系统运行时的一些状态。

让我们进一步再来看看events/sample-trace这个目录:

可以看到

目录名称sample-trace由TRACE_SYSTEM这个宏定义,所以通过查找这个宏,就能知道有多少events的大类

每一个TRACE_EVENT都有一个自己的目录

源文件中trace_XXX的函数就是执行探测记录的地方了。那么这些函数是怎么定义的呢?

TRACE_EVENT定义

看完了例子,我们就该看代码实现了。讲真,这是我见过的最长的宏展开了。之前在qemu上看到的那个hmp-command和这个比起来简直就是个小屁孩。

先来看一下例子中是如何定义一个trace event的。和其他定义不同,定义trace event的定义在头文件,而非源文件。我把trace-events-sample.h文件做一个简要的打开。

中间我省略了很多TRACEEVENT及其变体,每一个TRACEEVENT对应了一个trace point。

可以看到,一个trace event的定义需要涉及到起码两个头文件。

史上最长宏定义

你以为就这么简单吗?当然不是,作为有多年阅读c语言代码的老司机,看到真正的定义,我都差点没有吐出来。。。

好了,不扯淡了。怎么能很好的解释这个宏展开的过程呢?还是用一张图吧。倒吸一口气,准备一次无尽的代码阅读。

终于完了,也不知道有没有漏掉什么。。。大家如果真的想要看实际代码中展开后的代码,可以运行

make samples/trace_events/trace-events-sample.i

生成的文件是经过预处理后得到的源代码。不过相信我,你可能不太会愿意去看这个(捂脸)

回过头来再看这展开,让我们来总结一下这个过程:

一共包含了两个头文件:linux/tracepoint.h 和 trace/define_trace.h

在trace/definetrace.h中,反复定义了TRACEEVENT且再次包含samples/trace_events/trace-events-sample.h,实现了一个宏定义多次展开的效果

究竟定义了什么?

哪怕有了上面这个图,我想大部分人也是不会去看的。或者说,看了可能也不知道这些宏展开究竟定义了些什么?

帮人帮到底,送佛送到西

既然都帮大家做了宏展开,那我就干脆再用一张图展示一下这么多宏定义究竟定义了些什么。

经过了一番云里雾里的宏展开,实际上就是(主要)定义出了这么一个数据结构 --traceeventcall。而且这个数据结构关联了几个重要的小伙伴

tracepoint

trace_event_class

trace_event

后续,我们将逐渐看到在初始化和使能的过程中,这些数据结构之间的爱恨情仇。

注册trace_event

有了数据结构,想要使用这个功能,我们能想到的第一步就是要把相关的数据结构注册到某个地方,这样下次才能够被使用到是不是?

这个秘密隐藏在了刚才宏展开的最后一次展开中,大家可以回过去搜“section("ftraceevents") &event##name;”。有内核代码经验的朋友可能已经猜到了,这个意思是我们把一部分的内容强制保存在了一个名为ftraceevents的section中。这些是什么呢?对了就是traceevent_call结构体的指针们。

有了这个信息,我们再来看链接文件的定义:

我们看到ftraceevents这个section被包含在_startftrace_events之间。那就沿着这条线继续。

看到了么?我们依次从_start|stopftraceevents之间拿出每一个内容,再执行eventinit()。而这个类型正好是traceeventcall,和刚才的定义吻合上。

但是eventinit()里面又调用了什么call->class->rawinit(call),这是什么个鬼?别急,这个我已经给你写好了。请跳回到刚才解释traceeventcall的图上找找,这个rawinit函数就是traceeventrawinit。最后这个通过registertraceevent将traceeventcall.event注册到系统中,而这个event的类型是trace_event。

怎么样,是不是够刺激的?

最后我们再来展示一下trace_event注册到系统中后的样子吧。

trace_event结构会在两个地方注册:

ftraceeventlist:这个链表用来遍历事件的号码

event_hash[128]: 这个哈希表用来查找

有没有看到其中funcs的成员第一个是之前定义的 tracerawoutput_##name?我猜这个就是最后输出到trace文件的代码,你觉得呢?

好了,数据结构注册完了,接下来是什么呢?

注册traceeventcall

在上一节中,我们看到了内核通过编译链接的方法找到了traceeventcall,并且将其中的traceevent注册到了系统中,现在我们来看看traceevent_call是如何注册到系统中的。

这个过程就在event_init()函数下面一点。一共有两个步骤:

添加到ftrace_events链表

添加到trace_array的events

第一步就在刚才的代码片段中listadd(&call->list, &ftraceevents),而第二步则是通过函数_traceearlyaddevents()。

__trace_early_add_events() list_for_each(call, &ftrace_events, list) __trace_early_add_new_event(call, tr)

经过这次注册,将traceeventcall和trace_array连接了起来:

创建tracefs

在使用trace工具的时候,会通过tracefs往某些文件里读写来控制ftrace。trace_event也不例外,所以我们要先来看一下tracefs的构建,为后续的代码阅读做好准备。

说起来这个过程有点绕,因为创建tracefs的地方和刚才那些注册函数不在一个地方(系统启动时)。

具体细节可以看源代码,这里解释两点:

createeventtoplevel_files 创建了和trace event相关的根目录的一些文件

eventcreatedir则是会对每一个tracearray->events上的traceevent_file调用,创建每个event的目录

而这个tracearray->events则是由, 刚才看到的函数traceearlyaddnew_event()添加的。

初始化过程的梳理

到这里估计你已经晕了,没事我自己写得也晕了。让我们来梳理一下整个初始化过程,明确一下这个注册和tracefs的创建顺序。

(1) 从特定的section中拿到traceeventcall数据结构,并注册了trace_event

(2) 将traceeventcall添加到了ftrace_events链表

(3) 将每一个traceeventcall以traceeventfile的形式添加到trace_array.events

(4) 为每一个trace_array.events创建自己的tracefs

废了这么大力气我们都做了什么呢?

关联了tracefs和traceeventfile,也就是我嗯定义的traceeventcall。

所以,每当我们操作一个tracefs文件的时候,后面就对应这相应的traceeventfile和traceeventcall了。

OK, 我们已经为tracefs的操作做好了准备,让我们来看看打开trace event选项时的动作吧。

打开事件

在查看trace文件中的事件记录前,我们需要使能这个事件。

echo 1 > events/sample-trace/enable

所以有个开关来控制事件。而当我们写这个文件的时候,触发到的内核函数就是刚才我们注册tracefs对应的ops中的eventenablewrite。

绕晕了,其实呢就是通过某种方式设置了tracepoint结构体中的funcs成员。刚才我们在traceeventcall结构体中已经看到了tracepoint结构,这次该好好看一眼了。

主角终于登场了,经过这么一顿骚操作后,我们将之前定义好的 traceeventrawevent##name挂到了tracepoint的funcs列表中。当然我还省去了重要的一步--设置key。

输出事件

终于到了最后了。之前说的都是定义和初始化,终于要看到调用的情况了。在例子中我们看到,当我们需要输出一个事件时,就会调用trace_XXX()。这次该轮到它出场了。

先来看看trace_XXX这个函数的定义,它也藏在了我们刚才宏定义的展开中,这次我们仔细看一眼

每次我们调用traceXXX()函数的时候,先检查key是否使能了,如果使能了才继续往下走。接着我们再打开DOTRACE来看看。

联系上上一小节的tracepoint结构体是不是能想到啥?对了,就是遍历tracepoint->funcs数组,然后调用它们。

好了,终于完整的看完了TRACE_EVENT的定义和使用流程。小编累了,大家也累了,今天就到这里吧。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    88

    文章

    11817

    浏览量

    219537
  • C语言
    +关注

    关注

    183

    文章

    7646

    浏览量

    146143
  • 源码
    +关注

    关注

    8

    文章

    689

    浏览量

    31505

原文标题:Linux TraceEvent - 我见过的史上最长宏定义

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    【快速温变循环】快速温变循环试验箱的“循环”之道:展科技如何定义“一个循环”

    在军工、航天、汽车电子等高端制造领域,快速温变循环试验是验证产品可靠性的核心手段。然而,“一个循环”到底如何定义?是简单的升降温,还是对速率、驻留时间、温变曲线精度、循环重复性的严苛约束?广东
    的头像 发表于 04-16 09:38 259次阅读
    【快速温变循环】快速温变循环试验箱的“循环”之道:<b class='flag-5'>宏</b>展科技如何<b class='flag-5'>定义</b>“一个循环”

    极速温变 精准可靠 —— 广东展快速温变箱的技术实力与行业应用

    电子、汽车、医疗等行业的优选测试方案。一、核心性能参数:从温域到速率,定义测试精度展快速温变箱的性能参数直接对标国际一线水平,为严苛测试提供坚实基础:·温域覆盖:
    的头像 发表于 03-28 10:08 154次阅读
    极速温变 精准可靠 —— 广东<b class='flag-5'>宏</b>展快速温变箱的技术实力与行业应用

    低成本键盘旋钮开源项目介绍

    办公切软件、调参数太繁琐?创作时控笔刷 / 缩放总找快捷键?商用键盘价格高,还难适配个性化操作需求?想自制键盘,却遇设计复杂、组装难度高的问题?
    的头像 发表于 03-25 11:09 521次阅读

    debian image-69上自定义 linux 的启动问题求解

    我构建了自定义内核,但我在加载它时遇到问题。 启动挂起: [ 1.052175] 释放未使用的内核映像 (initmem) 内存:2168K [ 1.068868] 将 /init 作为 init
    发表于 03-20 07:25

    微科技首家欧洲全资子公司正式成立

    在国家积极鼓励企业“走出去”及共建“一带一路”倡议的宏大背景下,国内功率半导体领域的领先企业——江苏微科技股份有限公司(以下简称“微科技”)今日宣布,其首家欧洲全资子公司 MacMic
    的头像 发表于 03-04 11:06 647次阅读

    C语言中实现函数的三种方式

    1. 函数介绍 函数,即包含多条语句的定义,其通常为某一被频繁调用的功能的语句封装,且不想通过函数方式封装来降低额外的弹栈压栈开销。 函数
    发表于 12-29 07:34

    泰科技与ADI正式签署合作备忘录

    近日,南京泰半导体科技股份有限公司(以下简称“泰科技”)与全球领先的高性能半导体公司ADI正式签署合作备忘录,双方将在半导体测试与精密信号测量领域展开持续深度合作,共同推动高性能测试系统的技术创新与市场应用落地。
    的头像 发表于 12-02 09:09 1680次阅读

    Linux内核模块的加载机制

    可能会有限制。 接下来是模块的初始化。内核会执行模块的初始化函数,通常是用module_init定义的函数。这个函数负责模块的启动工作,比如注册设备驱动或文件系统。如果初始化成功,模块就被标记
    发表于 11-25 06:59

    C语言拼接运算符典型使用

    在C语言中,##运算符(称为[size=16.002px]标记拼接运算符)用于定义中将两个标记(token)拼接成一个新的标记。它在预处理阶段处理,常用于动态生成变量名、函数名或类型名,以提高代码
    发表于 11-20 08:27

    Linux史上10件最有意义的大事,你知道几件?

    个传奇。 今天,我们就带你回顾  Linux 发展史上最有意义的十件大事 ,看看它如何一步步改变了世界。 一、1991:Linus Torvalds发布第一版Linux内核 1991 年 8 月,芬兰
    的头像 发表于 10-20 11:10 523次阅读

    RT_USING_TIMER_SOFT定义是否一定要开启?

    定义 :RT_USING_TIMER_SOFT 请问:如果没有使能软件定时器的定义,只在创建定时器时,通过RT_TIMER_FLAG_SOFT_TIMER是否可以创建一个软件定
    发表于 09-29 07:11

    SConscript结果与rtconfig.h中定义相反,是什么原因呢?

    当我在rtconfig.h中把定义注释时,添加了构建 当我取消定义注释时,反而排除了构建 SConscript结果与rtconfig.h中
    发表于 09-23 06:01

    IEC 62353中常用的术语和定义

    本文详细解读IEC 62353标准中的关键术语,包括被测设备(DUT/EUT)、应用部分(B/BF/CF型)、泄漏电流、微电击与电击等定义,帮助理解医疗电气设备安全测试要求。
    的头像 发表于 07-29 17:27 872次阅读

    Vicor助力发打造主动悬架电源系统

    厦门发电声股份有限公司(发)打造业内性能卓越的主动悬架电源系统,旨在将长期以来仅见于豪华车型的功能引入中端车型。发成功突破困扰知名汽车技术供应商几十年的技术瓶颈,在满足主动悬架系统对尺寸、重量及瞬态性能的严苛需求的同时,兼
    的头像 发表于 06-04 15:24 1327次阅读

    5类网线最长传输距离是多少

    5类网线的最长传输距离在理论上为100米,但在实际应用中,其有效传输距离通常不超过80米。以下是具体分析: 理论传输距离 国际标准:5类网线的理论最大传输距离基于国际标准(如TIA/EIA-568
    的头像 发表于 05-28 09:32 3047次阅读