亲爱的支持,
它看起来我偶然发现了ke_timer_set,触发消息和ke_timer_clear之间的竞争条件(至少它看起来)。
我正在使用下面的计时器,作为10ms的刻度计时器(因此您目睹此种族条件的可能性增加)。
void app_timer_unset(ke_msg_id_t const timer_id,ke_task_id_t const task_id)
{
ke_timer_clear(timer_id, task_id);
}
作废我的应用程序启动计时器(作废)
{
应用程序\定时器\设置(我的应用程序\定时器,任务应用程序,1);
}
void my_stop_app_timer(void)
{
app_timer_unset(my_app_timer,task_app);
}
int my_timer_fired(ke_msg_id_t const msgs,void const * param,ke_task_id_t const dest_id,ke_task_id_t const src_id)
{
if(GetBits16(SYS_STAT_REG, PER_IS_DOWN))
periph_init();
开关(msgs)
{
案例我的应用程序计时器:
my_app_timer_fired();
中断;
违约:
中断;
}
返回ke_msg_consumed;
}
静态void my_app_timer_fired(void)
{
//做些事情,重新安排时间
应用程序\定时器\设置(我的应用程序\定时器,任务应用程序,1);
}
在很多情况下,当我调用myu stop\u app\u计时器时,计时器会一直运行。ke_timer_set的文档中提到以下内容:
//当计时器到期时,将消息发送到作为参数提供的任务,将定时器ID为消息ID。
嗯,这是否意味着我们不能简单地调用Ke_timer_clear,因为消息可能已经在消息队列中?
在这种情况下,计时器确实停止了,但是被重新调度,因为消息仍在处理中,因此计时器被重新调度。
对我来说,这看起来像是一个经典的竞争条件,我能想到的解决这个问题的唯一方法是将一条消息放在与timer expire消息放在同一个队列中。
我看过对话框的例子,但只需调用ke_timer_clear,没有特殊的魔法。是我的结论错误,我正在监督某些东西吗?
你完全正确。什么时候?the timer triggers, it sends a message to the queue and clears the timer object/status. When the message is on the queue, if you call ke_timer_clear nothing will happen and if you call ke_timer_active it will return false.
由于此问题,我们有一些额外的变量,指示计时器是否真正处于活动状态,在处理程序中,我们检查此变量是否为真。如果是真的,请像往常一样进程。如果为false,请丢弃它。
Boolean不会完全解决此问题(除非您将其与其他计数器相结合),请考虑以下方案:
1计时器正在运行,计划在t=100时关闭
2@t=100,消息放入队列
三。@t=100,在第2步之后,设置bool is \u active=false并调用ke \u timer \u clear
4. @t = 100,在步骤3之后,您将BOOL IS_Active = TRUE设置,并使用相同的TIMER_ID调用ke_timer_set和5min的超时
5. @ T = 100,在步骤4之后,处理队列中的消息。处理程序看到是_active = true,立即处理而不是5分钟后处理!
最简单的解决方案是增加一个全局计数器以及keu timer\u set和keu timer\u clear。
计数器必须作为计时器处理程序中的参数给定,由ke\u timer\u set完成。
如果参数计数器==全局计数器然后处理,否则丢弃。
嗯,很遗憾Dialog没有given us the means to add a private pointer into a timer, so my idea simply cannot be done.
这是否意味着Dialog将修改它们自己的配置文件,比如电池服务器应用程序,以便它们正常工作?我认为这是一个影响很多地方的主要错误。
你再次正确。我们的logic is a bit more complicated than a simple boolean. But a boolean probably works for most cases.
以下是我们(我认为是)的正确解决方案。
SDK 5有一些新的包装器API,但它具有相同的问题。
stable_timer.h:
#包括“keu timer.h”
/*
用这些函数代替keu timer\u set,keu timer\u clear
使用状态_variable.u是否激活了解计时器是否处于活动状态,而不是keïu timerïu active功能
计时器处理程序应如下所示:
int handler(...){
if(稳定\u计时器\u应该\u进程(&state \u变量)){
//正常处理
}
return(ke_msg_consumed);
}
* /
typedef结构StableTimerState{
无符号字符处于活动状态:1;
unsigned short ignore_count:15;
}StableTimerState;
void stable_timer_set(ke_msg_id_t const timer_id,ke_task_id_t const任务,uint16_t const delay,stabletimerstate * state_variable);
bool stable\u timer\u clear(ke\u msg\u id\u t const timer\u id,ke\u task\u id\u t const task,StableTimerState*state\u变量);
bool stable\u timer\u should\u process(StableTimerState*state\u变量);
稳定定时器c:
#包括“稳定定时器.h”
void stable\u timer\u set(ke\u msg\u id\u t const timer\u id,ke\u task\u id\u t const task,uint16\u t const delay,stablItemState*state\u变量){
如果(状态变量->处于活动状态&&!keu timer\u active(timer\u id,task)){
//它留下了定时器队列并输入了消息队列
nation_variable-> ignore_count ++;
}
ke_timer_set(timer_id,任务,延迟);
state_variable - > is_active = 1;
}
bool stable\u timer\u clear(ke\u msg\u id\u t const timer\u id,ke\u task\u id\u t const task,StableTimerState*state\u变量){
if(state\u variable->is\u active==0)
返回false;
if(!ke_timer_active(timer_id,任务)){
//它留下了定时器队列并输入了消息队列
nation_variable-> ignore_count ++;
} 别的 {
ke_timer_clear(timer_id,任务);
}
state_variable->is_active = 0;
返回真;
}
bool stable\u timer\u should\u process(StableTimerState*state\u变量){
if (state_variable->ignore_count > 0) {
状态变量->忽略计数--;
返回false;
} 别的 {
state_variable->is_active = 0;
返回真;
}
}
此代码适用于单次计时器,而不是重复计时器。'nation_variable - > is_active = 0;'在'stable_timer_shource_process'中杀死解决方案。
你是什么意思?
要使计时器周期化,只需在处理程序的末尾重新启动它?
是的,真实的,但随着“稳定_timer_should_process”称之为众多次,endy_variable - > is_active设置为零,即使在我称为'stable_timer_clear'之前也是如此。由于第一个语句'如果(event_varibey-> is_active == 0)返回false,后者函数现在永远不会停止计时器。
I removed the setting of is_active to zero from the 'stable_timer_should_process' and now it works as I expected.
是的,我将设置设置为零,超出了“稳定的计时器应该处理”的范围,因为它仍然是需要的。。。
据我所知,这也有效。这是一个耻辱,每个开发商都必须自己找到并解决它(我偶然地偶然发现了)。我宁愿在他们的代码中为所有人解决这个问题,以便在未来的未来中占世界其他地方的利益。
我必须在我们已经发布的产品中检查这一点,并在需要时带出辅助。
Hi paul.deboer,
如果您使用的是最小步长的计时器,它可能无法正常工作。如果您正在设置一个计时器,并在相同的10毫秒内取消它,您可能无法确定计时器是否已触发。当您试图清除计时器消息时,它可能已经在队列中等待执行。你可以做的是清除计时器,在你的回调中有一个故障保护机制来“过滤”那些调用。在sdk5中,有一个实现,当计时器被取消时,计时器的回调将被一个空回调替换,以防处理程序没有正确取消。
谢谢mt_dialog.
我认为你选择哪一步并不重要,重要的是时机。我将在sdk5中看看你是如何解决它的,但我解释它,这也是不防水的。在队列中处理消息之前,相同计时器的停止和启动序列仍然是一个问题。
I'd rather have had private data in the timer that was given back in the callback, then this would have been an easy fix. The callback prototype already has it, but there is simply no mean to set it.
我刚刚查看了一下SDK5,app\u easy\u timer.c,可以确认这似乎甚至解决了我所描绘的场景中的竞速条件。由于使用了计时器池,这就解决了问题。我很高兴这样的代码存在,并将其移植到我的SDK。
我认为新的Easy Timer API中有一个错误,您在使用“app_easy_timer_modify”功能“app_easy_timer_modify”修改定时器时,您尚未处理此功能。考虑计时器已被放入消息队列但尚未处理时的情况。如果您当时调用app_easy_timer_modify,则不会应用新延迟,但处理程序将很快执行,如果应用程序依赖于新延迟,则可能是一个问题。此外,将使用新的延迟设置新的“幽灵定时器”,该新延迟设置为在火灾时执行与该句柄相关联的回调,因此如果用户在触发前的时间恰好设置新计时器(它已经存在)从ke timer api移动到消息队列中),并且这个新的计时器与上一个“ghost timer”获得相同的手柄,Ghost Timer处理程序将几乎立即执行新的计时器的回调,而是在指定的延迟之后,另一个“幽灵”定时器“已创建,我们返回上一步...
Conclusion: the same carefullness must be applied to app_easy_timer_modify.
我有一个建议,你可以使用ke\u timer\u active in app\u easy\u timer\u cancel。如果计时器处于活动状态,您可以确定消息尚未进入消息队列,因此只需将回调设置为NULL,而不必发送APP\u CANCEL\u计时器消息。
修复app\u easy\u timer\u modify的一个简单方法是简单地将其作为两个函数app\u easy\u timer\u cancel的包装,然后是app\u easy\u timer并返回(可能是新的)句柄。我能看到的唯一问题是当没有更多的句柄来创建新计时器时。。。
你好,乔奇姆,
感谢您的指示,我将您的观察转发给SDK团队,以便看看您的建议。
谢谢mt_dialog.