Linux 應用開發之定時器

來源:嵌入式應用研究院

間隔定時器

#include <sys/time.h>

int setitimer(int which,const struct itimerval* new_value,struct itimerval* old_value);

針對所有這些信號的默認處置均會終止進程,除非真地期望如此,否則就需要針對這些定時器信號創建處理器函數。

struct itimerval{
struct timeval it_interval;/* Interval for periodic timer */
struct timeval it_value;/* Current value(time until next expiration) */
};

struct timeval{
time_t tv_sec;/* Seconds */
suseconds_t tv_usec;/* Microseconds */
};

可以在任何時刻調用 getitimer(),以瞭解定時器的當前狀態,距離下次到期的剩餘時間:

#include <sys/time.h>

int getitimer(int which,struct itimerval* curr_value);

使用 setitimer()alarm() 創建的定時器可以跨越 exec() 調用而得以保存,但由  fork() 創建的子進程並不繼承該定時器。

更爲簡單的定時器接口:alarm()

#include <unistd.h>

unsigned int alarm(unsigned int seconds);

setitimer()alarm() 之間的交互

Linux 中 alarm()setitimer()  針對同一進程共享一個實時定時器,無論調用兩者之中的哪個完成了對定時器的前一設置,同樣可以調用二者中的任一函數來改變這一設置。

程序設置實時定時器時,最好選用二者之一。

定時器的調度和精度

內核配置項 CONFIG_HIGH_RES_TIMERS 可以支持高分辨率定時器,使得定時器的精度不受軟件時鐘週期的影響,可以達到底層硬件所支持的精度,在現代硬件平臺上,精度達到微秒級別是司空見慣的。

爲阻塞操作設置超時

實時定時器的用途之一就是爲某個阻塞系統調用設置其處於阻塞狀態的時間上限。

例如,處理 read() 操作:

暫停運行一段固定時間

低分辨率休眠:sleep()

#include <unistd.h>

unsigned int sleep(unsigned int seconds);

高分辨率休眠 nanosleep()

#include <time.h>

int nanosleep(const struct timespec *req, struct timespec *rem);
struct timespec {
      time_t tv_sec;       /* seconds */
      long   tv_nsec;       /* nanoseconds */
};

POSIX 時鐘

Linux 中需要使用 realtime,實時函數庫,需要鏈接 librt 即需要加入 -lrt 選項。

獲取時鐘的值

#include <time.h>

int clock_getres(clockid_t clk_id, struct timespec *res);
int clock_gettime(clockid_t clk_id, struct timespec *tp);

設置時鐘的值

#define _POSIX_C_SOURCE 199309L
#include <time.h>

int clock_settime(clockid_t clk_id, const struct timespec *tp);

獲取特定進程或線程的時鐘 ID

要測量特定進程或線程消耗的 CPU 時間,首先要獲取其時鐘 ID:

#define _XOPEN_SOURCE 600
#include <time.h>

int clock_getcpuclockid(pid_t pid, clockid_t *clock_id);
#define _XOPEN_SOURCE 600
#include <pthread.h>
#include <time.h>

int pthread_getcpuclockid(pthread_t thread, clockid_t *clock_id);

高分辨率休眠的改進版

#define _XOPEN_SOURCE 600
#include <time.h>

int clock_nanosleep(clockid_t clock_id, int flags,const struct timespec *request,struct timespec *remain);

對於那些被信號處理器中斷並使用循環重啓休眠的進程來說,"嗜睡" 問題尤其明顯,如果以高頻接收信號,那麼按相對時間休眠的進程在休眠時間上會有較大誤差,可以通過如下方式避免嗜睡:

struct timespec request;

if(clock_gettime(CLOCK_REALTIME,&request) == -1)
errExit("clock_gettime");

request.tv_sec += 20; /* sleep for 20 seconds from now*/

s = clock_nanosleep(CLOCK_REALTIME,TIMER_ABSTIME,&request,NULL);
if(s != 0)
{
if(s == EINTR)
printf("Interrupted by signal handler\n");
else
errExit("clock_nanosleep");
}

POSIX 間隔式定時器

使用 settimer() 來設置經典 UNIX 間隔式定時器,會收到如下制約:

POSIX.1b 定義了一套 API 來突破這些限制,主要包含如下幾個階段:

fork()  創建的子進程不會繼承 POSIX 定時器,調用 exec() 期間亦或是進程終止時將停止並刪除定時器。

使用 POSIX 定時器的 API 程序編譯時需要使用 -lrt  選項。

創建定時器

