跳过主要内容

ke_timer竞态条件

DA14580

5年前

发布的paul.deboer0点 14回复
0旋转

亲爱的支持,

它看起来我偶然发现了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)

清除计时器(计时器id、任务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(系统状态寄存器,PER向下))
periph_init ();

开关(msgs)

案例MY_APP_TIMER:
我的应用程序计时器已启动();
打破;
默认值:
打破;

返回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,没有特殊的魔法。是我的结论错误,我正在监督某些东西吗?

5年前

Joacimwe 5分

你完全正确。当计时器触发时,它会向队列发送消息,并清除计时器对象/状态。当邮件位于队列上时,如果调用ke_timer_clear,没有任何情况发生,如果调用ke_timer_active它将返回false。

由于此问题,我们有一些额外的变量,指示计时器是否真正处于活动状态,在处理程序中,我们检查此变量是否为真。如果是真的,请像往常一样进程。如果为false,请丢弃它。

5年前

paul.deboer 0点

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完成。
如果参数计数器==全局计数器然后处理,否则丢弃。

5年前

paul.deboer 0点

嗯,不幸的是Dialog并没有给我们提供给计时器添加私有指针的方法,所以我的想法根本无法实现。

5年前

paul.deboer 0点

这是否意味着Dialog将修改自己的配置文件,如电池服务器应用程序,以使其正常工作?我认为这是一个影响很多地方的主要漏洞。

5年前

Joacimwe 5分

你又对了。我们的逻辑比简单的布尔逻辑复杂一点。但布尔值可能适用于大多数情况。
下面是我们的(我认为是)正确的解决方案。
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任务,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,任务);

状态变量->处于活动状态=0;
返回真;

bool stable_timer_should_process(StableTimerState* state_variable){/ /稳定的进程
If (state_variable->ignore_count > 0) {
state_variable - > ignore_count——;
返回false;
其他}{
状态变量->处于活动状态=0;
返回真;


5年前

paul.deboer 0点

此代码适用于单次计时器,而不是重复计时器。'nation_variable - > is_active = 0;'在'stable_timer_shource_process'中杀死解决方案。

5年前

Joacimwe 5分

你是什​​么意思?
要使计时器具有周期性,只需在处理程序结束时重新启动它?

5年前

paul.deboer 0点

是的,真实的,但随着“稳定_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,因为它仍然需要…

5年前

paul.deboer 0点

据我所知,这也有效。这是一个耻辱,每个开发商都必须自己找到并解决它(我偶然地偶然发现了)。我宁愿在他们的代码中为所有人解决这个问题,以便在未来的未来中占世界其他地方的利益。
我必须在我们已经发布的产品中检查这一点,并在需要时带出辅助。

5年前

mt_dialog. -30点

嗨,保罗,德波,

如果你在最小的步骤中使用计时器,它可能不能正常工作。如果你正在设置一个计时器,而你在同样的10毫秒内取消了它,你可能不确定计时器是否被触发。当您试图清除Timer消息时,它可能已经在队列中等待执行。你可以做的是,清除计时器,并在你的回调中有一个故障安全机制来“过滤掉”那些调用。在SDK 5中有一个实现,当一个计时器被取消时,计时器的回调被一个空回调代替,以防止处理程序没有正确地取消。

谢谢mt_dialog.

5年前

paul.deboer 0点

我认为选择哪一步并不重要,重要的是时机。我将在sdk5中查看你是如何解决它的,但正如我解释的那样,这也是不防水的。在队列中处理消息之前,同一计时器的停止和开始序列仍然是一个问题。
我宁愿在回调中返回的计时器中有私有数据,那么这将是一个简单的修复。回调原型已经有了它,但是根本没有必要设置它。

5年前

paul.deboer 0点

我刚刚看了一下SDK5, app_easy_timer.c,可以确定这似乎解决了我所描绘的场景中的竞态条件。由于使用了计时器池,这修复了这个问题。我很高兴这样的代码存在,并将其移植到我的SDK。

5年前

Joacimwe 5分

我认为新的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两个函数的包装器,并返回(可能是新的)句柄。我能看到的唯一问题是当没有更多的句柄创建新的计时器…

5年前

mt_dialog. -30点

嗨Joacimwe,

感谢您的指示,我将您的观察转发给SDK团队,以便看看您的建议。

谢谢mt_dialog.