Linux下如何实现秒以下精确定时与休眠

本文深入解析Linux中的定时器机制,包括setitimer、alarm、POSIX定时器、timerfd及利用select实现精确定时的方法。涵盖了定时器的类型、设置、获取及删除等操作,适合对Linux系统编程感兴趣的读者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux中提供的休眠函数是sleep和alarm,但是他们仅仅提供以秒为单位的休眠,这中休眠有些进程显然太长了,那么怎样才能使进程以更小的时间分辨率休眠呢?

下面就做分别介绍。

一、间隔定时器
1.setitimer
settitimer创建一个间隔式定时器,这种定时器会在未来某个时间到期,并于此后(可选择地)每隔一段时间到期一次

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
参数 which:

1. ITIMER_REAL  创建真实倒计时定时器。到期产生SIGALARM信号

2.ITIMER_VIRTUAL 创建以进程虚拟时间(用户模式下的cpu时间)倒计时定时器。到期产生SIGVTALRM信号

3.ITIMER_PROF 创建一个profiling定时器,以进程时间(用户态与内核态cpu时间总和)倒计时。到期产生SIGPROF信号。

所有这些信号的默认处理都会终止进程。

参数 new_value和old_value都是指向struct itimerval的指针。

struct itimerval {
    struct timeval it_interval;
    struct timeval it_value;
}

其中的timeval结构体如下:由秒和微妙组成

struc timeval {
    time_t tv_sec;//秒
    suseconds tv_usec;//微妙
}

参数new_value的下属结构it_value指定了距离定时器到期的延时时间。it_interval说明该定时器是否为周期性定时器。如果it_interval的两个字段均为0,那么就是一次性定时器。只要任何一个字段非零,那么在每次定时器到期之后,都会将定时器重置为指定间隔后再次到期。

进程只能拥有上述3中定时器的一种。当第2次调用settitimer时时将new_value的两个字段均设置为0,那么会屏蔽任何已有的定时器。

参数old_value是一个输出参数,如果参数不为NULL,返回之前定时器的设置。如果old_value的两个字段都为0说明该定时器处于屏蔽状态。如果old_value.it_interval的两个字段都为0,说明之前定时器设置的是一次性定时器。如果不关心前一次的定时器设置,将old_value设置为NULL就可以了。

定时器会从初始值(it_value)倒计时一直到0为止。递减为0时,会将相应的信号发送给进程,随后,如果it_interval非0,那么会再次将it_value加载至定时器,重新计时。

我们可以在任何时候调用gettitimer函数,了解定时器当前状态、距离下次到期的剩余时间。

int getitimer(int which, struct itimerval *curr_value)
which指定是哪种定时器,curr_value返回就是剩余时间。

2. alarm
系统调用alarm函数创建一次性实时定时器提供了一个简单的接口

unsigned int alarm(unsigned int seconds);
参数seconds表示定时器到期的秒数。到期后发送SIGALRM信号。

调用alarm会覆盖对定时器的前一次设置,调用alarm(0)可屏蔽现有定时器。

3. setitimer alarm联系
alarm和setitimer针对同一进程共享一个实时定时器,这意味着,无论调用两者哪个都覆盖了之前的设置。

二、POSIX间隔式定时器
使用setitimer来设置定时器,有几个制约:

1.针对ITIMER_REAL ITIMER_VIRTUAL ITIMER_PROF这3类定时器,每种只能设置一个

2.只能通过发送信号的方式来通知定时器,另外也不能改变到期的信号

3.如果一个间隔式定时器到期多次,且相应的信号阻塞,那么只会调用一次信号处理函数

4.分别率只能达到微妙级

因此我们可以使用timer_create函数创建定时器

timer_create函数,可以发送信号给进程也可以给线程,可以开启线程调用函数处理定时器到期。

这里具体函数使用就不说了

timer_settime函数设置定时器并且开启

timer_gettime获取定时器当前值

timer_delete删除定时器

三、利用文件描述符进行通知的定时器
linux特有的timerfd API,可以从文件描述符中读取所创建定时器的到期通知。可以使用select poll epoll将这样描述符和其他描述符一同进行监控,非常方便。

这几个API和之前的timer_create timer_setime timer_gettime相类似。

int timerfd_create(int clockid, int flags)
clockid可以设置为CLOCK_REALTIME CLOCK_MONOTONIC

int timerfd_settime(int fd, int flags, const struct itimerspec *new_value,
                        struct itimerspec *old_value)
new_value设置定时器,old_value获取定时器前一设置。

int timerfd_gettime(int fd, struct itimerspec *curr_value)
timerfd_gettime获取定时器到期的剩余时间

定时器到期后我们可以通过read文件描述符来获取定时器到期信息。
 

 还有方法是使用select来提供精确定时和休眠:

 int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
       struct timeval *timeout);

 n指监视的文件描述符范围,通常设为所要select的fd+1,readfds,writefds和exceptfds分别是读,写和异常文件描述符集,timeout为超时时间。

 可能用到的关于文件描述符集操作的宏有:

 FD_CLR(int fd, fd_set *set);    清除fd
 FD_ISSET(int fd, fd_set *set);  测试fd是否设置
 FD_SET(int fd, fd_set *set);     设置fd
 FD_ZERO(fd_set *set);             清空描述符集 

 我们此时用不到这些宏,因为我们并不关心文件描述符的状态,我们关心的是select超时。所以我们需要把readfds,writefds和exceptfds都设为NULL,只指定timeout时间就行了。至于n我们可以不关心,所以你可以把它设为任何非负值。实现代码如下:

int usTimer(long us)
{
    struct timeval timeout;
    timeout.tv_sec = 0;
    timeout.tv_usec = us;

    return select(0,NULL,NULL,NULL,&timeout);
}

 结语:

     不推荐使用setitimer,Linux系统提供的timer有限(每个进程至多能设3个不同类型的timer),再者ssetitimer实现起来没有 select简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值