欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

POSIX定时器

程序员文章站 2022-07-14 13:18:03
...

最强大的定时器接口来自POSIX时钟系列,这个支持查询、信号和回调函数方式。

创建一个定时器

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.
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.

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作为为一个参数。

启动一个定时器

timer_create()所创建的定时器并未启动。要将它关联到一个到期时间以及启动时钟周期,可以使用timer_settime()。

int timer_settime(timer_t timerid, int flags,
const struct itimerspec *value, struct itimerspect *ovalue);
struct itimespec{
        struct timespec it_interval;
        struct timespec it_value;
};

如同settimer(),it_value用于指定当前的定时器到期时间。当定时器到期,it_value的值会被更新成it_interval 的值。如果it_interval的值为0,则定时器不是一个时间间隔定时器,一旦it_value到期就会回到未启动状态。timespec的结构提供了纳秒级分辨率:

struct timespec{
        time_t tv_sec;
        long tv_nsec;  
};

timer定时的时间间隔,第一次是ts.it_value这么长,后面每次时间间隔是ts.it_interval这么长

    ts.it_interval.tv_sec = 0;

    ts.it_interval.tv_nsec = 200000000; //200ms 

    ts.it_value.tv_sec = 0;

    ts.it_value.tv_nsec = 200000000; //200ms 

    ts.it_interval.tv_sec = 0;

    ts.it_interval.tv_nsec = 200000000; //200ms 

    ts.it_value.tv_sec = 0;

    ts.it_value.tv_nsec = 200000000; //200ms 

获得一个活动定时器的剩余时间

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指定了无效的定时器。

删除一个定时器

int timer_delete (timer_t timerid);

一次成功的timer_delete()调用会销毁关联到timerid的定时器并且返回0。执行失败时,此调用会返回-1并将errno设定会 EINVAL,这个唯一的错误情况代表timerid不是一个有效的定时器。

下面提供一个信号的例子

timer.c

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>

#define CLOCKID CLOCK_REALTIME
#define SIG SIGRTMIN

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
					   } while (0)

static void
print_siginfo(siginfo_t *si)
{
   timer_t *tidp;
   int or;

   tidp = si->si_value.sival_ptr;

   printf("    sival_ptr = %p; ", si->si_value.sival_ptr);
   printf("    *sival_ptr = 0x%lx\n", (long) *tidp);

   or = timer_getoverrun(*tidp);
   if (or == -1)
	   errExit("timer_getoverrun");
   else
	   printf("    overrun count = %d\n", or);
}

static void
handler(int sig, siginfo_t *si, void *uc)
{
   /* Note: calling printf() from a signal handler is not
	  strictly correct, since printf() is not async-signal-safe;
	  see signal(7) */

   printf("Caught signal %d\n", sig);
   print_siginfo(si);
   signal(sig, SIG_IGN);
}

int
main(int argc, char *argv[])
{
   timer_t timerid;
   struct sigevent sev;
   struct itimerspec its;
   long long freq_nanosecs;
   sigset_t mask;
   struct sigaction sa;

   if (argc != 3) {
	   fprintf(stderr, "Usage: %s <sleep-secs> <freq-nanosecs>\n",
			   argv[0]);
	   exit(EXIT_FAILURE);
   }

   /* Establish handler for timer signal */

   printf("Establishing handler for signal %d\n", SIG);
   sa.sa_flags = SA_SIGINFO;
   sa.sa_sigaction = handler;
   sigemptyset(&sa.sa_mask);
   if (sigaction(SIG, &sa, NULL) == -1)
	   errExit("sigaction");

   /* Block timer signal temporarily */

   printf("Blocking signal %d\n", SIG);
   sigemptyset(&mask);
   sigaddset(&mask, SIG);
   if (sigprocmask(SIG_SETMASK, &mask, NULL) == -1)
	   errExit("sigprocmask");

   /* Create the timer */

   sev.sigev_notify = SIGEV_SIGNAL;
   sev.sigev_signo = SIG;
   sev.sigev_value.sival_ptr = &timerid;
   if (timer_create(CLOCKID, &sev, &timerid) == -1)
	   errExit("timer_create");

   printf("timer ID is 0x%lx\n", (long) timerid);

   /* Start the timer */

   freq_nanosecs = atoll(argv[2]);
   its.it_value.tv_sec = freq_nanosecs / 1000000000;
   its.it_value.tv_nsec = freq_nanosecs % 1000000000;
   its.it_interval.tv_sec = its.it_value.tv_sec;
   its.it_interval.tv_nsec = its.it_value.tv_nsec;

   if (timer_settime(timerid, 0, &its, NULL) == -1)
		errExit("timer_settime");

   /* Sleep for a while; meanwhile, the timer may expire
	  multiple times */

   printf("Sleeping for %d seconds\n", atoi(argv[1]));
   sleep(atoi(argv[1]));

   /* Unlock the timer signal, so that timer notification
	  can be delivered */

   printf("Unblocking signal %d\n", SIG);
   if (sigprocmask(SIG_UNBLOCK, &mask, NULL) == -1)
	   errExit("sigprocmask");

   exit(EXIT_SUCCESS);
}

执行gcc timer.c -o timer -lrt

在可执行中,第二个参数为进程sleep时间,单位为s,第三个参数为定时器时间,单位为ns。

输入

[email protected]:~/timerPOSIX$ ./timer 5 1000000000
Establishing handler for signal 34
Blocking signal 34
timer ID is 0x17f9570
Sleeping for 5 seconds
Unblocking signal 34
Caught signal 34
    sival_ptr = 0xbfd69274;     *sival_ptr = 0x17f9570
    overrun count = 4

下面介绍定时启动线程的方法

