2012-12-16 55 views
0

有人請解釋一下如何在Linux下使用clock_gettime來製作倒數計時器。我知道你可以使用clock()函數來獲得cpu時間,然後乘以CLOCKS_PER_SEC來獲得實際時間,但我被告知clock()函數並不適合這一點。如何使用clock_gettime製作精確的倒數計時器?

到目前爲止,我已經嘗試這(一十億是暫停一秒)

#include <stdio.h> 
#include <time.h> 

#define BILLION 1000000000 

int main() 
{ 
struct timespec rawtime; 
clock_gettime(CLOCK_MONOTONIC_RAW, &rawtime); 
unsigned long int current = (rawtime.tv_sec + rawtime.tv_nsec); 
unsigned long int end = ((rawtime.tv_sec + rawtime.tv_nsec) + BILLION); 
while (current < end) 
{ 
    clock_gettime(CLOCK_MONOTONIC_RAW, &rawtime); 
    current = (rawtime.tv_sec + rawtime.tv_nsec); 
} 

return 0; 
} 

我知道這不會是對自己非常有用,但一旦我發現瞭如何時間正確地,我可以在我的項目中使用它。我知道sleep()可以用於這個目的,但我想自己編寫計時器,這樣我就可以更好地將它集成到我的項目中 - 例如可以返回剩餘的時間,而不是暫停整個程序。

+0

是的,我已經使用-lrt – 2012-12-16 19:21:10

+1

鏈接它歡迎來到程序員。請花點時間閱讀該網站的[常見問題],您將在這裏找到關於提問的一些好信息。在處理代碼實現時,這個問題可能更適合於SO。請不要在那裏重新提問,因爲這可以遷移。遵循的一般規則是如果你的問題在你的IDE之前,它就屬於SO。如果它在白板前面,則屬於程序員。 – Walter

回答

4

請不要這樣做。在busy loop中,您正在刻錄CPU電源。

爲什麼不使用nanosleep()函數呢?它非常適合您所概述的用例。或者,如果你想更簡單的界面,或許像

#define _POSIX_C_SOURCE 200809L 
#include <time.h> 
#include <errno.h> 

/* Sleep for the specified number of seconds, 
* and return the time left over. 
*/ 
double dsleep(const double seconds) 
{ 
    struct timespec req, rem; 

    /* No sleep? */ 
    if (seconds <= 0.0) 
     return 0.0; 

    /* Convert to seconds and nanoseconds. */ 
    req.tv_sec = (time_t)seconds; 
    req.tv_nsec = (long)((seconds - (double)req.tv_sec) * 1000000000.0); 

    /* Take care of any rounding errors. */ 
    if (req.tv_nsec < 0L) 
     req.tv_nsec = 0L; 
    else 
    if (req.tv_nsec > 999999999L) 
     req.tv_nsec = 999999999L; 

    /* Do the nanosleep. */ 
    if (nanosleep(&req, &rem) != -1) 
     return 0.0; 

    /* Error? */ 
    if (errno != EINTR) 
     return 0.0; 

    /* Return remainder. */ 
    return (double)rem.tv_sec + (double)rem.tv_nsec/1000000000.0; 
} 

不同的是,使用這一個CPU可以自由地做其他的事情,而不是像旋轉速度一個瘋狂松鼠。

+0

感謝名義動物,做我需要的!你是否認爲這樣做並不會完全暫停執行 - 因此程序可以繼續正常運行,並在達到定時器時打印語句 – Joshun

+1

當然。使用修改例如信號的信號處理程序一個全局的'volatile sig_atomic_t'變量。使用'timer_create()'創建一個計時器(見http://www.kernel.org/doc/man-pages/online/pages/man2/timer_create.2.html),然後根據需要使用'timer_settime ()'(見http://www.kernel.org/doc/man-pages/online/pages/man2/timer_settime.2.html)。信號傳遞給你的信號處理程序會中斷阻塞I/O函數,所以你必須用'errno == EINTR'來處理'-1'的阻塞函數。我喜歡在觸發後立即重置中斷,所以我不會錯過任何事件。 –

+0

我建議您想出一個好的,簡單的示例案例或程序,並將其作爲新主題發佈。只是讓它有趣,所以我們不會錯過它:)。 –

2

這不是一個答案,而是一個如何使用信號和POSIX定時器來實現超時定時器的例子;意在作爲對OP對後續問題的迴應,並對已接受的答案發表評論。

#define _POSIX_C_SOURCE 200809L 
#include <stdlib.h> 
#include <signal.h> 
#include <time.h> 
#include <errno.h> 
#include <string.h> 
#include <stdio.h> 

/* Timeout timer. 
*/ 
static timer_t     timeout_timer; 
static volatile sig_atomic_t timeout_state = 0; 
static volatile sig_atomic_t timeout_armed = 2; 
static const int    timeout_signo = SIGALRM; 

#define TIMEDOUT() (timeout_state != 0) 

/* Timeout signal handler. 
*/ 
static void timeout_handler(int signo, siginfo_t *info, void *context __attribute__((unused))) 
{ 
    if (timeout_armed == 1) 
     if (signo == timeout_signo && info && info->si_code == SI_TIMER) 
      timeout_state = ~0; 
} 

/* Unset timeout. 
* Returns nonzero if timeout had expired, zero otherwise. 
*/ 
static int timeout_unset(void) 
{ 
    struct itimerspec t; 
    const int   retval = timeout_state; 

    /* Not armed? */ 
    if (timeout_armed != 1) 
     return retval; 

    /* Disarm. */ 
    t.it_value.tv_sec = 0; 
    t.it_value.tv_nsec = 0; 
    t.it_interval.tv_sec = 0; 
    t.it_interval.tv_nsec = 0; 
    timer_settime(timeout_timer, 0, &t, NULL); 

    return retval; 
} 

