为什么转发后我的hrtimer回调过早返回?

问题描述 投票:1回答:2

我想使用hrtimer来控制两个硬件gpio引脚来做一些总线信号。我在这样的内核模块中设置了一个hrtimer

#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/ktime.h>
#include <linux/hrtimer.h>

#define PIN_A_HIGH_TO_A_LOW_US  48  /* microseconds */
#define PIN_A_LOW_TO_B_LOW_US   24  /* microseconds */

static struct kt_data {
    struct hrtimer timer;
    ktime_t period;
} *data;

typedef enum {
    eIdle = 0,
    eSetPinALow,
    eSetPinBLow,
} teControlState;

static enum hrtimer_restart TimerCallback(struct hrtimer *var);
static void StopTimer(void);

static teControlState cycle_state = eIdle;

static enum hrtimer_restart TimerCallback(struct hrtimer *var)
{
    local_irq_disable();

    switch (cycle_state) {
    case eSetPinALow:
        SetPinA_Low();
        data->period = ktime_set(0, PIN_A_LOW_TO_B_LOW_US * 1000);
        cycle_state = eSetPinBLow;
        break;
    case eSetPinBLow:
        SetPinB_Low();
        /* Do Stuff */
        /* no break */
    default:
        cycle_state = eIdle;
        break;
    }

    if (cycle_state != eIdle) {
        hrtimer_forward_now(var, data->period);
        local_irq_enable();
        return HRTIMER_RESTART;
    }

    local_irq_enable();
    return HRTIMER_NORESTART;
}

void StartBusCycleControl(void)
{
    SetPinA_High();
    SetPinB_High();

    data->period = ktime_set(0, PIN_A_HIGH_TO_A_LOW_US * 1000);
    cycle_state = eSetPinALow;
    hrtimer_start(&data->timer, data->period, HRTIMER_MODE_REL);
}

int InitTimer(void)
{
    data = kmalloc(sizeof(*data), GFP_KERNEL);

    if (data) {
        hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
        data->timer.function = TimerCallback;
        printk(KERN_INFO DRV_NAME
               ": %s hr timer successfully initialized\n", __func__);
        return 0;
    } else {
        printk(KERN_CRIT DRV_NAME
               ": %s failed to initialize the hr timer\n", __func__);
        return -ENOMEM;
    }
}

所以这个想法就是这样

  • 两个引脚在开始时都很高
  • hrtimer将在48微秒后过期
  • 在回调函数中,引脚A被拉低
  • 计时器向前推进24微秒
  • 第二次触发回调时,引脚B被拉低

我使用带有内核4.1.2的BeagleBoneBlack和rt-preempt补丁。

我在范围内看到的是,第一个计时器就像一个大约65-67微秒的魅力(我可以忍受)。但转发似乎有故障,因为我在引脚A变低和引脚B变低之间测量的时间在2到50微秒之间。所以从本质上讲,第二次触发回调有时会发生在我定义的24微秒之前。而且这个时间对我的用例不起作用。

什么指向我做错了什么?

c linux timer linux-kernel linux-device-driver
2个回答
3
投票

所以自己回答:这是一个错误的期望问题。

我们在此期望的是在回调期间将定时器设置为我们设置的量(24us)。但是如果我们看一下hrtimer_forward_now()we的内核实现,可以看到时间实际上是添加到计时器的最后一个事件/事件中(参见delta的计算):

来自Linux/kernel/time/hrtimer.c

833 u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval)
834 {
835         u64 orun = 1;
836         ktime_t delta;
837 
838         delta = ktime_sub(now, hrtimer_get_expires(timer));
839 
840         if (delta.tv64 < 0)
841                 return 0;
842 
843         if (WARN_ON(timer->state & HRTIMER_STATE_ENQUEUED))
844                 return 0;
845 
846         if (interval.tv64 < hrtimer_resolution)
847                 interval.tv64 = hrtimer_resolution;
848 
849         if (unlikely(delta.tv64 >= interval.tv64)) {
850                 s64 incr = ktime_to_ns(interval);
851 
852                 orun = ktime_divns(delta, incr);
853                 hrtimer_add_expires_ns(timer, incr * orun);
854                 if (hrtimer_get_expires_tv64(timer) > now.tv64)
855                         return orun;
856                 /*
857                  * This (and the ktime_add() below) is the
858                  * correction for exact:
859                  */
860                 orun++;
861         }
862         hrtimer_add_expires(timer, interval);
863 
864         return orun;
865 }

这意味着在这里不考虑定时器触发和实际执行的回调之间的时间延迟。 hrtimers在间隔时间上是精确的,并且不受发射和回调之间的通常延迟的影响。我们的期望是将时间包括在计算中,因为我们希望计时器从我们在计时器回调中执行操作的那一刻开始重新启动。

我试图将其绘制成下图:hrtimer expectation vs reality diagram

在红色编号的气泡之后我们得到:

  1. 计时器以X时间开始启动
  2. 时间X已经过去,计时器被触发
  3. 在“延迟X”之后,取决于系统的负载和其他因素,调用hrtimer的回调函数
  4. hrtimer_forward_now根据最后一个事件加上新的预期时间(未来可能只有2个而不是24个)来设置新的计时器。
  5. 这是期望与现实的差异。 hrtimer在最后一次事件发生后24小时开火,我们预计它会在打电话给forward_now()之后发射24us

总而言之,我们完全破坏了上面的代码示例,并在触发两个GPIO引脚之间进行了usleep_range()调用。该函数的底层实现也是使用hrtimer完成的,但它对用户是隐藏的,并且在这种情况下它的行为与我们期望的一样。


1
投票

我也遇到了这个问题。感谢TabascoEye的回答。我只想添加一些代码作为示例,以便于理解。

在我的应用程序中,我有一个硬件中断(30ms + -3ms间隔)来调用reactive_hrtimer()然后timer_do()将在10ms后被调用。由于进入中断的时间不规律,我需要自己实现hrtimer_add_expires()的输入。

对于不规则时间间隔:

enum hrtimer_restart timer_do(struct hrtimer *timer)
{
    /**something**/
    return HRTIMER_NORESTART;
}

void reactive_hrtimer( struct hrtimer *hr_timer, ktime_t ktime_interval)
{
    ktime_t delta;
    ktime_t now;
    now = hrtimer_cb_get_time(hr_timer);
    delta = ktime_sub(now, hrtimer_get_expires(hr_timer));

    hrtimer_add_expires(hr_timer, ktime_add(ktime_interval, delta));
    hrtimer_restart(hr_timer);
 }

这些代码可以放在任何地方而不是回调内部

© www.soinside.com 2019 - 2024. All rights reserved.