亲爱的支持,
它看起来我偶然发现了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);
}
空白my_start_app_timer(空白)
{
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并调用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完成。
如果参数计数器==全局计数器然后处理,否则丢弃。
嗯,悲伤的对话框没有给我们将私有指针添加到计时器中的手段,因此我的想法无法完成。
这是否意味着Dialog将修改它们自己的配置文件,就像电池服务器应用程序那样,以便它们能够正确工作?我认为这是一个影响许多地方的主要漏洞。
你再次正确。我们的逻辑比简单的布尔值得更复杂。但布尔可能适用于大多数情况。
下面是我们(我认为是)的一个正确的解决方案。
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(...){
如果(stable_timer_should_process (&state_variable)) {
//正常进程
}
return(ke_msg_consumed);
}
* /
typedef struct 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 task, uint16_t const delay, StableTimerState* state_variable);
bool stable_timer_clear(ke_msg_id_t const timer_id, ke_task_id_t const task, StableTimerState* state_variable);
bool stable_timer_should_process (StableTimerState * state_variable);
stable_timer.c:
# include“stable_timer.h”
void stable_timer_set(ke_msg_id_t const timer_id, ke_task_id_t const task, uint16_t const delay, StableTimerState* state_variable) {
If (state_variable->is_active && !ke_timer_active(timer_id, task)) {
//它留下了定时器队列并输入了消息队列
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 task, StableTimerState* state_variable) {
If (state_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(StableTimerState* state_variable) {
if(state_variable-> ignore_count> 0){
state_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到零的设置,现在它可以在我预期的工作。
是的,我在'stable_timer_should_process'外部设置为0,因为它仍然需要…
据我所知,这也有效。这是一个耻辱,每个开发商都必须自己找到并解决它(我偶然地偶然发现了)。我宁愿在他们的代码中为所有人解决这个问题,以便在未来的未来中占世界其他地方的利益。
我必须在我们已经发布的产品中检查这一点,并在需要时带出辅助。
嗨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.