【问题标题】:Efficient way to wait for an interrupt in C在C中等待中断的有效方法
【发布时间】:2016-04-29 00:00:44
【问题描述】:

我在树莓派上使用 WiringPi。有了它,我分配了一个稍后调用的中断函数。我不确定在等待中断被调用时该怎么做。

示例使用(自旋锁?)for (;;) 例如

int main()
{
    // register interrupt
    wiringPiISR( 18, INT_EDGE_BOTH, &myInterrupt );

    for (;;) {
        // really?
    }
    return 0;
}

我注意到sleep 也有效。无论睡眠如何,都会调用中断

int main() 
{
    // register interrupt
    wiringPiISR( 18, INT_EDGE_BOTH, &myInterrupt );

    for (;;) {
        sleep(1000000);
    }
    return 0;
}

什么是保持程序运行的最佳实践,使用最少的资源(例如,如果这是用于后台恶魔)?

来自其他语言,我原以为for(;;) 会占用资源。我想知道该做什么或做什么的指针(线程等)。

【问题讨论】:

  • 一个只是休眠的for(;;) 循环不会消耗大量资源,因为一旦循环的迭代执行,它就会返回给调度程序。如果您在中断触发后不一定需要在主循环中进行任何处理,为什么不保持原样(在您的第二个示例中)主循环并在 ISR 中进行中断处理?
  • 允许编译器假设for(;;) { }(带有空体)立即终止。它已经发生了。
  • @Joker_vD:为什么要终止无限循环??
  • 在任何语言中的行为都是一样的。为什么您认为 sleep 在例如蟒蛇?
  • @Joker_vD:这与无限循环不同!它将改变抽象机器的行为。仔细阅读:port70.net/~nsz/c/c11/n1570.html#6.8.5p6(见脚注 156)。

标签: c embedded raspberry-pi


【解决方案1】:

我最近不得不这样做。我的理论是KISSsleep 根据定义编写为使用最少的资源 - 使用它意味着我不必关心线程。

在原始 Raspberry Pi B 上简单、可读且没有可衡量的性能:

int main() 
{
    // register interrupt
    wiringPiISR( 18, INT_EDGE_BOTH, &myInterrupt );

    for (;;) {
        sleep(UINT_MAX);
    }
    return 0;
}

注意使用 UINT_MAX 来最小化 for 循环调用的次数 - 这假设一个无符号的 32 位定时器延迟,这是 WiringPi 使用的。

【讨论】:

  • 这种实现定义的幻数是无稽之谈。 sleep 采用 unsigned int,因此只需使用 UINT_MAX
  • 随时提出修改建议以改进答案!
【解决方案2】:

WiringPi 设置一个单独的线程并从该线程调用您的 isr 函数。

然后您可以使用 pthread 条件变量来阻塞另一个线程,在本例中为 main(),并让 isr 函数在中断发生时将其唤醒。

#include <pthread.h>

pthread_cond_t isr_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t isr_mtx = PTHREAD_MUTEX_INITIALIZER;
unsigned int isr_count = 0;

void myInterrupt(void)
{
    pthread_mutex_lock(&isr_mtx);
    isr_count++;
    pthread_cond_signal(&isr_cond);    
    pthread_mutex_unlock(&isr_mtx);

}

int main() 
{
    // register interrupt
    wiringPiISR( 18, INT_EDGE_BOTH, &myInterrupt );

    for (;;) {
       pthread_mutex_lock(&isr_mtx);
       while (isr_count == 0) {
          pthread_cond_wait(&isr_cond, &isr_mtx);
       }
       //add logic here to handle the ISR, isr_count
       //tells you how many ISRs occured.
       //heavy work should be moved outside the mutex.

      isr_count = 0;
      pthread_mutex_unlock(&isr_mtx);
    }

    return 0;
}

您需要使用-pthread 标志编译和链接您的代码。

【讨论】:

  • 你不能调用任何可能阻塞的东西,例如。 pthread_mutex_lock(),来自中断上下文。这是非常危险的,并且会以灾难性的方式失败!
  • @MartinJames 你是 100% 正确的。但请注意,我们谈论的是 WiringPI 库。这不是真正的 ISR。它是一个与 Linux 上通过 sysfs 传递到用户空间的中断挂钩的库。 WiringPI 库产生了一个非常普通的 pthread,它通过管道接收中断发生的通知并调用提供的“ISR”函数 - 全部在用户空间中,没有中断上下文,没有有趣的业务,没有 unix 信号处理等。真正的 ISR在内核中完成。
  • 好的,@nos,我删除了我的反对票:) 请注意,我做了一个编辑,并立即撤消了一个编辑,(我只是添加了一个句号并删除了它),以便这样做'cos投票超时。
【解决方案3】:

您也可以使用semaphoresem_post()must be async-signal-safe:

#include <semaphore.h>

static sem_t staticSem;

void myInterupt( void )
{
    sem_post( &staticSem );
}

int main() 
{
    sem_init( &staticSem, 0, 0 );

    // register interrupt
    wiringPiISR( 18, INT_EDGE_BOTH, &myInterrupt );

    for (;;) {
        sem_wait( &staticSem );
        ...
    }
    return 0;
}