下面先介绍一下线程的基础

线程属性pthread_attr_t简介

  Posix线程中的线程属性pthread_attr_t主要包括scope属性、detach属性、堆栈地址、堆栈大小、优先级。在pthread_create中,把第二个参数设置为NULL的话,将采用默认的属性配置。
pthread_attr_t的主要属性的意义如下:
__detachstate,表示新线程是否与进程中其他线程脱离同步, 如果设置为PTHREAD_CREATE_DETACHED 则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。
__schedpolicy,表示新线程的调度策略,主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时可以用过pthread_setschedparam()来改变。
__schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0。
__inheritsched,有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),而后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。
__scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。
  为了设置这些属性,POSIX定义了一系列属性设置函数,包括pthread_attr_init()、pthread_attr_destroy()和与各个属性相关的pthread_attr_getXXX/pthread_attr_setXXX函数。
在设置线程属性 pthread_attr_t 之前,通常先调用pthread_attr_init来初始化,之后来调用相应的属性设置函数。
主要的函数如下:

1、pthread_attr_init
功能:        对线程属性变量的初始化。
头文件:     <pthread.h>
函数原型:   int pthread_attr_init (pthread_attr_t* attr);
函数传入值:attr:线程属性。
函数返回值:成功: 0
                失败: -1
2、pthread_attr_setscope
功能:        设置线程 __scope 属性。scope属性表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程一起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。默认为PTHREAD_SCOPE_PROCESS。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。
头文件:     <pthread.h>
函数原型:   int pthread_attr_setscope (pthread_attr_t* attr, int scope);
函数传入值:attr: 线程属性。
                      scope:PTHREAD_SCOPE_SYSTEM,表示与系统中所有线程一起竞争CPU时间,
                                 PTHREAD_SCOPE_PROCESS,表示仅与同进程中的线程竞争CPU
函数返回值得:同1。
3、pthread_attr_setdetachstate
功能:        设置线程detachstate属性。该表示新线程是否与进程中其他线程脱离同步,如果设置为PTHREAD_CREATE_DETACHED则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也可以在线程创建并运行以后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不论是创建时设置还是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。
头文件:      <phread.h>
函数原型:    int pthread_attr_setdetachstate (pthread_attr_t* attr, int detachstate);
函数传入值:attr:线程属性。
detachstate:PTHREAD_CREATE_DETACHED,不能用pthread_join()来同步,且在退出时自行释放所占用的资源
                    PTHREAD_CREATE_JOINABLE,能用pthread_join()来同步
函数返回值得:同1。
4、pthread_attr_setschedparam
功能:       设置线程schedparam属性,即调用的优先级。
头文件:     <pthread.h>
函数原型:   int pthread_attr_setschedparam (pthread_attr_t* attr, struct sched_param* param);
函数传入值:attr:线程属性。
                 param:线程优先级。一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并可以在运行时通过pthread_setschedparam()函数来改变,缺省为0
函数返回值:同1。
5、pthread_attr_getschedparam
功能:       得到线程优先级。
头文件:    <pthread.h>
函数原型:  int pthread_attr_getschedparam (pthread_attr_t* attr, struct sched_param* param);
函数传入值:attr:线程属性;
                    param:线程优先级;
函数返回值:同1。

启动线程定时例子

timerpthread.c

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <pthread.h>

#define CLOCKID CLOCK_REALTIME

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
					   } while (0)

/*static*/void fade_in_callback(union sigval v)
{
    timer_t *ptr = (timer_t*)v.sival_ptr;

    //timer_delete(*ptr);//如果删除,会反复进入
	printf("fade_in_callback %d!\n", *ptr);
}

int
main(int argc, char *argv[])
{
	timer_t timerid;
	struct sigevent sev;
	struct itimerspec its;
	long long freq_nanosecs;
	sigset_t mask;
	pthread_attr_t attr = {0};

	if (argc != 3) {
		fprintf(stderr, "Usage: %s <sleep-secs> <freq-nanosecs>\n",
		        argv[0]);
		exit(EXIT_FAILURE);
	}

	/*初始化属性线程属性*/
	pthread_attr_init (&attr);
	pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);
	pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);

	/* Create the timer */

	sev.sigev_notify = SIGEV_THREAD;
	sev.sigev_notify_function = fade_in_callback;
	sev.sigev_value.sival_ptr = &timerid;
	if (timer_create(CLOCKID, &sev, &timerid) == -1)
		errExit("timer_create");

	printf("timer ID is 0x%lx\n", (long) timerid);

	/* Start the timer */

	freq_nanosecs = atoll(argv[2]);
	its.it_value.tv_sec = freq_nanosecs / 1000000000;
	its.it_value.tv_nsec = freq_nanosecs % 1000000000;
	its.it_interval.tv_sec = its.it_value.tv_sec;
	its.it_interval.tv_nsec = its.it_value.tv_nsec;

	if (timer_settime(timerid, 0, &its, NULL) == -1)
		errExit("timer_settime");

	/* Sleep for a while; meanwhile, the timer may expire
	  multiple times */

	printf("Sleeping for %d seconds\n", atoi(argv[1]));
	sleep(atoi(argv[1]));

	printf("main out\n");

	exit(EXIT_SUCCESS);
}

gcc timerpthread.c -o timerpthread -lrt -lpthread

./timerpthread 5 1000000000
timer ID is 0x16a9130
Sleeping for 5 seconds
fade_in_callback 23761200!
fade_in_callback 23761200!
fade_in_callback 23761200!
fade_in_callback 23761200!
main out

参考博文

https://blog.csdn.net/a1232345/article/details/45307723

https://blog.csdn.net/hudashi/article/details/7709413