亲爱的支持,
它看起来我偶然发现了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);
}
void my_start_app_timer(void)
{
app_timer_set(my_app_timer,task_app,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:
my_app_timer_fired();
休息;
默认:
休息;
}
返回ke_msg_consumed;
}
静态void my_app_timer_fired(void)
{
//做的东西和重新安排计时器
app_timer_set(my_app_timer,task_app,1);
}
在许多场合,当我调用my_stop_app_timer时,计时器会导致运行。KE_TIMER_SET提出的文档以下内容:
//当计时器到期时,将消息发送到作为参数提供的任务,将定时器ID为消息ID。
嗯,这是否意味着我们不能简单地调用Ke_timer_clear,因为消息可能已经在消息队列中?
在这种情况下,定时器确实停止但被重新安排,因为仍然处理消息,因此重新安排计时器。
这看起来像我的古典竞赛条件,我可以想到如何解决这一点的唯一方法是将一个消息放在与定时器到期消息的位置相同的队列中。
我看过对话框的例子,但只需调用ke_timer_clear,没有特殊的魔法。是我的结论错误,我正在监督某些东西吗?
你完全正确。当计时器触发时,它会向队列发送消息,并清除计时器对象/状态。当邮件位于队列上时,如果调用ke_timer_clear,没有任何情况发生,如果调用ke_timer_active它将返回false。
由于此问题,我们有一些额外的变量,指示计时器是否真正处于活动状态,在处理程序中,我们检查此变量是否为真。如果是真的,请像往常一样进程。如果为false,请丢弃它。
Boolean不会完全解决此问题(除非您将其与其他计数器相结合),请考虑以下方案:
1.计时器正在运行,并计划下车@ t = 100
2. @ T = 100,邮件放入队列中
3. @ T = 100,在步骤2之后,您将BOOL IS_Active = false设置为false并调用ke_timer_clear
4. @t = 100,在步骤3之后,您将BOOL IS_Active = TRUE设置,并使用相同的TIMER_ID调用ke_timer_set和5min的超时
5. @ T = 100,在步骤4之后,处理队列中的消息。处理程序看到是_active = true,立即处理而不是5分钟后处理!
最简单的解决方案是将全局计数器与KE_TIMER_SET和KE_TIMER_CLEAR增加。
必须将计数器作为计时器处理程序中的参数给出,由ke_timer_set完成。
如果参数计数器==全局计数器然后处理,否则丢弃。
嗯,悲伤的对话框没有给我们将私有指针添加到计时器中的手段,因此我的想法无法完成。
这是否意味着对话框将修改自己的配置文件,如电池服务器应用程序,以便它们正常工作?我会将其视为影响许多地方的主要错误。
你再次正确。我们的逻辑比简单的布尔值得更复杂。但布尔可能适用于大多数情况。
下面是我们的(我相信的是)一个正确的解决方案。
SDK 5有一些新的包装器API,但它具有相同的问题。
stable_timer.h:
#include“ke_timer.h”
/ *
使用这些函数而不是ke_timer_set,ke_timer_clear
使用state_variable.is_active以知道计时器是否处于活动状态而不是ke_timer_active函数
定时器处理程序应该如下所示:
int handler(...){
if(stable_timer_should_process(&nats_variable)){
//像正常的过程
}
return(ke_msg_consumed);
}
* /
typedef结构stabletimerstate {
unsigned char is_active: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_timer_clear(ke_msg_id_t const timer_id,ke_task_id_t const任务,stabletimerstate * nation_variable);
bool stable_timer_should_process(stabletiferstate * nation_variable);
stable_timer.c:
#include“stable_timer.h”
void stable_timer_set(ke_msg_id_t const timer_id,ke_task_id_t const任务,uint16_t const延迟,stabletimerstate * nation_variable){
if(nats_variable - > is_active &&!ke_timer_active(timer_id,任务)){
//它留下了定时器队列并输入了消息队列
nation_variable-> ignore_count ++;
}
ke_timer_set(timer_id,任务,延迟);
state_variable - > is_active = 1;
}
bool stable_timer_clear(ke_msg_id_t const timer_id,ke_task_id_t const任务,stabletimerstate * nation_variable){
if(nation_variable - > is_active == 0)
返回false;
if(!ke_timer_active(timer_id,任务)){
//它留下了定时器队列并输入了消息队列
nation_variable-> ignore_count ++;
} 别的 {
ke_timer_clear(timer_id,任务);
}
nats_variable - > is_active = 0;
返回真;
}
bool stable_timer_should_process(stablediferstate * nation_variable){
if(state_variable-> ignore_count> 0){
equend_variable-> ignore_count--;
返回false;
} 别的 {
nats_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,后者函数现在永远不会停止计时器。
我从'stable_timer_should_process'中删除了is_active到零的设置,现在它可以在我预期的工作。
是的,我将设置放在“稳定_timer_should_process”之外的零,因为它仍然需要...
据我所知,这也有效。这是一个耻辱,每个开发商都必须自己找到并解决它(我偶然地偶然发现了)。我宁愿在他们的代码中为所有人解决这个问题,以便在未来的未来中占世界其他地方的利益。
我必须在我们已经发布的产品中检查这一点,并在需要时带出辅助。
嗨paul.deboer,
如果您在最小步骤中使用定时器,则可能无法正常运行。如果要设置计时器,并且在相同的10ms中取消它可能无法确定计时器是否已被触发。定时器消息可能已经在等待执行的队列中,同时尝试清除它。您可以做的是,要清除计时器,并在回调中有一个故障安全机制,以“过滤掉”这些呼叫。在SDK 5中,存在一个实现,其中当定时器被取消时,在处理程序未正确取消的情况下,计时器的回调被空回调替换。
谢谢mt_dialog.
我认为你选择了什么步骤并不重要,这是一个时间问题。我会看看SDK5你如何解决它,但在我解释它时,这也不是防水。在队列中处理消息之前的一系列停止和开始相同的计时器仍然是一个问题。
我宁愿在回调中给出的计时器中有私人数据,那么这将是一个简单的修复。回调原型已经拥有它,但根本没有意味着设置它。
我只是看看SDK5,App_easy_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处理程序将几乎立即执行新的计时器的回调,而是在指定的延迟之后,另一个“幽灵”定时器“已创建,我们返回上一步...
结论:必须对App_easy_timer_modify应用同样的谨慎。
提示我可以在app_easy_timer_cancel中使用ke_timer_active。如果计时器处于活动状态,则确保邮件尚未输入消息队列,因此可以将回调设置为NULL并忽略发送APP_CANCEL_TIMER消息的需要。
一个简单的方法来修复app_easy_timer_modify是简单地使它围绕两个函数app_easy_timer_cancel围绕app_easy_timer返回(潜在的新)句柄。我可以看到的唯一问题是当没有更多的句柄来创建新的计时器...
嗨Joacimwe,
感谢您的指示,我将您的观察转发给SDK团队,以便看看您的建议。
谢谢mt_dialog.