不包括错误检查和处理来自sem_wait() 的潜在虚假唤醒。

【讨论】:

    【解决方案4】:

    这取决于您是否需要用户级别的通知。那么有一些情况:

    1) '我根本不需要通知,因为我在中断状态下做所有事情'。好的,使用长的 Sleep() 循环在 main() 中等待,或者 Sleep(INFINITE) 如果可用,或者等待一些从未发出信号的同步对象,或者循环一些“设置低功耗状态”或“HALT”指令。这消除了从用户状态执行的需要,只是让您的处理器等待中断。

    2) '我需要用户状态的通知,但我不关心延迟'。好的,从中断状态设置一些原子 int 或布尔值,并从需要注意可能发生中断的任何 main() 或线程轮询它。这样的轮询可能很浪费并且响应缓慢,但是如果您不需要关心您的应用程序,那很好:)

    3) '我需要尽快通知用户状态'。实现这种信号的“经典”方式是让处理程序线程在信号量上等待,从中断处理程序发出信号量并“指示”操作系统它必须在中断处理程序结束后立即执行重新调度。所以使等待线程准备好/运行。其他信号机制,例如。 events/condvars,也可以安全地从中断状态发出信号,但您应该检查您的操作系统文档。

    我不能做什么?你不能,和/或不能调用任何可能试图阻塞的中断状态。那是灾难性的,您的操作系统可能会严重崩溃:(

    【讨论】:

      【解决方案5】:

      我不确定 WiringPi,但在完成任务后,使用等待由您的中断处理程序 signal领导的 pthread_cond_t 怎么样?

      This 是一个参考。

      【讨论】:

      • 没有。要使用pthread_cond_signal() 正确地向pthread_cond_t 发出信号,需要锁定互斥锁。这大约是async-signal-unsafe 尽可能。见stackoverflow.com/questions/4544234/…
      • @AndrewHenle 我的回答和锁定互斥锁之间有什么问题?我从来没有说过不应该这样做。
      • 阅读我第一条评论中的链接。要安全可靠地使用pthread_cond_signal(),需要锁定一个共享互斥体。锁定互斥锁不是异步信号安全的,因此不能在信号处理程序中安全地完成。
      • @AndrewHenle 哦,没错!很抱歉造成误解。
      • @AndrewHenle WiringPI 库没有做异步信号不安全的事情。这不是信号处理程序,它由普通的 poll() 事件循环驱动。
      【解决方案6】:

      我会(和do,在嵌入编码时)宁愿使用自旋锁while(1) ;。它直截了当并表达了意图 - 永远不要超过这一点。

      睡眠不仅有过期时间,这可能不是马上的问题,但多年后就会成为问题。它还必须执行一些计算才能实际计算时间。

      这是英特尔酷睿 i3-4330TE 上 sleep(0xFFFFFFFF); 的比较:

              .file   "test.c"
              .text
              .globl  main
              .type   main, @function
      main:
      .LFB0:
              .cfi_startproc
              pushq   %rbp
              .cfi_def_cfa_offset 16
              .cfi_offset 6, -16
              movq    %rsp, %rbp
              .cfi_def_cfa_register 6
              movl    $-1, %edi
              movl    $0, %eax
              call    sleep
              movl    $0, %eax
              popq    %rbp
              .cfi_def_cfa 7, 8
              ret
              .cfi_endproc
      .LFE0:
              .size   main, .-main
              .ident  "GCC: (Debian 4.9.2-10) 4.9.2"
              .section        .note.GNU-stack,"",@progbits
      

      while(1); 方法:

              .file   "test.c"
              .text
              .globl  main
              .type   main, @function
      main:
      .LFB0:
              .cfi_startproc
              pushq   %rbp
              .cfi_def_cfa_offset 16
              .cfi_offset 6, -16
              movq    %rsp, %rbp
              .cfi_def_cfa_register 6
      .L2:
              jmp     .L2
              .cfi_endproc
      .LFE0:
              .size   main, .-main
              .ident  "GCC: (Debian 4.9.2-10) 4.9.2"
              .section        .note.GNU-stack,"",@progbits
      

      如果不跟踪时间,工作量就会减少。在 ISR 到达之前,我不确定 linux 调度程序是否可以识别这种模式。


      话虽如此,“使用最少的资源保持程序运行”的正确方法是查看处理器提供的the sleep modes (page 2-14 or 36)

      【讨论】:

      • ' 它还必须执行一些计算才能实际计算时间。' - 当然,一次,然后它就完成了,没有更多的 CPU 周期或内存带宽被浪费在无意义的循环上。如果您真的担心过期时间,请将 Sleep() 调用置于循环中。如果处理器上没有可用的 HALT/lowPower/任何指令,则等待从未发出信号的事件/信号量是另一种无 CPU/带宽浪费解决方案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-03
      • 2012-04-01
      • 1970-01-01
      • 2018-09-04
      • 1970-01-01
      • 2022-08-07
      相关资源
      最近更新 更多