软件追踪系统中的 QS 与 QSPY 应用详解
在软件开发过程中,软件追踪系统对于调试和性能分析至关重要。本文将详细介绍 QS(Quantum Spy)软件追踪系统的相关接口、字典记录、应用特定记录、移植配置,以及 QSPY 主机应用的相关内容。
1. 面向块的接口:QS_getBlock()
QS 提供了一种面向块的接口
QS_getBlock()
,用于一次性获取连续的数据块,这对于 DMA 类型的传输非常有用。
-
函数行为 :
-
若调用时存在可用字节,函数返回 QS 跟踪缓冲区中数据块起始位置的指针,并将连续字节数写入
pNbytes所指向的位置。*pNbytes的值也作为输入,限制调用者可接受的数据块最大大小,且字节不会从跟踪缓冲区复制。 -
若调用时 QS 缓冲区中无可用字节,函数返回
NULL指针,并将pNbytes指向的值设为 0。 -
当
QS_getBlock()返回的字节数少于*pNbytes的初始值时,不能假定 QS 跟踪缓冲区为空,可能数据块接近跟踪缓冲区末尾,需再次调用QS_getBlock()获取剩余数据。 -
调用者在
QS_getBlock()返回内存块后,必须传输完返回块中的所有字节,才能再次调用该函数。
-
若调用时存在可用字节,函数返回 QS 跟踪缓冲区中数据块起始位置的指针,并将连续字节数写入
-
注意事项 :
QS_getBlock()函数内部不锁定中断,且不可重入,应在锁定中断的情况下调用。
以下是使用
QS_getBlock()
将数据输出到 16550 兼容 UART 的示例代码:
void QF_onIdle(void) {
/* called with interrupts LOCKED */
QF_INT_UNLOCK(dummy);
/* always unlock interrupts */
#ifdef Q_SPY
if ((inportb(l_uart_base + 5) & (1 << 5)) != 0) {
/* THR Empty? */
uint16_t fifo = UART_16550_TXFIFO_DEPTH;
/* 16550 Tx FIFO depth */
uint8_t const *block;
QF_INT_LOCK(dummy);
block = QS_getBlock(&fifo);
/* try to get next block to transmit */
QF_INT_UNLOCK(dummy);
while (fifo-- != 0) {
/* any bytes in the block? */
outportb(l_uart_base + 0, *block++);
}
}
#endif
}
操作步骤如下:
1. 空闲处理是实现跟踪数据输出的理想方式,使用
QF_onIdle()
空闲回调。
2.
QF_onIdle()
回调在锁定中断的情况下调用,需先解锁中断。
3. 仅当
Q_SPY
宏定义时,才执行 QS 跟踪缓冲区输出。
4. 检查 16550 UART 的发送保持寄存器(THR)是否为空。
5. 获取 16550 UART 的 Tx FIFO 深度。
6. 定义临时指针
block
用于保存
QS_getBlock()
的返回值。
7. 锁定中断后调用
QS_getBlock()
获取要传输的连续跟踪数据块。
8. 解锁中断。
9. 当 Tx FIFO 有空间时,将跟踪字节写入发送保持寄存器。
2. 字典跟踪记录
在编译和加载应用程序映像到目标设备时,对象名、函数名和信号名等符号信息会从代码中剥离。为了让 QSPY 主机组件获取这些符号信息,QS 提供了特殊的跟踪记录。
- 作用 :字典跟踪记录并非生成跟踪所必需,但符号信息的存在能显著提高软件跟踪或调试器的使用效率。
-
类型 :QS 支持三种类型的字典跟踪记录,分别是对象字典、函数字典和信号字典。
字典类型 生成宏 作用 对象字典 QS_OBJ_DICTIONARY()将对象在内存中的地址与其符号名关联 函数字典 QS_FUN_DICTIONARY()将函数在内存中的地址与其符号名关联 信号字典 QS_SIG_DICTIONARY()将事件信号的数值和状态机对象与信号的符号名关联 -
对象字典 :
-
使用
QS_OBJ_DICTIONARY()宏生成,该宏仅接受一个参数,即对象的地址,并使用“字符串化”预处理器运算符将参数转换为 C 字符串。应使用有意义的持久对象地址调用该宏,如&l_table或&l_philo[3],而非通用指针。 - QS 通过内存池管理的内存缓冲区识别内存池,通过事件队列管理的环形缓冲区识别事件队列。
-
对象字典记录与 QS 本地过滤器密切相关,二者使用相同约定。例如,可通过
QS_FILTER_MP_OBJ()宏选择特定内存池的本地过滤器,通过QS_FILTER_EQ_OBJ()宏选择特定事件队列的本地过滤器。
-
使用
以下是对象字典所需的预定义 QS 记录示例:
| 对象类型 | 示例 | QS 记录 |
| ---- | ---- | ---- |
| 状态机 |
QS_OBJ_DICTIONARY(&l_table)
|
QS_QEP_STATE_EMPTY
,
QS_QEP_STATE_ENTRY
,
QS_QEP_STATE_EXIT
,
QS_QEP_STATE_INIT
,
QS_QEP_INIT_TRAN
,
QS_QEP_INTERN_TRAN
,
QS_QEP_TRAN
,
QS_QEP_IGNORED
|
| 活动对象 |
QS_OBJ_DICTIONARY(&l_philo[0])
|
QS_QF_ACTIVE_ADD
,
QS_QF_ACTIVE_REMOVE
,
QS_QF_ACTIVE_SUBSCRIBE
,
QS_QF_ACTIVE_UNSUBSCRIBE
,
QS_QF_ACTIVE_POST_FIFO
,
QS_QF_ACTIVE_POST_LIFO
,
QS_QF_ACTIVE_GET
,
QS_QF_ACTIVE_GET_LAST
|
| 内存池 |
QS_OBJ_DICTIONARY(l_smlPoolSto)
|
QS_QF_MPOOL_INIT
,
QS_QF_MPOOL_GET
,
QS_QF_MPOOL_PUT
|
| 事件队列 |
QS_OBJ_DICTIONARY(l_philQueueSto[0])
|
QS_QF_EQUEUE_INIT
,
QS_QF_EQUEUE_POST_FIFO
,
QS_QF_EQUEUE_POST_LIFO
,
QS_QF_EQUEUE_GET
,
QS_QF_EQUEUE_GET_LAST
|
| 时间事件 |
QS_OBJ_DICTIONARY(&l_philo[0].timeEvt)
|
QS_QF_TICK
,
QS_QF_TIMEEVT_ARM
,
QS_QF_TIMEEVT_AUTO_DISARM
,
QS_QF_TIMEEVT_DISARM_ATTEMPT
,
QS_QF_TIMEEVT_DISARM
,
QS_QF_TIMEEVT_REARM
,
QS_QF_TIMEEVT_POST
,
QS_QF_TIMEEVT_PUBLISH
|
-
函数字典 :使用
QS_FUN_DICTIONARY()宏生成,该宏仅接受一个参数,即函数的地址,并使用“字符串化”预处理器运算符将参数转换为 C 字符串,主要为状态处理函数提供符号名。 -
信号字典 :使用
QS_SIG_DICTIONARY()宏生成,该宏接受两个参数:事件信号的数值和状态机对象的地址。使用信号值和状态机对象是因为仅信号值不足以唯一标识符号信号,全局发布的信号需系统唯一,局部使用的信号在不同状态机中可能有不同含义。
以下是使用
QS_SIG_DICTIONARY()
宏的示例:
// 全局发布信号示例
QS_SIG_DICTIONARY(GLOBAL_SIG_1, NULL);
QS_SIG_DICTIONARY(GLOBAL_SIG_2, NULL);
// 局部信号示例
QS_SIG_DICTIONARY(TIMEOUT_SIG, &Philosopher);
3. 应用特定的 QS 跟踪记录
应用特定的 QS 记录允许从应用程序级代码生成跟踪信息,可将其视为
printf()
的等效替代,但开销更小。
-
记录结构
:
-
多数情况下,应用特定记录使用
QS_BEGIN()/QS_END()宏对封装,这对宏在记录开始时锁定中断,结束时解锁中断。 -
若要在已建立的关键部分或 ISR 中生成跟踪数据,可使用
QS_BEGIN_NOLOCK()/QS_END_NOLOCK()宏避免关键部分嵌套。 -
QS_BEGIN()宏接受两个参数:第一个参数是枚举记录类型,用于全局开关过滤器,且是每个记录头的一部分,应用特定记录类型必须从QS_USER值开始,以避免与 QP 组件中已有的预定义 QS 记录重叠;第二个参数用于本地过滤器,可选择性记录特定应用级对象。若不想使用本地过滤器,可将NULL作为第二个参数。
-
多数情况下,应用特定记录使用
以下是一个应用特定 QS 记录的示例:
QS_BEGIN(MY_QS_RECORD, myObjectPointer)
/* trace record begin */
QS_STR("Hello");
/* string data element */
QS_U8(3, n);
/* uint8_t data, 3-decimal digits format */
...
/* QS data */
QS_MEM(buf, sizeof(buf));
/* memory block of a given size */
QS_END()
/* trace record end */
-
数据元素 :支持的数据元素包括 8 位、16 位和 32 位的有符号和无符号整数、32 位和 64 位的浮点数、以零结尾的字符串和可变大小的内存块。还提供了特殊宏用于插入平台相关元素,如事件信号、对象指针和函数指针,QS 仅记录给定平台所需的最小字节数。
-
编码结构 :应用特定跟踪记录与所有 QS 记录一样,以序列号和记录 ID 开头,紧随记录 ID 的是时间戳,时间戳使用的字节数可通过
QS_TIME_SIZE宏配置。每个数据元素以格式字节开头,包含数据类型信息(低半字节)和显示元素的格式宽度(高半字节)。
以下是应用特定跟踪记录的编码结构示意图:
graph LR
A[Seq No.] --> B[Rec ID]
B --> C[Chk sum]
C --> D[Flag 0x7E]
D --> E[QS_TIME_SIZE]
E --> F[Time stamp]
F --> G[fmt 'H']
G --> H['e']
H --> I['l']
I --> J['l']
J --> K['o']
K --> L['\0']
L --> M[fmt n]
M --> N[...]
N --> O[fmt size]
O --> P[buf[0]]
P --> Q[buf[1]]
Q --> R[buf[2]]
R --> S[...]
操作步骤如下:
1. 使用
QS_BEGIN()
宏开始记录,指定记录类型和可选的本地过滤器对象。
2. 使用各种数据元素宏插入所需数据。
3. 使用
QS_END()
宏结束记录。
4. 移植和配置 QS
使用 QS 时,需将其适配到所选的 CPU、编译器和内核/RTOS,这称为移植。
-
移植代码组织
:QS 平台特定代码仅包含
qs_port.h和通常在应用程序的板级支持包(bsp.c)中定义的 QS 回调函数。
以下是 80x86、QK/DOS、大内存模型的
qs_port.h
头文件示例:
#ifndef qs_port_h
#define qs_port_h
#define QS_OBJ_PTR_SIZE 4
#define QS_FUN_PTR_SIZE 4
#define QS_TIME_SIZE 4
#include "qf_port.h"
/* use QS with QF */
#include "qs.h"
/* QS platform-independent public interface */
#endif
/* qs_port_h */
-
宏定义说明 :
-
QS_OBJ_PTR_SIZE:指定特定平台上对象指针的大小(字节)。 -
QS_FUN_PTR_SIZE:指定特定平台上函数指针的大小(字节)。 -
QS_TIME_SIZE:配置 QS 时间戳QSTimeCtr的大小(字节)。
-
-
注意事项 :
-
若 QS 与 QF 实时框架一起使用,需包含
qf_port.h头文件,此时 QS 关键部分与qf_port.h中定义的相同。 -
若 QS 仅与 QEP 组件一起使用或完全独立使用,需通过定义
QS_INT_KEY_TYPE、QS_INT_LOCK()和QS_INT_UNLOCK()宏提供自己的独立关键部分机制。 -
qs.h平台无关头文件必须包含在qf_port.h头文件中。
-
若 QS 与 QF 实时框架一起使用,需包含
操作步骤如下:
1. 创建
qs_port.h
头文件。
2. 根据平台需求定义
QS_OBJ_PTR_SIZE
、
QS_FUN_PTR_SIZE
和
QS_TIME_SIZE
宏。
3. 若与 QF 一起使用,包含
qf_port.h
头文件。
4. 包含
qs.h
平台无关头文件。
5. QSPY 主机应用
QSPY 是 Quantum Spy 软件追踪系统的主机端组件,是一个简单的控制台应用程序,无复杂 GUI,旨在提供 QS 数据的解析、存储和导出功能,可将数据导出到 MATLAB 等强大工具。
-
特点 :
- 平台中立,使用可移植的 C++ 编写,支持 Linux 和 Windows 等多种平台。
- 易于适配各种目标 - 主机通信链路,开箱支持串行(RS232)、TCP/IP 和文件通信链路,可通过通用硬件抽象层(HAL)轻松添加其他通信链路。
- 接受多个命令行参数,可配置数据链路和所有目标依赖项,如指针大小、信号大小等,可处理来自任何嵌入式目标的数据,已在多种 8 位、16 位或 32 位 CPU 上测试。
-
功能 :
- 提供简单的、统一的、人类可读的文本输出到屏幕。
- 若 QS 跟踪数据包含字典跟踪记录,QSPY 应用符号信息输出对象、信号和状态的标识符;否则,输出各种指针和信号的十六进制值。
- 可将跟踪数据以矩阵格式导出到 MATLAB,提供了将 QSPY 跟踪数据导入 MATLAB 的特殊脚本。
-
安装 :QSPY 主机应用程序包含在特定目录中,QP/C 和 QP/C++ 版本除包含的
qs.h头文件版本不同外,其他相同。
以下是 QSPY 主机应用程序的源代码组织:
<qp>\qpc\
- QP/C root directory (<qp>\qpcpp for QP/C++)
|
+-doxygen\
- QP/C documentation generated with Doxygen
| +-html\
- "QP/C Reference Manual" in HTML format
| | +-index.html
- The starting HTML page for the "QP/C Reference Manual"
| | |
(contains the "QSPY Reference Manual")
| | +- . . .
| +-qpc.chm
- "QP/C Reference Manual" in CHM Help format
|
(contains the "QSPY Reference Manual")
|
+-include/
- QP platform-independent header files
| +-qs.h
- QS platform-independent header file (used by QSPY)
|
+-tools\
- Tools directory
| +-qspy\
- QSPY host application
| | +-include\
- platform-independent include
| | | +-dict.h
- dictionary class header file
| | | +-getopt.h
- command-line option parser
| | | +-hal.h
- Hardware Abstraction Layer header file
| | | +-qspy.h
- QSPY parser header file
| | +-source\
- platform-independent sources (C++)
| | | +-dict.cpp
- dictionary class implementation
| | | +-getopt.c
- command-line option parser
| | | +-main.cpp
- main() entry point
| | | +-qspy.cpp
- QSpy parser
| | |
| | +-linux\
- Linux version of QSPY
| | | +-gnu\
- GNU compiler
| | | | +-dbg\
- debug build directory
| | | | +-rel\
- release build directory
| | | | +-com.cpp
- serial port HAL for Linux
| | | | +-tcp.cpp
- TCP/IP port HAL for Linux
| | | | +-Makefile - make file to build QSPY for Linux
| | |
| | +-win32\
- Win32 (Windows) version of QSPY
| | | +-mingw\
- MinGW compiler (GNU)
| | | | +-dbg\
- debug build directory
| | | | +-rel\
- release build directory
| | | | | +-qspy.exe – QSPY executable
| | | | +-com.cpp
- serial port HAL for Win32
| | | | +-tcp.cpp
- TCP/IP port HAL for Win32
| | | | +-make.bat – Simple batch script to build QSPY
| | |
| | | +-vc2005\
- Visual C++ 2005 toolset
| | | | +-Debug\
- debug build directory
| | | | +-Release\ - release build directory
| | | | | +-qspy.exe – QSPY executable
| | | | +-com.cpp
- serial port HAL for Win32
| | | | +-tcp.cpp
- TCP/IP port HAL for Win32
| | | | +-qspy.sln - Visual C++ Solution to build QSPY for Win32
| | |
| | +-matlab\
- MATLAB scripts
| | | +-qspy.m
- MATLAB script to import the QS data into MATLAB
| | | +-dpp.spy
- Example of a QS binary file from DPP application
| | | +-philo_timing.m - example MATLAB script to generate timing diagrams
| | |
for the DPP example
操作步骤如下:
1. 从指定目录获取 QSPY 主机应用程序。
2. 根据需要选择合适的版本(QP/C 或 QP/C++)。
3. 配置命令行参数,包括数据链路和目标依赖项。
4. 运行 QSPY 应用程序,解析和处理 QS 跟踪数据。
5. 若需要,将数据导出到 MATLAB 进行进一步分析。
综上所述,QS 和 QSPY 为软件开发中的调试和性能分析提供了强大的工具,通过合理使用这些功能,可以提高开发效率和软件质量。
软件追踪系统中的 QS 与 QSPY 应用详解
6. QSPY 主机应用的使用与配置
QSPY 主机应用的使用涉及多个方面,包括安装、命令行参数配置等,以下将详细介绍其操作步骤和注意事项。
-
安装步骤 :
-
找到 QSPY 主机应用程序所在目录,对于 QP/C 是
<qp>\qpc\tools\qspy\,对于 QP/C++ 是<qp>\qpcpp\tools\qspy\。 -
确认所需的依赖文件,如
qs.h头文件,根据使用的是 QP/C 还是 QP/C++ 版本选择对应的头文件。
-
找到 QSPY 主机应用程序所在目录,对于 QP/C 是
-
命令行参数配置 :QSPY 接受多个命令行参数来配置数据链路和目标依赖项,以下是一些常见参数的说明:
| 参数类型 | 说明 | 示例 |
| ---- | ---- | ---- |
| 数据链路参数 | 用于指定与目标设备的通信方式,如串口、TCP/IP 或文件 |--serial /dev/ttyS0或--tcp 192.168.1.100:1234|
| 目标依赖参数 | 配置指针大小、信号大小等目标相关信息 |--ptr-size 4或--sig-size 2|
以下是一个使用命令行启动 QSPY 的示例:
qspy --serial /dev/ttyS0 --ptr-size 4 --sig-size 2
-
数据处理与输出
:
- 运行 QSPY 应用程序后,它会开始解析从目标设备接收到的 QS 跟踪数据。
- 若跟踪数据包含字典跟踪记录,QSPY 会应用符号信息输出对象、信号和状态的标识符;否则,输出十六进制值。
- QSPY 提供简单的、统一的、人类可读的文本输出到屏幕,方便开发者查看。
-
若需要将数据导出到 MATLAB 进行进一步分析,可使用提供的
qspy.m脚本。操作步骤如下:- 确保 MATLAB 环境已正确配置。
-
在 MATLAB 中运行
qspy.m脚本,并指定 QSPY 输出的数据文件。 - 数据将以矩阵格式导入 MATLAB,可使用 MATLAB 的强大功能进行数据处理和可视化。
7. QS 与 QSPY 的应用场景与优势
QS 和 QSPY 在软件开发中具有广泛的应用场景,以下是一些常见的应用场景及它们带来的优势。
-
调试与故障排查 :
- 在开发过程中,当软件出现故障时,QS 可以记录详细的运行信息,如对象状态、函数调用、信号传递等。
- QSPY 可以解析这些记录,将其转换为人类可读的形式,帮助开发者快速定位问题所在。例如,通过查看对象字典和函数字典记录,开发者可以清晰地了解各个对象和函数的状态。
- 优势:提高调试效率,减少故障排查时间。
-
性能分析 :
- QS 可以记录软件的运行时间、资源使用等性能数据。
- QSPY 可以对这些数据进行分析和可视化,帮助开发者找出性能瓶颈。例如,通过分析时间戳和函数调用记录,开发者可以了解哪些函数执行时间过长。
- 优势:优化软件性能,提高系统响应速度。
-
系统监控 :
- 在软件运行过程中,QS 可以持续记录系统的运行状态。
- QSPY 可以实时显示这些信息,帮助开发者监控系统的健康状况。例如,通过查看事件队列的使用情况,开发者可以了解系统的负载情况。
- 优势:及时发现系统异常,保障系统稳定运行。
8. 总结与建议
通过以上对 QS 和 QSPY 的详细介绍,我们可以看到它们在软件开发中的重要作用。以下是一些总结和建议,帮助开发者更好地使用这些工具。
-
总结 :
- QS 提供了面向块的接口、字典跟踪记录、应用特定记录等功能,方便开发者记录软件运行信息。
- QSPY 作为主机应用,负责解析和处理 QS 记录,提供人类可读的输出,并支持数据导出到 MATLAB 进行进一步分析。
-
建议 :
- 在系统初始化阶段,合理生成字典记录,避免在实时运行时生成,以减少对系统性能的影响。
- 在使用 QS 记录时,注意选择合适的记录类型和数据元素,避免生成过大的记录,保持关键部分代码的简洁。
- 在配置 QSPY 时,仔细设置命令行参数,确保正确处理 QS 跟踪数据。
- 结合 MATLAB 等工具进行数据处理和可视化,充分发挥 QS 和 QSPY 的优势。
以下是使用 QS 和 QSPY 的整体流程图:
graph LR
A[软件开发] --> B[QS 记录生成]
B --> C[数据传输]
C --> D[QSPY 解析处理]
D --> E[数据输出]
E --> F[查看分析]
F --> G{是否需要进一步分析}
G -- 是 --> H[导出到 MATLAB]
G -- 否 --> I[结束]
H --> J[MATLAB 处理可视化]
J --> I
总之,QS 和 QSPY 为软件开发提供了一套完整的跟踪和分析解决方案,开发者可以根据实际需求灵活运用这些工具,提高开发效率和软件质量。在实际应用中,不断探索和总结经验,充分发挥它们的潜力,为软件开发带来更多的便利和价值。
超级会员免费看
65

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



