在 Linux 中創建定時器
對開發人員來說,定時某些事件是一項常見任務。定時器的常見場景是看門狗、任務的循環執行,或在特定時間安排事件。在這篇文章中,我將演示如何使用 timer_create(...) 創建一個 POSIX 兼容的間隔定時器。
你可以從 GitHub 下載下面樣例的源代碼。
準備 Qt Creator
我使用 Qt Creator 作為該樣例的 IDE。為了在 Qt Creator 運行和調試樣例代碼,請克隆 GitHub 上的倉庫,打開 Qt Creator,在 「 文件 -> 打開文件或項目…… 」 並選擇 「CMakeLists.txt」:
在 Qt Creator 中打開項目
選擇工具鏈之後,點擊 「 配置項目 」。這個項目包括三個獨立的樣例(我們在這篇文章中將只會用到其中的兩個)。使用綠色標記出來的菜單,可以在每個樣例的配置之間切換,並為每個樣例激活在終端運行 「 在終端中運行 」(用黃色標記)。當前用於構建和調試的活動示例可以通過左下角的「 調試 」 按鈕進行選擇(參見下面的橙色標記)。
項目配置
線程定時器
讓我們看看 simple_threading_timer.c
樣例。這是最簡單的一個。它展示了一個調用了超時函數 expired
的間隔定時器是如何被創建的。在每次過期時,都會創建一個新的線程,在其中調用函數 expired
:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
void expired(union sigval timer_data);
pid_t gettid(void);
struct t_eventData{
int myData;
};
int main()
{
int res = 0;
timer_t timerId = 0;
struct t_eventData eventData = { .myData = 0 };
/* sigevent 指定了過期時要執行的操作 */
struct sigevent sev = { 0 };
/* 指定啟動延時時間和間隔時間
* it_value和it_interval 不能為零 */
struct itimerspec its = { .it_value.tv_sec = 1,
.it_value.tv_nsec = 0,
.it_interval.tv_sec = 1,
.it_interval.tv_nsec = 0
};
printf("Simple Threading Timer - thread-id: %dn", gettid());
sev.sigev_notify = SIGEV_THREAD;
sev.sigev_notify_function = &expired;
sev.sigev_value.sival_ptr = &eventData;
/* 創建定時器 */
res = timer_create(CLOCK_REALTIME, &sev, &timerId);
if (res != 0){
fprintf(stderr, "Error timer_create: %sn", strerror(errno));
exit(-1);
}
/* 啟動定時器 */
res = timer_settime(timerId, 0, &its, NULL);
if (res != 0){
fprintf(stderr, "Error timer_settime: %sn", strerror(errno));
exit(-1);
}
printf("Press ETNER Key to Exitn");
while(getchar()!='n'){}
return 0;
}
void expired(union sigval timer_data){
struct t_eventData *data = timer_data.sival_ptr;
printf("Timer fired %d - thread-id: %dn", ++data->myData, gettid());
}
這種方法的優點是在代碼和簡單調試方面用量小。缺點是由於到期時創建新線程而增加額外的開銷,因此行為不太確定。
中斷信號定時器
超時定時器通知的另一種可能性是基於 內核信號。內核不是在每次定時器過期時創建一個新線程,而是向進程發送一個信號,進程被中斷,並調用相應的信號處理程序。
由於接收信號時的默認操作是終止進程(參考 signal 手冊頁),我們必須要提前設置好 Qt Creator,以便進行正確的調試。
當被調試對象接收到一個信號時,Qt Creator 的默認行為是:
- 中斷執行並切換到調試器上下文。
- 顯示一個彈出窗口,通知用戶接收到信號。
這兩種操作都不需要,因為信號的接收是我們應用程序的一部分。
Qt Creator 在後台使用 GDB。為了防止 GDB 在進程接收到信號時停止執行,進入 「 工具 -> 選項 」 菜單,選擇 「 調試器 」,並導航到 「 本地變數和表達式 」。添加下面的表達式到 「 定製調試助手 」:
handle SIG34 nostop pass
Sig 34 時不停止
你可以在 GDB 文檔 中找到更多關於 GDB 信號處理的信息。
接下來,當我們在信號處理程序中停止時,我們要抑制每次接收到信號時通知我們的彈出窗口:
Signal 34 彈出窗口
為此,導航到 「GDB」 標籤並取消勾選標記的複選框:
定時器信號窗口
現在你可以正確的調試 signal_interrupt_timer
。真正的信號定時器的實施會更複雜一些:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#define UNUSED(x) (void)(x)
static void handler(int sig, siginfo_t *si, void *uc);
pid_t gettid(void);
struct t_eventData{
int myData;
};
int main()
{
int res = 0;
timer_t timerId = 0;
struct sigevent sev = { 0 };
struct t_eventData eventData = { .myData = 0 };
/* 指定收到信號時的操作 */
struct sigaction sa = { 0 };
/* 指定啟動延時的時間和間隔時間 */
struct itimerspec its = { .it_value.tv_sec = 1,
.it_value.tv_nsec = 0,
.it_interval.tv_sec = 1,
.it_interval.tv_nsec = 0
};
printf("Signal Interrupt Timer - thread-id: %dn", gettid());
sev.sigev_notify = SIGEV_SIGNAL; // Linux-specific
sev.sigev_signo = SIGRTMIN;
sev.sigev_value.sival_ptr = &eventData;
/* 創建定時器 */
res = timer_create(CLOCK_REALTIME, &sev, &timerId);
if ( res != 0){
fprintf(stderr, "Error timer_create: %sn", strerror(errno));
exit(-1);
}
/* 指定信號和處理程序 */
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = handler;
/* 初始化信號 */
sigemptyset(&sa.sa_mask);
printf("Establishing handler for signal %dn", SIGRTMIN);
/* 註冊信號處理程序 */
if (sigaction(SIGRTMIN, &sa, NULL) == -1){
fprintf(stderr, "Error sigaction: %sn", strerror(errno));
exit(-1);
}
/* 啟動定時器 */
res = timer_settime(timerId, 0, &its, NULL);
if ( res != 0){
fprintf(stderr, "Error timer_settime: %sn", strerror(errno));
exit(-1);
}
printf("Press ENTER to Exitn");
while(getchar()!='n'){}
return 0;
}
static void
handler(int sig, siginfo_t *si, void *uc)
{
UNUSED(sig);
UNUSED(uc);
struct t_eventData *data = (struct t_eventData *) si->_sifields._rt.si_sigval.sival_ptr;
printf("Timer fired %d - thread-id: %dn", ++data->myData, gettid());
}
與線程定時器相比,我們必須初始化信號並註冊一個信號處理程序。這種方法性能更好,因為它不會導致創建額外的線程。因此,信號處理程序的執行也更加確定。缺點顯然是正確調試需要額外的配置工作。
總結
本文中描述的兩種方法都是接近內核的定時器的實現。不過,即使 timer_create(...) 函數是 POSIX 規範的一部分,由於數據結構的細微差別,也不可能在 FreeBSD 系統上編譯樣例代碼。除了這個缺點之外,這種實現還為通用計時應用程序提供了細粒度控制。
via: https://opensource.com/article/21/10/linux-timers
作者:Stephan Avenwedde 選題:lujun9972 譯者:FigaroCao 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive