threadx的trace源码分析--Apple的学习笔记
一,前言
之前做了基于Segger RTT的上下位机,来测试os的task调度。我向来对整体系统(上下位机的通信很感兴趣)知道了RTT的原理,所以就进行了资源利用,后来底层我改成了自己写的精简FIFO,也很好用。那么threadx的traceX模块设计是否也能为我所用呢!
二,带着问题看源码
1. threadx中的traceX模块的上下位机设计原理和RTT一样吗?
答:不一样。我看了Azure RTOS TraceX的使用,它描述是转储(将内存数据转为s19等上位机工具支持的格式),而且是事后查看。于是我去看了单片机端的code,就找到了TX_TRACE_IN_LINE_INSERT。也就是FIFO进入的函数,没有找到退出的函数,且用循环覆盖来实现环形FIFO。所以我理解上位机是不支持实时查看的,等于单片机一直把log放入FIFO内存,等用户暂停调试单片机的时候,可以将内存取出转储后,通过上位机来查看切换情况。
2.TX_TRACE_IN_LINE_INSERT入队列中怎么没有开关中断?
答:RTT主要用来实时打印及和上位机交互数据的(其实是用来代替串口的),所用不同优先级的task会去调用它的写数据函数,就存在一个是否函数可重入的问题,由于多个task用一个资源,那么就需要用到互斥,也就是要加开关全局中断的。当然,全局开关中断也不能加太多,否则会影响整体系统运行,但是不添加的话,可能打印的log准确性可能会有问题,这在使用的时候可以综合考虑。
threadx的trace log功能仅仅是向内存写入事件切换的时间戳及相关对象的4个附带信息。那么若仅在切换的时候写入,或者说是os 切换各种事件的时候写入,那么我理解不在task或中断运行时候写入就不存在打断问题,因为所有打断都是os切换导致的,若仅在os切换事件的时候写入,就可以不用添加开关中断。
3.循环队列成员为什么都用指针
答:循环队列之前了解的比较多。设计时候结构体用数组的比较多,单链表方式也有,里面特别的元素成员就是size和溢出flag。这里的设计直接用end和start地址,所以省略了size。关于溢出的判断也变简单了,就是start==end
地址说明溢出了。我理解这是不同的用途导致FIFO设计时候使用的结构体也设计的不同,因为此trace功能只有FIFO输入没有输出,所有size成员用途不大。稍微功能有点点区别,就可以考虑更为优化的设计了。设计真是千变万化呢,这就是编码吸引我的地方,可以自由创作,然后寻找最优方案。
三,traceX底层源码分析
官网先看了help,也把traceX上位机软件打开对照字段看了下。还是很容易理解它的设计的。主的一个结构体对象中,包括了obj和buffer成员。
为什么要obj和buffer2个成员呢?原因就是buffer的后面4个u32字节为每个obj成员特有的,所以trace对象就包括了obj和buffer2个成员,buffer可以引用obj的数据。上位机用的就是buffer成员。
最简单的看代码的方法就是先看数据结构,因为数据解构决定算法设计。
/* Define the an Trace Buffer Entry. */
typedef struct TX_TRACE_BUFFER_ENTRY_STRUCT
{
ULONG tx_trace_buffer_entry_thread_pointer;
ULONG tx_trace_buffer_entry_thread_priority;
ULONG tx_trace_buffer_entry_event_id;
ULONG tx_trace_buffer_entry_time_stamp;
#ifdef TX_MISRA_ENABLE
ULONG tx_trace_buffer_entry_info_1;
ULONG tx_trace_buffer_entry_info_2;
ULONG tx_trace_buffer_entry_info_3;
ULONG tx_trace_buffer_entry_info_4;
#else
ULONG tx_trace_buffer_entry_information_field_1;
ULONG tx_trace_buffer_entry_information_field_2;
ULONG tx_trace_buffer_entry_information_field_3;
ULONG tx_trace_buffer_entry_information_field_4;
#endif
} TX_TRACE_BUFFER_ENTRY;
traceX的上位机截图.png
代码的结构体和上位机的截图前8个完全对应,所以就是从单片机获取的数据。对应的4个不同内容也有注释,比如queue对象,就有queue prt,destination ptr,wait option和enqueued和上位机一致。
/* I1 = queue ptr, I2 = destination ptr, I3 = wait option, I4 = enqueued */
#define TX_TRACE_QUEUE_RECEIVE 68
/* I1 = queue ptr, I2 = source ptr, I3 = wait option, I4 = enqueued */
#define TX_TRACE_QUEUE_SEND 69
再看看源码中TX_TRACE_IN_LINE_INSERT宏函数,赋值8个字段,全部可以对应上。
trace_event_ptr -> tx_trace_buffer_entry_thread_pointer = (ULONG) trace_thread_ptr; \
trace_event_ptr -> tx_trace_buffer_entry_thread_priority = (ULONG) trace_priority; \
trace_event_ptr -> tx_trace_buffer_entry_event_id = (ULONG) (i); \
trace_event_ptr -> tx_trace_buffer_entry_time_stamp = (ULONG) TX_TRACE_TIME_SOURCE; \
TX_TRACE_INFO_FIELD_ASSIGNMENT((a),(b),(c),(d)) \
我之前说它是FIFO且覆盖方式代码如下,完成current的指针赋值8个字段后,就指向下一个空闲内容,且判断下一个空闲内容是否为end,若为end就指向start。
trace_event_ptr++; \
if (trace_event_ptr >= _tx_trace_buffer_end_ptr) \
{ \
trace_event_ptr = _tx_trace_buffer_start_ptr; \
_tx_trace_buffer_current_ptr = trace_event_ptr; \
_tx_trace_header_ptr -> tx_trace_header_buffer_current_pointer = (ULONG) trace_event_ptr; \
if (_tx_trace_full_notify_function) \
(_tx_trace_full_notify_function)((VOID *) _tx_trace_header_ptr); \
} \
else \
{ \
_tx_trace_buffer_current_ptr = trace_event_ptr; \
_tx_trace_header_ptr -> tx_trace_header_buffer_current_pointer = (ULONG) trace_event_ptr; \
} \
这里可圈可点的内容如下,就是刚进入函数时候的if判断是否使能。而且用的是mask对象的方式,我之前自己设计没有考虑到此项,这样就可以实现通过宏定义来配置使能某些想观察的对象。而非控制所有,可以用来调整的颗粒度为obj对象。这是本次阅读源码设计最大的收获。
#define TX_TRACE_IN_LINE_INSERT(i,a,b,c,d,e) \
{ \
TX_TRACE_BUFFER_ENTRY *trace_event_ptr; \
ULONG trace_system_state; \
ULONG trace_priority; \
TX_THREAD *trace_thread_ptr; \
trace_event_ptr = _tx_trace_buffer_current_ptr; \
if ((trace_event_ptr) && (_tx_trace_event_enable_bits & ((ULONG) (e)))) \
{ \
...
} \
四,小结
同样的模板字段可以输出不同的含义内容,这就是buffer复用的思想。这样就可以使用同一的框架,这样的设计思想我也可以借鉴。另外,对不同对象进行mask使能的方式也很有用。哈哈,收获颇丰~