在linux系統中定時器有分為軟定時和硬體定時器,硬體定時器一般指的是CPU的一種底層寄存器,它負責按照固定時間頻率產生中斷信號,形成信號源。基於硬體提供的信號源,系統就可以按照信號中斷來計數,計數在固定頻率下對應固定的時間,根據預設的時間參數即可產生定時中斷信號,這就是軟定時。 ...
以下內容為本人的著作,如需要轉載,請聲明原文鏈接 微信公眾號「englyf」https://www.cnblogs.com/englyf/p/16651865.html
曾經常去沙縣小吃,就為了蹭上一碗4塊錢的蔥油拌面,聽著邊上的幾位小哥老說
華仔,有軟硬之分。
其實寫代碼也有這種講究。
在linux系統中定時器有分為軟定時和硬體定時器,硬體定時器一般指的是CPU的一種底層寄存器,它負責按照固定時間頻率產生中斷信號,形成信號源。基於硬體提供的信號源,系統就可以按照信號中斷來計數,計數在固定頻率下對應固定的時間,根據預設的時間參數即可產生定時中斷信號,這就是軟定時。
這裡主要講軟定時器,而硬體定時器涉及到硬體手冊這裡略過。
1. 利用內核節拍器相關定時器實現定時
linux內核有可調節的系統節拍,由於節拍依據硬體定時器的定時中斷計數得來,節拍頻率設定後,節拍周期恆定,根據節拍數可以推得精確時間。從系統啟動以來記錄的節拍數存放在全局變數jiffies中,系統啟動時自動設置jiffies為0。
#include <linux/jiffies.h>
高節拍數可以計算更高的時間精度,但是會頻繁觸發系統中斷,犧牲系統效率。
定義定時器
struct timer_list {
struct list_head entry; // 定時器鏈表的入口
unsigned long expires; // 定時器超時節拍數
struct tvec_base *base; // 定時器內部值,用戶不要使用
void (*function)(unsigned long); // 定時處理函數
unsigned long data; // 要傳遞給定時處理函數的參數
int slack;
};
設置節拍數expires時,可以使用函數msecs_to_jiffies將毫秒值轉化為節拍數。
初始化定時器
void init_timer(struct timer_list *timer);
註冊定時器到內核,並啟動
void add_timer(struct timer_list *timer);
刪除定時器
int del_timer(struct timer_list *timer);
如果程式運行在多核處理器上,此函數有可能導致運行出錯,建議改用del_timer_sync。
同步刪除定時器
int del_timer_sync(struct timer_list *timer);
如果程式運行在多處理器上,此函數會等待其它處理器對此定時器的操作完成。另外,此函數不能用在中斷上下文中。
修改定時值並啟動定時器
int mod_timer(struct timer_list *timer, unsigned long expires);
註意:在應用層開發過程中,一般不會使用內核的函數來設定定時器。
2. 應用層的alarm鬧鐘
在應用層開發時,設置鬧鐘參數,並啟動鬧鐘定時器非常方便
#include<unistd.h>
unsigned int alarm(unsigned int seconds);
註意:每個進程只允許設置一個鬧鐘,重覆設置會覆蓋前一個鬧鐘。
當時間到達seconds秒後,會有SIGALRM信號發送給當前進程,可以通過函數signal註冊該信號的回調處理函數callback_fun
#include <signal.h>
typedef void (*sig_t)(int);
sig_t signal(int signum, sig_t handler);
3. 利用POSIX中內置的定時器介面
設定鬧鐘適用的情形比較簡單,而為了更靈活地使用定時功能,可以用到POSIX中的定時器功能。
創建定時器
#include <time.h>
int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
通過clock_id可以指定時鐘源,evp傳入超時通知配置參數,timerid返回被創建的定時器的id。evp如果為NULL,超時觸發時,預設發送信號SIGALRM通知進程。
clock_id是枚舉值,如下
CLOCK_REALTIME :Systemwide realtime clock.
CLOCK_MONOTONIC:Represents monotonic time. Cannot be set.
CLOCK_PROCESS_CPUTIME_ID :High resolution per-process timer.
CLOCK_THREAD_CPUTIME_ID :Thread-specific timer.
CLOCK_REALTIME_HR :High resolution version of CLOCK_REALTIME.
CLOCK_MONOTONIC_HR :High resolution version of CLOCK_MONOTONIC.
結構體sigevent
union sigval
{
int sival_int; //integer value
void *sival_ptr; //pointer value
}
struct sigevent
{
int sigev_notify; //notification type
int sigev_signo; //signal number
union sigval sigev_value; //signal value
void (*sigev_notify_function)(union sigval);
pthread_attr_t *sigev_notify_attributes;
}
類型timer_t
#ifndef _TIMER_T
#define _TIMER_T
typedef int timer_t; /* timer identifier type */
#endif /* ifndef _TIMER_T */
設置定時器,比如初次觸發時間,迴圈觸發的周期等。設置完成後啟動定時器。
int timer_settime(timer_t timerid, int flags, const struct itimerspec *value, struct itimerspec *ovalue);
struct timespec{
time_t tv_sec;
long tv_nsec;
};
struct itimerspec {
struct timespec it_interval;
struct timespec it_value;
};
獲取定時剩餘時間
int timer_gettime(timer_t timerid, struct itimerspec *value);
獲取定時器超限的次數
int timer_getoverrun(timer_t timerid);
定時器超時後發送的同一個信號如果掛起未處理,那麼在下次超時發生後,上一個信號會丟失,這就是定時器的超限。
刪除定時器
int timer_delete (timer_t timerid);
示例:超時觸發信號
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
void sig_handler(int signo)
{
time_t t;
char str[32];
time(&t);
strftime(str, sizeof(str), "%T", localtime(&t));
printf("handler %s::%d\n", str, signo);
}
int main()
{
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = sig_handler;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
if (sigaction(SIGUSR1, &act, NULL) == -1) {
perror("fail to sigaction");
exit(-1);
}
timer_t timerid;
struct sigevent evp;
memset(&evp, 0, sizeof(evp));
// 定時器超時觸發信號 SIGUSR1
evp.sigev_notify = SIGEV_SIGNAL;
evp.sigev_signo = SIGUSR1;
if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1) {
perror("fail to timer_create");
exit(-1);
}
// 設置初始觸發時間4秒,之後每2秒再次觸發
struct itimerspec its;
its.it_value.tv_sec = 4;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 2;
its.it_interval.tv_nsec = 0;
if (timer_settime(timerid, 0, &its, 0) == -1) {
perror("fail to timer_settime");
exit(-1);
}
while(1);
return 0;
}
上面的代碼中註冊信號響應回調用了函數sigaction,其實這裡用函數signal也可以的。
示例:超時啟動子線程
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
void timer_thread(union sigval v)
{
time_t t;
char str[32];
time(&t);
strftime(str, sizeof(str), "%T", localtime(&t));
printf("timer_thread %s::%d\n", str, v.sival_int);
}
int main()
{
timer_t timerid;
struct sigevent evp;
memset(&evp, 0, sizeof(evp));
evp.sigev_notify = SIGEV_THREAD;
evp.sigev_value.sival_int = 123;
evp.sigev_notify_function = timer_thread;
if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1) {
perror("fail to timer_create");
exit(-1);
}
struct itimerspec its;
its.it_value.tv_sec = 4;
its.it_value.tv_nsec = 0;
its.it_interval.tv_sec = 2;
its.it_interval.tv_nsec = 0;
if (timer_settime(timerid, 0, &its, 0) == -1) {
perror("fail to timer_settime");
exit(-1);
}
while(1);
return 0;
}