#define _POSIX_C_SOURCE 199309L
#include <signal.h>
#include <time.h>

int timer_create(clockid_t clockid, struct sigevent *sevp,timer_t *timerid);
union sigval {         /* Data passed with notification */
  int     sival_int;         /* Integer value */
  void   *sival_ptr;         /* Pointer value */
};

struct sigevent {
  int         sigev_notify; /* Notification method */
  int         sigev_signo; /* Notification signal */
  union sigval sigev_value; /* Data passed with notification */
  void       (*sigev_notify_function) (union sigval); /* Function used for thread notification (SIGEV_THREAD) */
  void       *sigev_notify_attributes; /* Attributes for notification thread (SIGEV_THREAD) */
  pid_t       sigev_notify_thread_id; /* ID of thread to signal (SIGEV_THREAD_ID) */
};

配備和解除定時器

#define _POSIX_C_SOURCE 199309L
#include <time.h>

int timer_settime(timer_t timerid, int flags,const struct itimerspec *new_value,struct itimerspec *old_value);
struct timespec {
  time_t tv_sec;               /* Seconds */
  long   tv_nsec;               /* Nanoseconds */
};

struct itimerspec {
  struct timespec it_interval; /* Timer interval */
  struct timespec it_value;     /* Initial expiration */
};

獲取定時器的當前值

#define _POSIX_C_SOURCE 199309L
#include <time.h>

int timer_gettime(timer_t timerid, struct itimerspec *curr_value);

刪除定時器

每個 POSIX 定時器都會消耗少量的系統資源,一旦使用完畢,應當及時釋放這些資源:

#define _POSIX_C_SOURCE 199309L
#include <time.h>

int timer_delete(timer_t timerid);

通過信號發出通知

如果選擇通過信號來接收定時器通知,那麼處理這些信號時既可以採用信號處理器函數,也可以調用 sigwaitinfo() 或是 sigtimerdwait()。接收進程藉助於這兩種方法可以獲取一個 siginfo_t 結構:

爲  evp.sigev_value 指定不同的值,可以將到期時發送同類信號的不同定時器區分開。

Linux 還爲 siginfo_t 結構提供瞭如下非標準字段:

定時器溢出

假設已經選擇通過信號傳遞的方式來接收定時器到期的通知。在捕獲或接收相關信號之前定時器到期多次,或者不論直接調用 sigprocmask() 還是在信號處理器函數中暗中處理,也都有可能堵塞相關信號的發送,那如何知道這些定時器溢出?

接收到定時器信號之後,有兩種方法可以獲取定時器的溢出值:

#define _POSIX_C_SOURCE 199309L
#include <time.h>

int timer_getoverrun(timer_t timerid);

通過線程來通知

SIGEV_THREAD 標誌允許程序從一個獨立的線程中調用函數來獲取定時器到期通知。

利用文件描述符進行通知的定時器

Linux 內核特有的創建定時器的 timerfd API,可從文件描述符中讀取其所創建定時器的到期通知。

#include <sys/timerfd.h>

int timerfd_create(int clockid, int flags);
#include <sys/timerfd.h>

int timerfd_settime(int fd, int flags,const struct itimerspec *new_value,struct itimerspec *old_value);
#include <sys/timerfd.h>

int timerfd_gettime(int fd, struct itimerspec *curr_value);

timerfd 與 fork() 以及 exec() 之間的交互

調用 fork() 期間,子進程會繼承 timerfd_create() 所創建的文件描述符的拷貝。

timerfd_create() 創建額度文件描述符能夠跨越 exec() 得以保存,除非將描述符設置爲運行時關閉,已配備的定時器在 exec() 之後會繼承生成到期通知。

從 timerfd 文件描述符讀取

一旦以 timer_settime() 啓動了定時器,就可以從相應文件描述符中調用 read() 來讀取定時器的到期信息,處於這一目的,傳給 read() 的緩衝區必須滿足容納一個 uint64_t 類型的要求。

在上次使用 timerfd_settime() 修改設置以後,或者是最後一次執行 read()  後,如果發生了一起或多起定時器到期時間,那麼 read() 立即返回,返回的緩衝區中包含了到期的次數。

如果並無定時器到期,read() 將會阻塞至下一個到期。

也可以執行 fcntl() 設置 O_NONBLOCK 標誌,這時的讀動作將是非阻塞的,如果沒有定時器到期,則返回,設置錯誤 EAGAIN

可以使用 select()poll()epoll()timerfd 文件描述符進行監控,如果定時器到期,則將對應的文件描述符標記爲可讀。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/KgnW2WEyAvdpgNTPKapeWw