创建回调
回调是由某个事件发出信号的函数。因此,如果出现这样的事件,则调用回调函数。例如,在计时器上。
回调函数可以在应用程序上下文(ring 3)或内核或实时上下文(ring 0)中执行。这提供了在应用程序上下文中轻松编程中断服务例程的可能性,或者实时性要求高的代码中执行。当然,在内核级别执行的代码有一些限制。
应用程序级别 (ring3) 与内核级别 (ring0)
通常,优先级较高的内核级别是为操作系统保留的,并且仅在开发内核驱动程序时可用。使用 Kithara RealTime Suite,几乎可以在 ring0 上运行任何用户代码。具体是如何完成的将在后面讨论,或者看看这里。
要在 ring0 上运行 user-code,需要 kernel-module。如果没有这个,回调只能在 ring3 执行。例如,作为事件触发的线程。
没有内核模式的回调功能有:
-
在应用程序级别的回调执行中,上下文切换将由 Windows 进行。因此,可访问的延迟仅是有限的可操作的,例如具有高优先级(通常为几微秒,偶尔几毫秒)。
-
原则上,通过嵌入多线程上下文,可以调用所有操作系统函数,例如从中断处理程序调用。
内核模式的回调特性包括:
-
无法调用任何操作系统函数。但是为最重要的任务提供了特殊功能。
-
为了与应用程序的其他部分进行通信,提供了设置或重置事件或发送消息的功能。
-
数据交换可以进行,例如通过
共享内存
或消息或数据管道进行。它不得发生在对全局变量或类似内容的访问中。
注意: 无法从 ring0 直接调用 Windows 或应用程序级别的对话框,例如 MFC、VCL 来显示值。有关 内核模式的更多信息,请参阅此处。
回调机制的优点是,即使没有内核模块,也可以在应用程序级别提供中断。并且具有内核级别,可以以更短的延迟做出反应,并且几乎没有 ring3 应用程序的中断。在应用程序级别,可以使用通用调试器进行调试。
回调原型
所有回调函数的原型是:
C/C++
Error __stdcall callBack(void* pArgs, void* pContext);
Delphi
function callBack( pArgs, pContext: Pointer ): Error; stdcall;
C#
int callBack( void* pArgs, void* pContext );
概念
所有回调函数都需要遵循此内容。
-
返回的整数值是错误值。零表示未发生错误。当返回的值不等于零时,将停止对此回调的信令。
-
第一个参数 pArgs 可以指向任何数据,例如包含大量数据的结构或记录。
-
第二个参数 pContext 指向发生事件上下文中的数据结构。
上下文数据
pContext 中的上下文数据将包含一个 32 位整数值作为第一个成员。它被称为 ctxType,并显示传递的上下文结构。每个上下文结构都与特定模块中的事件一起描述。在大多数情况下,建议在每次回调中的第一个操作时将 pContext 指针强制转换为特定上下文类型。通过上下文类型的区分,可以使用相同的回调函数来提供多个事件。
参考数据
建议使用 pArgs 作为指向共享内存的指针。此地址可以是更复杂的数据结构的开始,以防止多个参数。这样,就可以很容易地将数据可用性从 ring0 更改为 ring3,反之亦然,使用 callback-function 的执行(ring0 或 ring3)级别。
如何创建回调
如上所述,回调可以在不同的执行级别运行。此级别将在创建回调时确定。应使用回调函数执行的用户代码必须重新定位到确定的级别。有不同的方法可以实现这一点。
创建回调的推荐方法:** 从以前加载的 DLL 创建回调。有关将DLL加载到不同的执行级别的更多信息,请参阅此处。这种方式是开发稳定项目的推荐策略,即使具有更高的复杂性。
除此之外,还有一种较旧
的回调方式:对回调函数命令对命令
进行完整分析,并在确定的地址空间中生成具有相同语义的副本(函数的重定位,仅适用于 32 位 Windows-OS)。此机制将不会进一步开发,并且不在 64 位 Windows-OS 上可用,因此不会讨论。
创建回调
-
如果是加载的 DLL 创建回调的方法,需要使用 KS_loadKernel 加载 DLL 才能创建回调。加载 DLL 时,将确定执行级别。从现在开始,这不能改变!阅读有关 加载 DLL 的更多信息,请点击此处。
-
使用函数 [KS_createKernelCallBack](https://kithara.com/cn/docs/krts:api:ks_createkernelcallback
KS_createKernelCallBack
) 创建回调。callback-function 将被选中,名称为 name。此功能必须遵循上述概念和原型。
执行级别和附加选项
如果 DLL 加载了KSF_USER_EXEC
,则创建的回调将在应用程序级别 (ring3) 执行。此处,参数 prio 将确定线程优先级。可以利用应用程序级别的所有优势。
如果 DLL 加载了KSF_KERNEL_EXEC
,则创建的回调将在内核级别 (ring0) 执行。在这种情况下,可以在 flags 中另外提供标志KSF_DIRECT_EXEC
或KSF_SAVE_FPU
。
使用标志KSF_DIRECT_EXEC
创建实时回调。此通话将实时运行,不能中断!主要用于中断服务例程或需要非常短抖动和不间断执行的例程。
如果回调函数中存在浮点运算,则需要KSF_SAVE_FPU
,即使这称为间接运算。
删除回调
如果不再需要回调,则必须使用KS_removeCallBack将其删除以释放所有资源。建议在调用KS_removeCallBack
时注销触发事件(例如中断或计时器)。
手动回调
有时需要手动执行回调。此选项随 KS_execCallBack 一起提供。也可以为执行提供上下文。在这种情况下,应为该用户定义的上下文准备回调函数。
项目实例
创建回调可参考以下Demo:
smp\TaskBasics
smp\EtherCATBasics
smp\EtherCATBridgeConfig
smp\EtherCATDataExchange
...