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

时间:2016-03-04 16:16:10

标签: c linux timer linux-kernel linux-device-driver

我想用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微秒。 而且这个时间对我的用例不起作用。

指出我做错了什么?

2 个答案:

答案 0 :(得分:3)

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

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

来自Linux/kernel/time/hrtimer.c

delta

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

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

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

  1. 计时器以X时间启动
  2. 启动
  3. 时间X已经过去,计时器被触发
  4. 在“延迟X”之后,取决于系统的负载和其他因素,称为hrtimer的回调函数
  5. 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 } 根据最后一个事件加上新的预期时间(未来可能只有2个而不是24个)来设置新的计时器。
  6. 这是期望与现实的差异。在最后一次事件之后,当我们预计在呼叫hrtimer_forward_now
  7. 之后它会发射24us时,rtimer会发射24us

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

答案 1 :(得分:1)

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

在我的应用程序中,我有一个硬件中断(间隔30ms + -3ms)来调用reactive_hrtimer(),然后在10ms之后将调用timer_do()。由于输入中断的时间不是很规律,因此我需要自己为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);
 }

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