2011-04-29 75 views
7

我想在頻率爲十的頻率下中斷,因此從/ dev/rtc中啓用中斷並不理想。我想在中斷之間睡1毫秒或250微秒。如何在Linux中獲得最準確的實時週期性中斷?

啓用從/ dev/HPET週期性中斷工作得很好,但它似乎並沒有在一些機器上工作。顯然,我不能在沒有HPET的機器上使用它。但是我無法在一些有可用時鐘源的機器上工作。例如,在Core 2 Quad上,設置爲輪詢時,內核文檔中包含的示例程序在HPET_IE_ON上失敗。

這將是更好的使用被Linux提供的,而不是直接與硬件設備驅動程序接口的itimer接口。在一些系統中,itimer提供了隨時間推移更穩定的週期性中斷。也就是說,由於hpet不能以我想要的頻率中斷,所以中斷開始偏離牆壁時間。但是我看到有些系統的睡眠方式比使用itimer更長(10+毫秒)。

這是一個使用itimer進行中斷的測試程序。在某些系統上,它只會打印出一個警告,它會在目標時間內睡眠大約100微秒左右。在其他情況下,它會打印出批量的警告,說它在目標時間內睡了10+毫秒。與-lrt編譯和使用sudo CHRT -f 50運行[名]

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <error.h> 
#include <errno.h> 
#include <sys/ioctl.h> 
#include <sys/types.h> 
#include <sys/time.h> 
#include <time.h> 
#include <signal.h> 
#include <fcntl.h> 
#define NS_PER_SECOND 1000000000LL 
#define TIMESPEC_TO_NS(aTime) ((NS_PER_SECOND * ((long long int) aTime.tv_sec)) \ 
    + aTime.tv_nsec) 

int main() 
{ 
    // Block alarm signal, will be waited on explicitly 
    sigset_t lAlarm; 
    sigemptyset(&lAlarm); 
    sigaddset(&lAlarm, SIGALRM ); 
    sigprocmask(SIG_BLOCK, &lAlarm, NULL); 

    // Set up periodic interrupt timer 
    struct itimerval lTimer; 
    int lReceivedSignal = 0; 

    lTimer.it_value.tv_sec = 0; 
    lTimer.it_value.tv_usec = 250; 
    lTimer.it_interval = lTimer.it_value; 

    // Start timer 
    if (setitimer(ITIMER_REAL, &lTimer, NULL) != 0) 
    { 
     error(EXIT_FAILURE, errno, "Could not start interval timer"); 
    } 
    struct timespec lLastTime; 
    struct timespec lCurrentTime; 
    clock_gettime(CLOCK_REALTIME, &lLastTime); 
    while (1) 
    { 
     //Periodic wait 
     if (sigwait(&lAlarm, &lReceivedSignal) != 0) 
     { 
      error(EXIT_FAILURE, errno, "Failed to wait for next clock tick"); 
     } 
     clock_gettime(CLOCK_REALTIME, &lCurrentTime); 
     long long int lDifference = 
      (TIMESPEC_TO_NS(lCurrentTime) - TIMESPEC_TO_NS(lLastTime)); 
     if (lDifference > 300000) 
     { 
      fprintf(stderr, "Waited too long: %lld\n", lDifference ); 
     } 
     lLastTime = lCurrentTime; 
    } 
    return 0; 
} 
+0

這可能是一個內核錯誤。我的itimer示例似乎在所有使用2.6.32的機器上都能正常工作,但在2.6.35或2.6.38上沒有問題。 – Matt 2011-04-29 15:44:39

回答

4

無論您使用的計時機制,把它歸結爲你的任務的運行狀態變化,組合調用內核調度時(通常每秒100或1000次),以及與其他進程的cpu爭用。

我發現的機制,實現Linux上的「最佳」時機(Windows和)是做到以下幾點:

  1. 放置過程上Shielded CPU
  2. 有過程開始睡覺持續1ms。如果屏蔽的CPU,你的進程應該權的OS調度器的刻度邊界
  3. 使用。可使用RDTSC直接或CLOCK_MONOTONIC在喚醒捕捉當前時間。將此用作計算所有未來期間的絕對喚醒時間的零時間。這將有助於最大限度地減少隨時間的漂移由於硬件計時隨時間波動(散熱問題等),因此無法完全消除,但這是一個非常好的開始。
  4. 創建睡1ms的短期的目標絕對喚醒時間(因爲這是最準確的OS調度器即可)睡眠功能則燒燬CPU在緊密循環不斷檢查RDTSC/CLOCK_REALTIME值。

這需要一些工作,但你可以使用這種方法可以獲得非常好的結果。您可能需要查看一個相關問題,可以找到here

+2

Linux現在可以在任務準備就緒的情況下安排任務,而不是依靠調度器運行每個jiffy(我相信這是NO_HZ選項)。 HPET方法(對read()調用進行阻塞)對於上下文切換至高實時優先級任務(如果HPET方法完全適用)每250微秒工作良好。 itimer方法通常適用於做同樣的事情。在某些機器上,它始終都能正常工作。在其他方面,它通常是有效的(我沒有充斥着警告信息),但有時我會等待1MS +。 – Matt 2011-04-29 21:14:51

4

我已經與裸setitimer()設置了同樣的問題。 問題是默認情況下,您的進程由靜態優先級爲0的SCHED_OTHER策略調度。這意味着您與其他所有流程都在一個池中,並決定動態優先級。有一些系統負載的時候,你會得到延遲。

的解決方案是使用sched_setscheduler()系統調用,提高您的靜態優先級的至少一個,並指定SCHED_FIFO策略。它引起了戲劇性的改進。

#include <sched.h> 
... 
int main(int argc, char *argv[]) 
{ 
    ... 
    struct sched_param schedp; 
    schedp.sched_priority = 1; 
    sched_setscheduler(0, SCHED_FIFO, &schedp); 
    ... 
} 

您必須以root身份運行才能執行此操作。另一種方法是使用chrt程序來執行相同的操作,但您必須知道RT進程的PID。

sudo chrt -f -p 1 <pid> 

看到我的博客文章關於它here