上一篇 linux 下 alarm(), setitimer 定時器與 POSIX 定時器 timer_settime()對比總結 (上)總結了 alarm 和setitimer定時器的用法和注意事項。alarm不適用於精度要求比較高的場景,而setitimer也有一個缺點:因爲setitimer 是配合SIGALRM中斷信號使用的,而SIGALRM的中斷信號會終止sleep,因爲sleep就是用SIGALRM信號量實現的,因此也就有了POSIX 定時器出場的機會:
POSIX 定時器 主要分創建,初始化以及刪除三個操作,對應的函數分別爲:
timer_create()(創建定時器)
timer_settime()(初始化定時器)
timer_delete(銷燬定時器)
首先需要創建一個定時器對象,設置通知類型,一般包括信號、脈衝或線程創建,並創建通知結構(結構sigevent),設置定時類型 (相對與絕對,一次與週期),最後啓動它。
1. 創建定時器
int timer_create(clockid_t clock_id, struct sigevent *evp, timer_t *timerid)
進程可以通過調用timer_create()創建特定的定時器,定時器是每個進程自己的,不是在fork時繼承的。clock_id說明定時器是基於哪個時鐘的,*timerid裝載的是被創建的定時器的ID。該函數創建了定時器,並將他的ID 放入timerid指向的位置中。參數evp指定了定時器到期要產生的異步通知。如果evp爲NULL,那麼定時器到期會產生默認的信號,對 CLOCK_REALTIMER來說,默認信號就是SIGALRM。如果要產生除默認信號之外的其它信號,程序必須將 evp->sigev_signo設置爲期望的信號碼。struct sigevent 結構中的成員evp->sigev_notify說明了定時器到期時應該採取的行動。通常,這個成員的值爲SIGEV_SIGNAL,這個值說明在定時器到期時,會產生一個信號。程序可以將成員evp->sigev_notify設爲SIGEV_NONE來防止定時器到期時產生信號。
如果幾個定時器產生了同一個信號,處理程序可以用 evp->sigev_value來區分是哪個定時器產生了信號。要實現這種功能,程序必須在爲信號安裝處理程序時,使用struct sigaction的成員sa_flags中的標誌符SA_SIGINFO。
clock_id取值爲以下:
CLOCK_REALTIME :Systemwide realtime clock. 標準POSIX定義的時鐘,如果它處於省電模式,基於這個時鐘的定時器可以喚醒處理器
CLOCK_SOFTTIME: 這個時鐘只在處理器不處於節電模式時才激活。例如,使用CLOCK_SOFTTIME計時器休眠的應用程序不會在應用程序應該喚醒時喚醒處理器。這將允許處理器進入省電模式。
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數據結構的指針。這個數據結構用於通知內核,在“觸發”時,計時器應該傳遞什麼類型的事件。
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;
}
union sigval
{
int sival_int; //integer value
void *sival_ptr; //pointer value
}
通過將evp->sigev_notify設定爲如下值來定製定時器到期後的行爲:
SIGEV_NONE:什麼都不做,只提供通過timer_gettime和timer_getoverrun查詢超時信息。
SIGEV_SIGNAL: 當定時器到期,內核會將sigev_signo所指定的信號傳送給進程。在信號處理程序中,si_value會被設定會sigev_value。
SIGEV_THREAD: 當定時器到期,內核會(在此進程內)以sigev_notification_attributes爲線程屬性創建一個線程,並且讓它執行sigev_notify_function,傳入sigev_value作爲爲一個參數。
2. 設置定時器
timer_create()所創建的定時器並未啓動。要將它關聯到一個到期時間以及啓動時鐘週期。使用timer_settime()的參數組合完成,timer_settime()函數用於實際啓動計時器:
int timer_settime (timer_t timerid, int flags, struct itimerspec *value, struct itimerspec *oldvalue);
timerid參數是從timer_create()函數調用中得到的值,創建一組計時器,然後在它們上分別調用timer_settime()來設置和啓動它們。
flags參數就是指定絕對與相對標誌。如果傳遞常量TIMER_ABSTIME,則value所指定的時間值會被解讀成絕對值(此值的默認的解讀方式爲相對於當前的時間)。這個經修改的行爲可避免取得當前時間、計算“該時間”與“所期望的未來時間”的相對差額以及啓動定時器期間造成競爭條件。在希望計時器關閉的時候傳遞實際的日期和時間。如果傳遞一個0,那麼計時器將被視爲相對於當前時間。
value 參數指定時間。以下是兩個數據結構的關鍵部分():
struct timespec
{ long tv_sec,
tv_nsec;
};
struct itimerspec
{
struct timespec it_value,//單次啓動時間
it_interval;//定時時間
};
如同settimer(),it_value用於指定當前的定時器到期時間。當定時器到期,it_value的值會被更新成it_interval 的值。如果it_interval的值爲0,則定時器不是一個時間間隔定時器,一旦it_value到期就會回到未啓動狀態。timespec的結構提供了納秒級分辨率:
ovalue的值不是NULL,則之前的定時器到期時間會被存入其所提供的itimerspec。如果定時器之前處在未啓動狀態,則此結構的成員全都會被設定成0。
獲得一個活動定時器的剩餘時間:
int timer_gettime(timer_t timerid,struct itimerspec *value);
取得一個定時器的超限運行次數:
有可能一個定時器到期了,而同一定時器上一次到期時產生的信號還處於掛起狀態。在這種情況下,其中的一個信號可能會丟失。這就是定時器超限。程序可以通過調用timer_getoverrun來確定一個特定的定時器出現這種超限的次數。定時器超限只能發生在同一個定時器產生的信號上。由多個定時器,甚至是那些使用相同的時鐘和信號的定時器,所產生的信號都會排隊而不會丟失。
int timer_getoverrun(timer_t timerid);
執行成功時,timer_getoverrun()會返回定時器初次到期與通知進程(例如通過信號)定時器已到期之間額外發生的定時器到期次數。舉例來說,在我們之前的例子中,一個1ms的定時器運行了10ms,則此調用會返回9。如果超限運行的次數等於或大於DELAYTIMER_MAX,則此調用會返回DELAYTIMER_MAX。
執行失敗時,此函數會返回-1並將errno設定會EINVAL,這個唯一的錯誤情況代表timerid指定了無效的定時器
3. 刪除定時器
int timer_delete (timer_t timerid);
一次成功的timer_delete()調用會銷燬關聯到timerid的定時器並且返回0。執行失敗時,此調用會返回-1並將errno設定會 EINVAL,這個唯一的錯誤情況代表timerid不是一個有效的定時器。
4. 示例
4.1 採用通知方式爲信號(signal)的處理方式
4.1.1:使用SIGALRM信號量定時
上一篇講到,如果使用信號量SIGALRM,(對 CLOCK_REALTIMER來說,默認信號就是SIGALRM)sleep()函數使用的就是實時時鐘CLOCK_REALTIMER
所以使用信號值SIGALRM會中斷sleep(int second)函數的休眠;
下面給出一個例子:
//timercreate_demo.cpp
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
void SignHandler(int iSignNo);
void testTimerSign();
void printTime();
int main() {
testTimerSign();
while(true){
int left = sleep(5);
printTime();
printf("sleep(5)(left=%d)\n", left);
}
return 0;
}
void SignHandler(int iSignNo){
//printTime();
if(iSignNo == SIGUSR1){
printf("Capture sign no : SIGUSR1\n");
}else if(SIGALRM == iSignNo){
//printf("Capture sign no : SIGALRM\n");
}else{
printf("Capture sign no:%d\n",iSignNo);
}
}
void testTimerSign(){
struct sigevent evp;
struct itimerspec ts;
timer_t timer;
int ret;
evp.sigev_value.sival_ptr = &timer;
evp.sigev_notify = SIGEV_SIGNAL;
evp.sigev_signo = SIGALRM;
signal(evp.sigev_signo, SignHandler);
ret = timer_create(CLOCK_REALTIME, &evp, &timer);
if(ret) {
perror("timer_create");
}
ts.it_interval.tv_sec = 1;
ts.it_interval.tv_nsec = 0;
ts.it_value.tv_sec = 1;
ts.it_value.tv_nsec = 0;
printTime();
printf("start\n");
ret = timer_settime(timer, 0, &ts, NULL);
if(ret) {
perror("timer_settime");
}
}
void printTime(){
struct tm *cursystem;
time_t tm_t;
time(&tm_t);
cursystem = localtime(&tm_t);
char tszInfo[2048] ;
sprintf(tszInfo, "%02d:%02d:%02d",
cursystem->tm_hour,
cursystem->tm_min,
cursystem->tm_sec);
printf("[%s]",tszInfo);
}
因爲timer_settime()中定時器間隔時間爲1秒
於是sleep(5)每次都被打斷不能按時休眠,剩餘4秒未能執行;
4.1.2 示例2 SIGALRM信號量不同線程的影響
因爲休眠sleep(unsigned int)爲線程內操作
所以如果不同線程,信號量SIGALRM是不能中斷sleep();
下面用一個例子說明:
//timercreate_demo.cpp
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <pthread.h>
void SignHandler(int iSignNo);
void testTimerSign();
void printTime();
void *function(void *arg);
int main() {
pthread_t thread1;
pthread_create(&thread1,NULL,function,(char*)"111");
testTimerSign();
while(true);
return 0;
}
void SignHandler(int iSignNo){
if(iSignNo == SIGUSR1){
printf("Capture sign no : SIGUSR1\n");
}else if(SIGALRM == iSignNo){
//printf("Capture sign no : SIGALRM\n");
}else{
printf("Capture sign no:%d\n",iSignNo);
}
}
void testTimerSign(){
struct sigevent evp;
struct itimerspec ts;
timer_t timer;
int ret;
evp.sigev_value.sival_ptr = &timer;
evp.sigev_notify = SIGEV_SIGNAL;
evp.sigev_signo = SIGALRM;
signal(evp.sigev_signo, SignHandler);
ret = timer_create(CLOCK_REALTIME, &evp, &timer);
if(ret) {
perror("timer_create");
}
ts.it_interval.tv_sec = 1;
ts.it_interval.tv_nsec = 0;
ts.it_value.tv_sec = 1;
ts.it_value.tv_nsec = 0;
printTime();
printf("start\n");
ret = timer_settime(timer, 0, &ts, NULL);
if(ret) {
perror("timer_settime");
}
}
void printTime(){
struct tm *cursystem;
time_t tm_t;
time(&tm_t);
cursystem = localtime(&tm_t);
char tszInfo[2048] ;
sprintf(tszInfo, "%02d:%02d:%02d",
cursystem->tm_hour,
cursystem->tm_min,
cursystem->tm_sec);
printf("[%s]",tszInfo);
}
void *function(void *arg){
char *m;
m = (char *)arg;
while(true) {
while(true){
int left = sleep(3);
printTime();
printf("sleep(3)(left=%d)\n", left);
}
}
}
注意編譯的時候添加 -lpthread
輸出結果:
可以看到主線程的定時器中的信號量SIGALRM是無法中斷子線程thread1的休眠;
4.1.3 示例3 使用SIGUSR1信號量定時
//signalDemo.cpp
//compile : gcc signalDemo.cpp -o testSignal -lrt
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
void testTimerSign();
void SignHandler(int iSignNo);
void printTime();
int main() {
testTimerSign();
while(true) {
sleep(1);
}
return 0;
}
void SignHandler(int iSignNo){
printTime();
if(iSignNo == SIGUSR1){
printf("Capture sign No.=SIGUSR1\n");
}else{
printf("Capture sign No.=%d\n",iSignNo);
}
}
void testTimerSign(){
struct sigevent evp;
struct itimerspec ts;
timer_t timer;
int ret;
evp.sigev_value.sival_ptr = &timer;
evp.sigev_notify = SIGEV_SIGNAL;
evp.sigev_signo = SIGUSR1;
signal(evp.sigev_signo, SignHandler);
ret = timer_create(CLOCK_REALTIME, &evp, &timer);
if(ret) {
perror("timer_create");
}
ts.it_interval.tv_sec = 1; // the spacing time
ts.it_interval.tv_nsec = 0;
ts.it_value.tv_sec = 2; // the delay time start
ts.it_value.tv_nsec = 0;
printTime();
printf("start\n");
ret = timer_settime(timer, 0, &ts, NULL);
if(ret) {
perror("timer_settime");
}
}
void printTime(){
struct tm *cursystem;
time_t tm_t;
time(&tm_t);
cursystem = localtime(&tm_t);
char tszInfo[2048] ;
sprintf(tszInfo, "%02d:%02d:%02d",
cursystem->tm_hour, cursystem->tm_min,
cursystem->tm_sec);
printf("[%s]",tszInfo);
}
輸出結果:
4.1.4 示例4 信號量爲signaction的處理函數
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#define CLOCKID CLOCK_REALTIME
void sig_handler(int signo)
{
printf("timer_signal function! %d\n", signo);
}
int main()
{
//XXX int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
// signum--指定的信號編號,可以指定SIGKILL和SIGSTOP以外的所有信號編號
// act結構體--設置信號編號爲signum的處理方式
// oldact結構體--保存上次的處理方式
//
// struct sigaction
// {
// void (*sa_handler)(int); //信號響應函數地址
// void (*sa_sigaction)(int, siginfo_t *, void *); //但sa_flags爲SA——SIGINFO時才使用
// sigset_t sa_mask; //說明一個信號集在調用捕捉函數之前,會加入進程的屏蔽中,當捕捉函數返回時,還原
// int sa_flags;
// void (*sa_restorer)(void); //未用
// };
//
timer_t timerid;
struct sigevent evp;
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = sig_handler;
act.sa_flags = 0;
// XXX int sigaddset(sigset_t *set, int signum); //將signum指定的信號加入set信號集
// XXX int sigemptyset(sigset_t *set); //初始化信號集
sigemptyset(&act.sa_mask);
if (sigaction(SIGUSR1, &act, NULL) == -1)
{
perror("fail to sigaction");
exit(-1);
}
memset(&evp, 0, sizeof(struct sigevent));
evp.sigev_signo = SIGUSR1;
evp.sigev_notify = SIGEV_SIGNAL;
if (timer_create(CLOCK_REALTIME, &evp, &timerid) == -1)
{
perror("fail to timer_create");
exit(-1);
}
struct itimerspec it;
it.it_interval.tv_sec = 2;
it.it_interval.tv_nsec = 0;
it.it_value.tv_sec = 1;
it.it_value.tv_nsec = 0;
if (timer_settime(timerid, 0, &it, 0) == -1)
{
perror("fail to timer_settime");
exit(-1);
}
pause();
return 0;
}
輸出結果:
4.2 採用新線程派駐的通知方式
4.2.1 示例5 簡單函數線程的處理方式
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#define CLOCKID CLOCK_REALTIME
void timer_thread(union sigval v)
{
printf("timer_thread function! %d\n", v.sival_int);
}
int main()
{
// XXX int timer_create(clockid_t clockid, struct sigevent *evp, timer_t *timerid);
// clockid--值:CLOCK_REALTIME,CLOCK_MONOTONIC,CLOCK_PROCESS_CPUTIME_ID,CLOCK_THREAD_CPUTIME_ID
// evp--存放環境值的地址,結構成員說明了定時器到期的通知方式和處理方式等
// timerid--定時器標識符
timer_t timerid;
struct sigevent evp;
memset(&evp, 0, sizeof(struct sigevent)); //清零初始化
evp.sigev_value.sival_int = 111; //也是標識定時器的,這和timerid有什麼區別?回調函數可以獲得
evp.sigev_notify = SIGEV_THREAD; //線程通知的方式,派駐新線程
evp.sigev_notify_function = timer_thread; //線程函數地址
if (timer_create(CLOCKID, &evp, &timerid) == -1)
{
perror("fail to timer_create");
exit(-1);
}
// XXX int timer_settime(timer_t timerid, int flags, const struct itimerspec *new_value,struct itimerspec *old_value);
// timerid--定時器標識
// flags--0表示相對時間,1表示絕對時間,通常使用相對時間
// new_value--定時器的新初始值和間隔,如下面的it
// old_value--取值通常爲0,即第四個參數常爲NULL,若不爲NULL,則返回定時器的前一個值
//第一次間隔it.it_value這麼長,以後每次都是it.it_interval這麼長,就是說it.it_value變0的時候會裝載it.it_interval的值
//it.it_interval可以理解爲週期
struct itimerspec it;
it.it_interval.tv_sec = 1; //間隔1s
it.it_interval.tv_nsec = 0;
it.it_value.tv_sec = 1;
it.it_value.tv_nsec = 0;
if (timer_settime(timerid, 0, &it, NULL) == -1)
{
perror("fail to timer_settime");
exit(-1);
}
pause();
return 0;
}
/*
* int timer_gettime(timer_t timerid, struct itimerspec *curr_value);
* 獲取timerid指定的定時器的值,填入curr_value
*
*/
輸出結果:
4.2.2 示例6 使用pthread 創建線程的處理方法
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <error.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <sys/wait.h>
void timer_thread(union sigval v)
{
printf("timer_thread function! %d\n", v.sival_int);
}
int init_timer(timer_t *timerid, struct sigevent *evp, struct itimerspec *it)
{
if ( !evp || !it )
return -1;
memset(evp, 0, sizeof(struct sigevent)); //清零初始化
evp->sigev_value.sival_int = 111; //也是標識定時器的,這和timerid有什麼區別?回調函數可以獲得
evp->sigev_notify = SIGEV_THREAD; //線程通知的方式,派駐新線程
evp->sigev_notify_function = timer_thread; //線程函數地址
if (timer_create(CLOCK_REALTIME, evp, timerid) == -1)
{
perror("fail to timer_create");
return -1;;
}
printf("timer_create timerid = %d\n", *timerid);
it->it_interval.tv_sec = 1; // 後續按照該時間間隔
it->it_interval.tv_nsec = 0;
it->it_value.tv_sec = 3; // 最初開始時間間隔
it->it_value.tv_nsec = 0;
return 0;
}
int start_timer(timer_t *timerid, struct itimerspec *it)
{
if (it == NULL){
return -1;
}
if (timer_settime(*timerid, 0, it, NULL) == -1)
{
perror("fail to timer_settime");
return -1;
}
return 0;
}
void* thread_func(void *param)
{
int a = *(int *)param;
while(1){
sleep(1);
printf("This is thread..\n");
}
a = 100;
printf("param = %d\n", a);
}
int main(int argc, const char *argv[])
{
pid_t pid = 0;
pthread_t thread;
timer_t timerid = 0;
int ret;
struct sigevent evp;
struct itimerspec it;
int a = 10;
#if 0
int ret = init_timer(&timerid, &evp, &it);
if (ret < 0){
printf("init_timer failed\n");
return -1;
}
#endif
if ((pid = fork()) < 0)
{
printf("fork failed.\n");
return -1;
}
else if ( pid == 0){
printf("child proc..\n");
ret = pthread_create(&thread, NULL, thread_func, &a);//最後一個參數爲void*類型,傳遞變量的地址,其實其他的類型也遵循這個原則
int ret = init_timer(&timerid, &evp, &it);
if (ret < 0){
printf("init_timer failed\n");
return -1;
}
sleep(2);
printf("child timer_Id addr = %d\n", timerid);
start_timer(&timerid, &it);
sleep(10);
exit(0);
}
else{
printf("I'm parent proc..\n");
printf("parent timer_Id addr = %d\n", timerid);
printf("pthread_id = %d\n", thread);
do {
ret = waitpid(pid, NULL, WNOHANG);
if (ret == 0){
printf("No child exit\n");
sleep(2);
}
else if (ret == pid){
printf("Successly get child %d\n", pid);
}
else
printf("something error\n");
}while(ret == 0);
/*ret = waitpid(pid, NULL, 0);*/
/*if (ret == pid)*/
/*printf("successly get child %d\n", pid);*/
}
pause();
return 0;
}
輸出結果: