Linux中國

在 Linux 中創建定時器

對開發人員來說,定時某些事件是一項常見任務。定時器的常見場景是看門狗、任務的循環執行,或在特定時間安排事件。在這篇文章中,我將演示如何使用 timer_create(...) 創建一個 POSIX 兼容的間隔定時器

你可以從 GitHub 下載下面樣例的源代碼。

準備 Qt Creator

我使用 Qt Creator 作為該樣例的 IDE。為了在 Qt Creator 運行和調試樣例代碼,請克隆 GitHub 上的倉庫,打開 Qt Creator,在 「 文件 File -> 打開文件或項目…… Open File or Project... 」 並選擇 「CMakeLists.txt」:

Qt Creator open project

在 Qt Creator 中打開項目

選擇工具鏈之後,點擊 「 配置項目 Configure Project 」。這個項目包括三個獨立的樣例(我們在這篇文章中將只會用到其中的兩個)。使用綠色標記出來的菜單,可以在每個樣例的配置之間切換,並為每個樣例激活在終端運行 「 在終端中運行 Run in terminal 」(用黃色標記)。當前用於構建和調試的活動示例可以通過左下角的「 調試 Debug 」 按鈕進行選擇(參見下面的橙色標記)。

Project configuration

項目配置

線程定時器

讓我們看看 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()!=&apos;n&apos;){}
    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 在進程接收到信號時停止執行,進入 「 工具 Tools -> 選項 Options 」 菜單,選擇 「 調試器 Debugger 」,並導航到 「 本地變數和表達式 Locals & Expressions 」。添加下面的表達式到 「 定製調試助手 Debugging Helper Customization 」:

handle SIG34 nostop pass

Signal no stop with error

Sig 34 時不停止

你可以在 GDB 文檔 中找到更多關於 GDB 信號處理的信息。

接下來,當我們在信號處理程序中停止時,我們要抑制每次接收到信號時通知我們的彈出窗口:

Signal 34 pop up box

Signal 34 彈出窗口

為此,導航到 「GDB」 標籤並取消勾選標記的複選框:

Timer signal windows

定時器信號窗口

現在你可以正確的調試 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()!=&apos;n&apos;){}
    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

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國