/* Set timeout (in wall clock seconds). 
* Cancels any pending timeouts. 
*/ 
static int timeout_set(const double seconds) 
{ 
    struct itimerspec t; 

    /* Uninitialized yet? */ 
    if (timeout_armed == 2) { 
     struct sigaction act; 
     struct sigevent  evt; 

     /* Use timeout_handler() for timeout_signo signal. */ 
     sigemptyset(&act.sa_mask); 
     act.sa_sigaction = timeout_handler; 
     act.sa_flags = SA_SIGINFO; 

     if (sigaction(timeout_signo, &act, NULL) == -1) 
      return errno; 

     /* Create a monotonic timer, delivering timeout_signo signal. */ 
     evt.sigev_value.sival_ptr = NULL; 
     evt.sigev_signo = timeout_signo; 
     evt.sigev_notify = SIGEV_SIGNAL; 

     if (timer_create(CLOCK_MONOTONIC, &evt, &timeout_timer) == -1) 
      return errno; 

     /* Timeout is initialzied but unarmed. */ 
     timeout_armed = 0; 
    } 

    /* Disarm timer, if armed. */ 
    if (timeout_armed == 1) { 

     /* Set zero timeout, disarming the timer. */ 
     t.it_value.tv_sec = 0; 
     t.it_value.tv_nsec = 0; 
     t.it_interval.tv_sec = 0; 
     t.it_interval.tv_nsec = 0; 

     if (timer_settime(timeout_timer, 0, &t, NULL) == -1) 
      return errno; 

     timeout_armed = 0; 
    } 

    /* Clear timeout state. It should be safe (no pending signals). */ 
    timeout_state = 0; 

    /* Invalid timeout? */ 
    if (seconds <= 0.0) 
     return errno = EINVAL; 

    /* Set new timeout. Check for underflow/overflow. */ 
    t.it_value.tv_sec = (time_t)seconds; 
    t.it_value.tv_nsec = (long)((seconds - (double)t.it_value.tv_sec) * 1000000000.0); 
    if (t.it_value.tv_nsec < 0L) 
     t.it_value.tv_nsec = 0L; 
    else 
    if (t.it_value.tv_nsec > 999999999L) 
     t.it_value.tv_nsec = 999999999L; 

    /* Set it repeat once every millisecond, just in case the initial 
    * interrupt is missed. */ 
    t.it_interval.tv_sec = 0; 
    t.it_interval.tv_nsec = 1000000L; 

    if (timer_settime(timeout_timer, 0, &t, NULL) == -1) 
     return errno; 

    timeout_armed = 1; 

    return 0; 
} 


int main(void) 
{ 
    char *line = NULL; 
    size_t size = 0; 
    ssize_t len; 

    fprintf(stderr, "Please supply input. The program will exit automatically if\n"); 
    fprintf(stderr, "it takes more than five seconds for the next line to arrive.\n"); 
    fflush(stderr); 

    while (1) { 

     if (timeout_set(5.0)) { 
      const char *const errmsg = strerror(errno); 
      fprintf(stderr, "Cannot set timeout: %s.\n", errmsg); 
      return 1; 
     } 

     len = getline(&line, &size, stdin); 
     if (len == (ssize_t)-1) 
      break; 

     if (len < (ssize_t)1) { 
      /* This should never occur (except for -1, of course). */ 
      errno = EIO; 
      break; 
     } 

     /* We do not want *output* to be interrupted, 
     * so we cancel the timeout. */ 
     timeout_unset(); 

     if (fwrite(line, (size_t)len, 1, stdout) != 1) { 
      fprintf(stderr, "Error writing to standard output.\n"); 
      fflush(stderr); 
      return 1; 
     } 
     fflush(stdout); 

     /* Next line. */ 
    } 

    /* Remember to cancel the timeout. Also check it. */ 
    if (timeout_unset()) 
     fprintf(stderr, "Timed out.\n"); 
    else 
    if (ferror(stdin) || !feof(stdin)) 
     fprintf(stderr, "Error reading standard input.\n"); 
    else 
     fprintf(stderr, "End of input.\n"); 

    fflush(stderr); 

    /* Free line buffer. */ 
    free(line); 
    line = NULL; 
    size = 0; 

    /* Done. */ 
    return 0; 
} 

如果保存如上timer.c,則可以使用例如編譯它

gcc -W -Wall -O3 -std=c99 -pedantic timer.c -lrt -o timer 

並使用./timer運行它。

如果仔細閱讀上面的代碼,您會發現它實際上是一個週期性定時器信號(以毫秒爲間隔),在第一個信號之前有一個可變延遲。這只是我喜歡用來確保我不會錯過信號的技術。 (信號重複,直到超時未設置。)

請注意,儘管您可以在信號處理程序中進行計算,但您應該只使用異步信號安全;見man 7 signal。另外,只有sig_atomic_t類型是原子型的。普通的單線程代碼和一個信號處理程序。所以,最好只用信號作爲指標,並在自己的程序中做實際的代碼。

如果您想要更新信號處理程序中的怪物座標,這是可能的,但有點棘手。我會用三個包含怪物信息的數組,並使用GCC __sync_bool_compare_and_swap()來更新數組指針 - 與圖形中的三重緩衝非常相似。

如果您需要多個併發超時,則可以使用多個定時器(有多個定時器可用),但最好的選擇是定義超時槽。 (您可以使用生成計數器來檢測「已忘記」的超時等等。)每當設置或取消設置新超時時,都會更新超時以反映下一個超時過期。這是一個更多的代碼,但是真的是上述的直接擴展。

+0

謝謝,我會仔細查看代碼並嘗試理解它 – Joshun