QSPY应用构建、调用及数据处理全解析
1. 从源码构建QSPY应用
QSPY的源代码采用可移植的C++编写,并且已经提供了针对Windows和Linux的移植版本。QSPY主机应用程序通过头文件
<qp>\qpc\include\qs.h
与QS目标组件相连接,该头文件列举了预定义的QS记录。
1.1 使用Visual C++ 2005为Windows构建QSPY
QSPY应用程序的Win32可执行文件位于
<qp>\qpc\tools\qspy\win32\vc2005\Release\qspy.exe
。这个可执行文件可以在任何32位Windows系统上运行。
若要重新构建该应用程序,
<qp>\qpc\tools\qspy\win32\vc2005\
目录中包含了用于构建QSPY应用程序的Microsoft Visual C++ 2005解决方案文件
qspy.sln
。你只需将该解决方案文件加载到Visual C++ 2005 IDE中,然后按下F7即可开始构建。
1.2 使用MinGW为Windows构建QSPY
你也可以使用从
www.mingw.org
获得的开源MinGW(Minimalist GNU for Windows)工具集来构建QSPY可执行文件。
<qp>\qpc\tools\qspy\win32\mingw\
目录中包含一个简单的批处理文件
make.bat
用于构建QSPY应用程序。你可能需要修改批处理文件顶部的
MINGW
符号定义,使其指向你安装MinGW工具集的位置。默认情况下,
make.bat
会在
<qp>\qpc\tools\qspy\win32\mingw\dbg\
目录中生成应用程序的调试版本。若要生成发布版本,可在
make.bat
脚本中添加
rel
参数(
make rel
),发布版本将生成在
<qp>\qpc\tools\qspy\win32\mingw\rel\
目录中。
1.3 为Linux构建QSPY
<qp>\qpc\tools\qspy\linux\gnu\
目录中包含了用于为Linux构建QSPY的Makefile。默认情况下,Makefile会在
<qp>\qpc\tools\qspy\linux\gnu\dbg\
目录中生成应用程序的调试版本。若要生成发布版本,可在
make
命令中添加
rel
目标(
make rel
),发布版本将生成在
<qp>\qpc\tools\qspy\linux\gnu\rel\
目录中。
2. 调用QSPY
QSPY主机应用程序旨在与所有可能的目标CPU和数据链路配合使用,这就需要具备广泛的可配置性。例如,对于任何给定的目标CPU,QSPY应用程序必须“知道”对象指针、函数指针、事件信号、时间戳大小等的大小。你可以通过命令行参数将这些信息提供给QSPY,这些参数总结在表1中,也可以在“QSPY参考手册”中找到。需要注意的是,这些选项是区分大小写的。
| 选项 | 示例 | 默认值 | 必须匹配的QP宏(QP端口头文件) | 注释 |
|---|---|---|---|---|
| -h | -h | 无 | 无 | 帮助;打印选项摘要 |
| -q | -q | 无 | 无 | 安静模式(无标准输出) |
| -o | -o qs.txt | 无 | 无 | 生成输出到指定文件 |
| -s | -s qs.spy | 无 | 无 | 将二进制输入保存到指定文件;与 -f 不兼容 |
| -m | -m qs.mat | 无 | 无 | 生成MATLAB输出到指定文件 |
| -c | -c COM2 | COM1 | 无 | COM端口选择;与 –t, -p, -f 不兼容 |
| -b | -b 115200 | 38400 | 无 | 波特率选择;与 –t, -p, -f 不兼容 |
| -t | -t | 无 | 无 | TCP/IP输入选择;与 –c, -b, -f 不兼容 |
| -p | -p 6602 | 6601 | 无 | TCP/IP服务器端口号;与 –c, -b, -f 不兼容 |
| -f | -f qs.spy | 无 | 无 | 文件输入选择;与 –c, -b, -t, -p 不兼容 |
| -T | -T 2 | 4 | QS_TIME_SIZE (qs_port.h) | 时间戳大小(字节);有效值:1, 2, 4 |
| -O | -O 2 | 4 | QS_OBJ_PTR_SIZE (qs_port.h) | 对象指针大小(字节);有效值:1, 2, 4 |
| -F | -F 2 | 4 | QS_FUN_PTR_SIZE (qs_port.h) | 函数指针大小(字节);有效值:1, 2, 4 |
| -S | -S 2 | 1 | Q_SIGNAL_SIZE (qep_port.h) | 信号大小(字节);有效值:1, 2, 4 |
| -E | -E 1 | 2 | QF_EVENT_SIZ_SIZE (qf_port.h) | 事件大小(字节);有效值:1, 2, 4 |
| -Q | -Q 1 | 2 | QF_EQUEUE_CTR_SIZE (qf_port.h) | 队列计数器大小(字节);有效值:1, 2, 4 |
| -P | -P 4 | 2 | QF_MPOOL_CTR_SIZE (qf_port.h) | 池计数器大小(字节);有效值:1, 2, 4 |
| -B | -B 1 | 2 | QF_MPOOL_SIZ_SIZE (qf_port.h) | 块大小(字节);有效值:1, 2, 4 |
| -C | -C 4 | 2 | QF_TIMEEVT_CTR_SIZE (qf_port.h) | 时间事件计数器大小;有效值:1, 2, 4 |
在调用QSPY时,你主要需要确保其与你所使用的目标系统完全匹配。表1的第四列列出了目标系统使用的配置宏以及定义这些宏的特定于平台的QP头文件。只有当QP宏与默认值不同时,你才需要使用相应的QSPY命令行选项。QSPY假定的默认值与QP中使用的默认值是一致的。
需要注意的是,当QSPY主机应用程序与QS目标组件不匹配时,QSPY应用程序将无法正确解析不匹配的跟踪记录,并会开始生成以下错误:
********** 028: Error xx bytes unparsed
********** 014: Error -yy bytes unparsed
错误前面的数字表示无法解析的跟踪记录的记录ID。
3. 将跟踪数据导出到MATLAB
QSPY主机应用程序还可以将跟踪数据导出到MATLAB,MATLAB是一种流行的数值计算环境和高级技术编程语言。由The MathWorks, Inc.创建的MATLAB允许轻松操作和绘制以矩阵形式表示的数据。
3.1 使用MATLAB分析跟踪数据
当你使用
-m <文件名>
选项调用QSPY时,QSPY应用程序除了生成前面讨论的人类可读格式外,还会生成一个指定名称的MATLAB可读文件。
MATLAB输出文件是一个ASCII文件,其中包含所有为MATLAB格式化的跟踪记录。然而,MATLAB文件中的各种跟踪记录仍然保持在目标中生成时的顺序,尚未形成合适的MATLAB矩阵,而矩阵是MATLAB中表示数据的最自然方式。
你可以在
<qp>\qpc\examples\80x86\qk\tcpp101\l\dpp\dpp.mat
文件中找到一个QSPY MATLAB输出的示例。
<qp>\qpc\tools\qspy\matlab\
目录中包含MATLAB脚本
qspy.m
,该脚本用于读取QSPY MATLAB文件并将数据转换为当前工作区中的多个MATLAB矩阵。假设
<qp>\qpc\tools\qspy\matlab\
目录已包含在MATLAB路径中,你可以从MATLAB命令窗口按以下方式调用该脚本:
Q_FILE='<qp>\qpc\examples\80x86\qk\tcpp101\l\dpp\dpp.mat'; qspy
变量
Q_FILE
被设置为QSPY MATLAB文件的文件名。需要注意的是,
qspy.m
脚本故意不是一个MATLAB函数,因为其主要目的是在脚本完成后在工作区中保留新创建的数据,以便你可以用于进一步的计算(MATLAB函数在单独的工作区中运行,函数返回后该工作区将消失)。
填充矩阵后,
qspy.m
脚本会执行
whos
命令以显示创建的对象。前缀为
Q_
的矩阵包含按时间顺序排列的跟踪数据。所有MATLAB矩阵的文档都可以在“QSPY参考手册”中找到。
3.2 MATLAB输出文件
QSPY MATLAB文件采用ASCII格式,下面是一个从DPP应用程序生成的QSPY MATLAB文件的片段示例:
62 Philo_initial= 308543675;
62 Philo_thinking= 308544589;
62 Philo_hungry= 308544835;
62 Philo_eating= 308545073;
60 HUNGRY_SIG=[
8
382343546];
60 TIMEOUT_SIG=[
10
382343546];
12
0
4
382343546
3
382343546
337510406 308544589
...
QSPY MATLAB文件以可移植的ASCII格式存储,以实现跨平台的可移植性,但实际上并不适合人类阅读。这个示例只是为了说明数据主要是数值,唯一的例外是“字典”条目,它们实际上以MATLAB命令的形式存储。
3.3 MATLAB脚本qspy.m
MATLAB脚本
qspy.m
位于
<qp>\qpc\tools\qspy\matlab\
目录中,其设计目的是读取QSPY MATLAB文件并将不同的记录分类到各种MATLAB矩阵中,以便进行后续分析。以下是
qspy.m
脚本的代码:
% the string Q_FILE must be defined
fid = fopen(Q_FILE, 'r');
if fid == -1
error('file not found')
end
Q_STATE = []; % sate entry/exit, init, tran, internal tran, ignored
Q_EQUEUE = []; % QEQueue
Q_MPOOL = []; % QMPool
Q_NEW = []; % new/gc
Q_ACTIVE = []; % active add/remove, subscribe/unsubscribe
Q_PUB = []; % publish/publish attempt
Q_TIME = []; % time event arm/disarm/rearm, clock tick
Q_INT_LOCK = []; % interrupt locking/unlocking
Q_ISR_LOCK = []; % ISR entry/exit
Q_MUTEX = []; % QK mutex locking/unlocking
Q_SCHED = []; % QK scheduler events
Q_TOT = 0; % total number of records processed
while feof(fid) == 0
line = fgetl(fid);
Q_TOT = Q_TOT+1;
rec = sscanf(line, '%d', 1); % extract the record type
switch rec % discriminate based on the record type
% QEP trace records
case 1 % QS_QEP_STATE_ENTRY
Q_STATE(size(Q_STATE,1)+1,:) = ...
[NaN 1 sscanf(line, '%*u %u %u')' NaN 1];
case 2 % QS_QEP_STATE_EXIT
Q_STATE(size(Q_STATE,1)+1,:) = ...
[NaN 2 sscanf(line, '%*u %u %u')' NaN 1];
case 3 % QS_QEP_STATE_INIT
Q_STATE(size(Q_STATE,1)+1,:) = ...
[NaN 3 sscanf(line, '%*u %u %u %u')' 1];
case 4 % QS_QEP_INIT_TRAN
tmp = sscanf(line, '%*u %u %u %u')';
Q_STATE(size(Q_STATE,1)+1,:) = ...
[tmp(1) 3 tmp(2) NaN tmp(3) 1];
case 5 % QS_QEP_INTERN_TRAN
Q_STATE(size(Q_STATE,1)+1,:) = ...
[sscanf(line, '%*u %u %u %u %u')' NaN 1];
case 6 % QS_QEP_TRAN
Q_STATE(size(Q_STATE,1)+1,:) = ...
[sscanf(line, '%*u %u %u %u %u')' 1];
case 7 % QS_QEP_IGNORED
Q_STATE(size(Q_STATE,1)+1,:) = ...
[sscanf(line, '%*u %u %u %u %u')' NaN 0];
% QF trace records
case 10 % QS_QF_ACTIVE_ADD
tmp = sscanf(line,'%*u %u %u %u %u')';
Q_ACTIVE(size(Q_ACTIVE,1)+1,:) = [tmp(1) NaN tmp(2) tmp(3) 1];
% Miscallaneous QS records
case 60 % QS_SIG_DICTIONARY
eval(line(5:end));
case 61 % QS_OBJ_DICTIONARY
eval(line(5:end));
case 62 % QS_FUN_DICTIONARY
eval(line(5:end));
% User records
% ...
end
end
% cleanup ...
fclose(fid);
clear fid;
clear line;
clear rec;
clear tmp;
% display status information...
Q_TOT
whos
该脚本的主要步骤如下:
1. 确保变量
Q_FILE
已定义,并打开该文件。
2. 初始化各种MATLAB矩阵,用于存储不同类型的跟踪记录。
3. 逐行读取MATLAB文件,提取每行的记录类型。
4. 根据记录类型,将数据存储到相应的MATLAB矩阵中。
5. 清理临时数据并显示处理的记录总数和当前工作区的内容。
3.4 MATLAB矩阵生成
qspy.m
脚本会生成11个MATLAB矩阵,每个矩阵包含不同组相关的QS跟踪记录。这里以
Q_STATE
矩阵为例,它存储了系统中与状态机活动相关的所有QS记录。以下是
Q_STATE
矩阵的存储方式总结:
| MATLAB索引 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| QS记录 | 时间戳 | 信号 | 状态机对象 | 源状态 | 新状态 | 事件处理 |
| QS_QEP_STATE_ENTRY | NaN | 1 | √ | 2 | NaN | 1 |
| QS_QEP_STATE_EXIT | NaN | 2 | √ | 2 | NaN | 1 |
| QS_QEP_STATE_INIT | NaN | 3 | √ | 2 | √ | 1 |
| QS_QEP_STATE_INIT_TRAN | √ | 3 | √ | 2 | NaN | √ |
| QS_QEP_STATE_INTERN_TRAN | √ | √ | 1 | √ | 2 | √ |
| QS_QEP_STATE_TRAN | √ | √ | 1 | √ | 2 | √ |
| QS_QEP_STATE_IGNORED | √ | √ | 1 | √ | 2 | √ |
通过以下索引矩阵可以从
Q_STATE
矩阵中明确选择特定的QS跟踪记录:
| QS记录 | MATLAB索引矩阵 |
| — | — |
| QS_QEP_STATE_ENTRY | Q_STATE(:,2) == 1 |
| QS_QEP_STATE_EXIT | Q_STATE(:,2) == 2 |
| QS_QEP_STATE_INIT | Q_STATE(:,2) == 3 |
| QS_QEP_STATE_INIT_TRAN | isnan(Q_STATE(:,4)) |
| QS_QEP_STATE_INTERN_TRAN | Q_STATE(:,2) > 3 & isnan(Q_STATE(:,5)) |
| QS_QEP_STATE_TRAN | Q_STATE(:,2) > 3 & ~isnan(Q_STATE(:,5)) |
| QS_QEP_STATE_IGNORED | ~Q_STATE(:,6) |
作为使用
Q_STATE
矩阵中信息的一个示例,以下是生成哲学家活动对象时序图的
philo_timing.m
脚本:
t=Q_STATE(:,2)>3 & ~isnan(Q_STATE(:,5)); % QS_QEP_STATE_TRAN
o=Q_STATE(:,3) == l_philo_0_;
subplot(5,1,1); stairs(Q_STATE(o & t,1),Q_STATE(o & t,5),'r')
o=Q_STATE(:,3) == l_philo_1_;
subplot(5,1,2); stairs(Q_STATE(o & t,1),Q_STATE(o & t,5),'r')
o=Q_STATE(:,3) == l_philo_2_;
subplot(5,1,3); stairs(Q_STATE(o & t,1),Q_STATE(o & t,5),'r')
o=Q_STATE(:,3) == l_philo_3_;
subplot(5,1,4); stairs(Q_STATE(o & t,1),Q_STATE(o & t,5),'r')
o=Q_STATE(:,3) == l_philo_4_;
subplot(5,1,5); stairs(Q_STATE(o & t,1),Q_STATE(o & t,5),'r')
xlabel('time stamp'); zoom on
该脚本的主要步骤如下:
1. 使用索引矩阵
t
选择
Q_STATE
矩阵中与状态转换对应的行。
2. 使用索引矩阵
o
选择特定哲学家的状态机对象。
3. 为每个哲学家绘制时序图。
显然,这个简短的演示只是触及了可能性的表面。有关其他MATLAB矩阵的描述,请参考“QSPY参考手册”。
4. 向QP应用程序添加QS软件跟踪
接下来将展示如何向DPP应用程序添加QS软件跟踪,此应用程序之前已作为软件跟踪会话的示例。
4.1 DPP示例概述
80x86搭配QK抢占式内核的DPP示例,展示了QS在所有QP组件(QEP、QF和QK)中的使用。该示例位于
<qp>\qpc\examples\80x86\qk\tcpp101\l\dpp\
目录。你可以通过将DPP - SPY.PRJ项目加载到Turbo C++ IDE中来重新构建“Spy”配置。此应用程序会链接到位于
<qp>\qpc\ports\80x86\qk\tcpp101\l\spy\
目录的“Spy”版本的QP库。该示例演示了QS设置的各个方面,特别是如何通过串口(UART)发送QS跟踪数据,以及如何使用每个基于x86的PC中都有的8254定时器/计数器以亚微秒精度为QS记录添加时间戳。
4.2 初始化QS并设置过滤器
以下是初始化QS、设置过滤器以及生成字典条目的代码(文件
<qp>\qpc\examples\80x86\qk\tcpp101\l\dpp\main.c
):
#include "qp_port.h"
#include "dpp.h"
#include "bsp.h"
/* Local-scope objects --------------------------------------------------*/
static QEvent const *l_tableQueueSto[N_PHILO];
static QEvent const *l_philoQueueSto[N_PHILO][N_PHILO];
static QSubscrList l_subscrSto[MAX_PUB_SIG];
static union SmallEvent {
void *min_size;
TableEvt te;
/* other event types to go into this pool */
} l_smlPoolSto[2*N_PHILO];
/* storage for the small event pool */
/*......................................................................*/
int main(int argc, char *argv[]) {
uint8_t n;
Philo_ctor();
/* instantiate all Philosopher active objects */
Table_ctor();
/* instantiate the Table active object */
BSP_init(argc, argv);
/* initialize the BSP (including QS) */
QF_init();
/* initialize the framework and the underlying RT kernel */
/* setup the QS filters ... */
QS_FILTER_ON (QS_ALL_RECORDS);
QS_FILTER_OFF(QS_QF_INT_LOCK);
QS_FILTER_OFF(QS_QF_INT_UNLOCK);
QS_FILTER_OFF(QS_QK_SCHEDULE);
/* provide object dictionaries... */
QS_OBJ_DICTIONARY(l_smlPoolSto);
QS_OBJ_DICTIONARY(l_tableQueueSto);
QS_OBJ_DICTIONARY(l_philoQueueSto[0]);
QS_OBJ_DICTIONARY(l_philoQueueSto[1]);
QS_OBJ_DICTIONARY(l_philoQueueSto[2]);
QS_OBJ_DICTIONARY(l_philoQueueSto[3]);
QS_OBJ_DICTIONARY(l_philoQueueSto[4]);
QF_psInit(l_subscrSto, Q_DIM(l_subscrSto));
/* init publish-subscribe */
/* initialize event pools... */
QF_poolInit(l_smlPoolSto, sizeof(l_smlPoolSto), sizeof(l_smlPoolSto[0]));
for (n = 0; n < N_PHILO; ++n) {
/* start the active objects... */
QActive_start(AO_Philo[n], (uint8_t)(n + 1),
l_philoQueueSto[n], Q_DIM(l_philoQueueSto[n]),
(void *)0, 0, (QEvent *)0);
}
QActive_start(AO_Table, (uint8_t)(N_PHILO + 1),
l_tableQueueSto, Q_DIM(l_tableQueueSto),
(void *)0, 0, (QEvent *)0);
QF_run();
/* run the QF application */
return 0;
}
代码的主要步骤如下:
1.
包含必要的头文件
:引入
qp_port.h
、
dpp.h
和
bsp.h
头文件。当启用QS跟踪(即定义了宏
Q_SPY
)时,
qp_port.h
会包含QS活动接口
qs.h
。
2.
初始化对象
:实例化所有哲学家活动对象和表活动对象。
3.
初始化BSP和QF
:调用
BSP_init
初始化板级支持包(同时也会初始化QS),调用
QF_init
初始化框架和底层实时内核。
4.
设置QS过滤器
:
- 启用所有全局过滤器:
QS_FILTER_ON (QS_ALL_RECORDS)
。
- 禁用高流量的跟踪记录:
QS_FILTER_OFF(QS_QF_INT_LOCK)
、
QS_FILTER_OFF(QS_QF_INT_UNLOCK)
、
QS_FILTER_OFF(QS_QK_SCHEDULE)
,以避免QS跟踪缓冲区溢出。
5.
提供对象字典
:使用
QS_OBJ_DICTIONARY
为相关对象提供字典条目。
6.
初始化发布 - 订阅和事件池
:调用
QF_psInit
初始化发布 - 订阅机制,调用
QF_poolInit
初始化事件池。
7.
启动活动对象
:通过
QActive_start
启动所有哲学家活动对象和表活动对象。
8.
运行应用程序
:调用
QF_run
运行QF应用程序。
4.3 总结与展望
通过上述步骤,我们详细了解了QSPY应用程序的构建、调用、跟踪数据导出到MATLAB以及向QP应用程序添加QS软件跟踪的方法。
在构建QSPY应用程序时,针对不同的操作系统和开发环境,提供了多种构建方式,如使用Visual C++ 2005、MinGW为Windows构建,以及为Linux构建。调用QSPY时,需要注意命令行参数的使用,确保与目标系统匹配,避免出现解析错误。将跟踪数据导出到MATLAB后,可以利用MATLAB强大的数据分析和可视化功能,对系统的运行状态进行深入分析。向QP应用程序添加QS软件跟踪时,需要正确初始化QS并设置过滤器,以确保跟踪数据的有效性和准确性。
未来,我们可以进一步探索如何优化QS软件跟踪的配置,以减少对系统性能的影响。同时,可以深入研究MATLAB矩阵的使用,挖掘更多潜在的分析方法,为系统的调试和优化提供更有力的支持。此外,随着技术的不断发展,还可以考虑将QS软件跟踪与其他工具和技术相结合,以满足更复杂的应用场景需求。
总之,掌握QSPY和QS软件跟踪技术,能够帮助我们更好地理解和调试事件驱动系统,提高系统的可靠性和性能。希望本文的内容能够为相关开发者提供有价值的参考,促进事件驱动系统开发技术的不断进步。
超级会员免费看
109

被折叠的 条评论
为什么被折叠?



