QK内核移植与测试:80x86平台实践
1. 引言
在嵌入式系统开发中,不同的CPU架构和编译器对内核的运行有不同的要求。一些较旧的CPU架构,如8位PIC微控制器,由于缺乏对C语言友好的栈架构,难以轻松运行QK内核。不过,在大多数情况下,可以使用非抢占式的普通内核。本文将详细介绍QK内核在DOS系统下的80x86 CPU上的移植过程,并通过就餐哲学家问题(DPP)应用程序对移植进行测试,展示QK的高级特性,如优先级上限互斥锁、多可重入库的线程本地存储(TLS)以及80x87 FPU的扩展上下文切换。
2. QK内核移植
2.1 移植概述
本次移植使用遗留的Turbo C++ 1.01编译器,配置为生成“大”内存模型的代码。移植的相关文件位于
<qp>\qpc\ports\80x86\qk\tcpp101\l\
目录下。以下是移植过程中涉及的主要头文件及其作用:
-
qep_port.h
:定义特定平台的精确宽度整数类型。
-
qf_port.h
:配置活动对象的最大数量,并包含必要的头文件。
-
qk_port.h
:实现QK内核到目标CPU/编译器的实际移植,包括中断锁定策略、QK特定的中断入口和出口处理,以及高级特性的定制。
2.2 qep_port.h头文件
该头文件为80x86/QK/Turbo C++ 1.01/大内存模型定义了精确宽度的整数类型,代码如下:
#ifndef qep_port_h
#define qep_port_h
/* Exact-width integer types for DOS/Turbo C++ 1.01/Large memory model */
typedef signed char int8_t;
typedef signed int int16_t;
typedef signed long int32_t;
typedef unsigned char uint8_t;
typedef unsigned int uint16_t;
typedef unsigned long uint32_t;
#include "qep.h"
/* QEP platform-independent public interface */
#endif
/* qep_port_h */
2.3 qf_port.h头文件
此头文件需要配置活动对象的最大数量
QF_MAX_ACTIVE
,并包含
qep_port.h
、
qk_port.h
和
qf.h
头文件,代码如下:
#ifndef qf_port_h
#define qf_port_h
/* The maximum number of active objects in the application */
#define QF_MAX_ACTIVE 63
#include "qep_port.h"
/* QEP port */
#include "qk_port.h"
/* QK port */
#include "qf.h"
/* QF platform-independent interface */
#endif
/* qf_port_h */
2.4 qk_port.h头文件
这是QK内核移植的关键头文件,主要决策包括中断锁定策略、QK特定的中断入口和出口处理,以及高级特性的定制。具体内容如下:
-
中断锁定策略
:由于标准PC配备了外部8259A可编程中断控制器(PIC),且Turbo C++ 1.01编译器提供了
disable()
和
enable()
函数,因此采用简单的“无条件中断解锁”策略。
-
QK特定的中断入口和出口
:在
QK_ISR_ENTRY()
中解锁中断,在
QK_ISR_EXIT()
中锁定中断,并调用扩展的QK调度器
QK_scheduleExt_()
处理异步抢占。
-
高级特性定制
:实现线程本地存储(TLS)和扩展上下文切换,支持80x87 FPU。
以下是
qk_port.h
头文件的代码:
#ifndef qk_port_h
#define qk_port_h
/* QF critical section entry/exit */
(1)
/* QF_INT_KEY_TYPE not defined */
(2)
#define QF_INT_LOCK(dummy) disable()
(3)
#define QF_INT_UNLOCK(dummy) enable()
/* QK-specific ISR entry and exit */
(4)
#define QK_ISR_ENTRY() do { \
++QK_intNest_; \
(5)
enable(); \
} while (0)
(6)
#define QK_ISR_EXIT() do { \
(7)
disable(); \
(8)
outportb(0x20, 0x20); \
--QK_intNest_; \
if (QK_intNest_ == 0) { \
(9)
QK_scheduleExt_(); \
} \
} while (0)
/* demonstration of advanced QK features: TLS and extended context switch */
(10)
typedef struct Lib1_contextTag {
/* an example of a library context */
double x;
} Lib1_context;
(11)
extern Lib1_context * volatile impure_ptr1;
(12)
typedef struct Lib2_contextTag {
/* an example of a library context */
double y;
} Lib2_context;
(13)
extern Lib2_context * volatile impure_ptr2;
(14)
typedef union FPU_contextTag {
uint32_t align;
(15)
uint8_t x87 [108];
/* the x87 FPU context takes 108-bytes */
} FPU_context;
(16)
typedef struct ThreadContextTag {
Lib1_context lib1;
/* library1 context */
Lib2_context lib2;
/* library2 context */
FPU_context fpu;
/* the FPU context */
} ThreadContext;
(17)
enum QKTaskFlags {
QK_LIB1_THREAD = 0x01,
QK_LIB2_THREAD = 0x02,
QK_FPU_THREAD = 0x04
};
/* QK thread-local storage */
(18)
#define QK_TLS(act_) \
(19)
impure_ptr1 = &((ThreadContext *)(act_)->thread)->lib1; \
(20)
impure_ptr2 = &((ThreadContext *)(act_)->thread)->lib2
/* QK extended context (FPU) save/restore */
(21)
#define QK_EXT_SAVE(act_) \
(22)
if (((act_)->osObject & QK_FPU_THREAD) != 0) \
(23)
FPU_save(&((ThreadContext *)(act_)->thread)->fpu)
(24)
#define QK_EXT_RESTORE(act_) \
(25)
if (((act_)->osObject & QK_FPU_THREAD) != 0) \
(26)
FPU_restore(&((ThreadContext *)(act_)->thread)->fpu)
(27)
void FPU_save(FPU_context *fpu);
/* defined in assembly */
(28)
void FPU_restore(FPU_context *fpu);
/* defined in assembly */
#include <dos.h>
/* see NOTE01 */
#undef outportb
/*don't use the macro because it has a bug in Turbo C++ 1.01*/
(29)
#include "qk.h"
/* QK platform-independent public interface */
(30)
#include "qf.h"
/* QF platform-independent public interface */
#endif
/* qk_port_h */
2.5 保存和恢复FPU上下文
FPU_save()
和
FPU_restore()
函数声明在
qk_port.h
中,定义在汇编文件
<qp>\qpc\ports\80x86\qk\tcpp101\l\src\fpu.asm
中,它们分别执行80x87机器指令
FSAVE
和
FRSTOR
来保存和恢复FPU上下文。
3. QK端口测试
3.1 测试概述
使用就餐哲学家问题(DPP)应用程序对QK端口进行测试,扩展后的DPP应用程序用于演示和测试QK的高级特性,位于
<qp>\qpc\examples\80x86\qk\tcpp101\l\dpp\
目录下。
3.2 异步抢占演示
在DPP应用程序中,有趣的异步抢占(一个任务异步抢占另一个任务)并不容易观察到。通常,系统时钟滴答中断(
ISR_tmr
)将时间事件发布到哲学家活动对象,中断和状态机执行速度很快,CPU很快回到执行QK空闲循环,因此
ISR_tmr()
很难实际抢占任何QK任务,只能抢占空闲循环。
为了增加异步抢占的可能性,采取了以下措施:
- 添加第二个中断(键盘触发的
ISR_kbd
),该中断与时钟滴答异步,并始终向
Table
活动对象发布事件。
- 在状态机和中断中添加各种忙等待函数,增加人工CPU负载。
- 对中断服务程序(ISR)进行插桩,将中断引起的抢占信息报告到屏幕。
同时,为DPP应用程序提供了一个命令行参数,用于确定各种忙等待函数的延迟时间。在2GHz的PC上,使用100次迭代的值可以轻松观察到几次异步抢占。但要注意不要过度使用该参数,以免使CPU过载,导致事件队列溢出。
以下是异步抢占演示的流程:
graph TD;
A[启动DPP应用程序] --> B[等待时钟滴答中断];
B --> C{是否发生键盘中断};
C -- 是 --> D[键盘中断发布事件到Table对象];
D --> E[QK执行异步上下文切换到Table对象];
E --> F[观察哲学家任务的抢占计数器增加];
C -- 否 --> B;
3.3 优先级上限互斥锁演示
为了演示QK优先级上限互斥锁,扩展了哲学家活动对象,允许哲学家有随机的思考和进食超时时间。使用Turbo C++ 1.01提供的伪随机数(PRN)生成器
random()
,由于该生成器不是可重入的,因此使用QK互斥锁保护其内部状态。代码如下:
QState Philo_thinking(Philo *me, QEvent const *e) {
switch (e->sig) {
case Q_ENTRY_SIG: {
QTimeEvtCtr think_time;
QMutex mutex;
mutex = QK_mutexLock(N_PHILO);
think_time = (QTimeEvtCtr)(random(THINK_TIME) + 1);
QK_mutexUnlock(mutex);
QTimeEvt_postIn(&me->timeEvt, (QActive *)me, think_time);
return (QState)0;
}
...
}
return (QState)&QHsm_top;
}
使用哲学家的数量
N_PHILO
作为互斥锁的优先级上限,由于
Table
活动对象的优先级(
N_PHILO + 1
)高于该上限,因此互斥锁不会影响
Table
对象。
3.4 TLS演示
在
qk_port.h
头文件中,定义了两个假设的库
lib1
和
lib2
的上下文,需要线程本地存储(TLS)支持。在应用程序级别,需要将这些上下文添加到活动对象中,并告知QK这些TLS上下文的位置。以下是相关代码:
typedef struct PhiloTag {
QActive super;
/* derives from the QActive base class */
(1)
ThreadContext context;
/* thread context */
QTimeEvt timeEvt;
/* for timing out thining or eating */
} Philo;
void Philo_start(uint8_t n,
uint8_t p, QEvent const *qSto[], uint32_t qLen) {
Philo *me = &l_philo [n];
Philo_ctor(me);
/* instantiate */
(2)
impure_ptr1 = &me->context.lib1;
/* initialize reentrant library1 */
(3)
lib1_reent_init(p);
(4)
impure_ptr2 = &me->context.lib2;
/* initialize reentrant library2 */
(5)
lib2_reent_init(p);
QActive_start((QActive *)me, p, qSto, qLen,
(6)
&me->context,
(7)
(uint8_t)(QK_LIB1_THREAD | QK_LIB2_THREAD | QK_FPU_THREAD),
(QEvent *)0);
}
QState Philo_thinking(Philo *me, QEvent const *e) {
switch (e->sig) {
...
case TIMEOUT_SIG: {
(8)
lib1_test();
(9)
lib2_test();
return (QState)0;
}
...
}
return Q_SUPER(&QHsm_top);
}
QState Philo_hungry(Philo *me, QEvent const *e) {
switch (e->sig) {
...
case EAT_SIG: {
if (((TableEvt const *)e)->philoNum == PHILO_ID(me)) {
(10)
lib1_test();
(11)
lib2_test();
return (QState)0;
}
break;
}
...
}
return Q_SUPER(&QHsm_top);
}
QState Philo_eating(Philo *me, QEvent const *e) {
switch (e->sig) {
...
case TIMEOUT_SIG: {
(12)
lib1_test();
(13)
lib2_test();
return Q_TRAN(&Philo_thinking);
}
...
}
return Q_SUPER(&QHsm_top);
}
在
Philo
类中添加
ThreadContext
类型的
context
成员,确保每个
Philo
对象都有私有的线程上下文区域。在
Philo_start()
函数中初始化可重入库的上下文,并将TLS指针传递给
QActive_start()
函数。在哲学家状态机中调用库函数,提供CPU负载以测试异步抢占。
同时,还需要定义实际使用“不纯指针”的库函数
lib1_test()
和
lib2_test()
,代码如下:
#include <math.h>
void lib1_reent_init(uint8_t prio) {
(1)
impure_ptr1->x = (double)prio * (M_PI / 6.0);
}
void lib1_test(void) {
(2)
uint32_t volatile i = l_delay;
(3)
while (i-->OUL) {
(4)
volatile double r = sin(impure_ptr1->x) * sin(impure_ptr1->x)
+ cos(impure_ptr1->x) * cos(impure_ptr1->x);
(5)
Q_ASSERT(fabs(r - 1.0) < 1e-99);
/* assert the identity */
}
}
void lib2_reent_init(uint8_t prio) {
(6)
impure_ptr2->y = (double)prio * (M_PI / 6.0) + M_PI;
}
4. 总结
通过以上步骤,完成了QK内核在80x86 CPU上的移植,并使用DPP应用程序对移植进行了测试,展示了QK的高级特性。在移植过程中,需要根据目标CPU和编译器的特点选择合适的中断锁定策略,并定制高级特性以满足应用需求。在测试过程中,通过添加额外的中断和人工CPU负载,增加了异步抢占的可能性,便于观察和调试。
3.4 TLS演示(续)
在前面提到了对
Philo
类及相关函数的修改以支持TLS,同时也对
Table
活动对象做了类似的更改,使其能与所有哲学家共享库。具体而言,在
Table
类中同样添加
ThreadContext
类型的成员,在其初始化函数中进行库上下文的初始化和TLS指针的传递,并且在状态机中调用库函数。
以下是
Table
类相关的示例代码(假设部分结构和函数已在前面定义):
typedef struct TableTag {
QActive super;
ThreadContext context;
// 其他成员
} Table;
void Table_start(uint8_t p, QEvent const *qSto[], uint32_t qLen) {
Table *me = &l_table;
Table_ctor(me);
impure_ptr1 = &me->context.lib1;
lib1_reent_init(p);
impure_ptr2 = &me->context.lib2;
lib2_reent_init(p);
QActive_start((QActive *)me, p, qSto, qLen,
&me->context,
(uint8_t)(QK_LIB1_THREAD | QK_LIB2_THREAD | QK_FPU_THREAD),
(QEvent *)0);
}
QState Table_state(Table *me, QEvent const *e) {
switch (e->sig) {
case SOME_SIG: {
lib1_test();
lib2_test();
return (QState)0;
}
// 其他情况
}
return Q_SUPER(&QHsm_top);
}
3.5 测试总结
通过对QK端口的测试,我们成功验证了其各项功能和高级特性。以下是测试结果的总结表格:
| 测试特性 | 测试方法 | 测试结果 |
| — | — | — |
| 异步抢占 | 添加额外中断和人工CPU负载,观察抢占计数器 | 可观察到异步抢占现象 |
| 优先级上限互斥锁 | 使用QK互斥锁保护非可重入的PRN生成器 | 保护了生成器内部状态,不影响高优先级对象 |
| 线程本地存储(TLS) | 在活动对象中添加TLS上下文,调用库函数 | 实现了多可重入库的线程本地存储 |
3.6 调试说明
在调试QK任务时,使用Turbo C++ IDE的调试器是可行的。虽然QK任务嵌套在中断栈帧上,但调试相对直接。然而,调试中断服务程序(ISR)比较困难,因为Turbo C++调试器在CPU或8259A PIC级别锁定中断时会冻结。
以下是调试异步抢占的步骤:
1. 加载项目
DPP - DBG.PRJ
到Turbo C++ IDE中。
2. 打开文件
qk_ext.c
,在
QK_scheduleExt_()
函数内设置断点(如前面提到的特定位置)。
3. 选择
Run | Arguments...
定义命令行参数(如100)。
4. 运行程序,开始在键盘上输入,当发生异步抢占时会触发断点。
5. 可以使用单步执行(如F7)逐步查看代码执行过程。
以下是调试流程的mermaid流程图:
graph TD;
A[加载项目DPP - DBG.PRJ] --> B[打开qk_ext.c文件];
B --> C[设置断点];
C --> D[定义命令行参数];
D --> E[运行程序];
E --> F{是否发生异步抢占};
F -- 是 --> G[触发断点,单步调试];
F -- 否 --> E;
4. 技术要点回顾
4.1 移植要点
- 中断锁定策略 :根据目标系统的硬件和编译器特性选择合适的中断锁定策略,如“保存和恢复中断状态”或“无条件中断解锁”。在本次移植中,由于标准PC的硬件特性和编译器支持,采用了“无条件中断解锁”策略。
-
头文件配置
:正确配置
qep_port.h、qf_port.h和qk_port.h头文件,定义平台特定的整数类型、活动对象最大数量、中断处理宏和高级特性的实现。 -
FPU上下文处理
:使用汇编代码实现
FPU_save()和FPU_restore()函数,保存和恢复80x87 FPU的上下文。
4.2 测试要点
- 异步抢占 :通过添加额外中断和人工CPU负载,增加异步抢占的可能性,便于观察和验证。
- 优先级上限互斥锁 :使用互斥锁保护非可重入的资源,合理设置优先级上限,确保不影响高优先级对象。
- 线程本地存储(TLS) :在活动对象中添加TLS上下文,初始化可重入库的上下文,并在状态机中调用库函数,实现多可重入库的线程本地存储。
5. 结论
通过本次实践,我们成功完成了QK内核在80x86 CPU上的移植,并通过DPP应用程序对移植进行了全面测试。在移植过程中,我们深入了解了不同CPU架构和编译器对内核运行的影响,学会了根据目标系统的特点选择合适的中断锁定策略和定制高级特性。在测试过程中,我们掌握了如何增加异步抢占的可能性,以及如何使用QK的高级特性,如优先级上限互斥锁和线程本地存储。这些经验对于嵌入式系统开发中使用QK内核具有重要的指导意义,能够帮助我们更好地应对不同的开发需求和挑战。
未来,我们可以进一步探索QK内核在其他CPU架构和操作系统上的移植和应用,不断扩展其应用范围。同时,可以对QK的高级特性进行更深入的研究和优化,提高系统的性能和稳定性。
超级会员免费看
110

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



