linux下系统调用timerfd和epoll

1:EPOLL

epoll在Linux2.6内核正式提出,是基于事件驱动的I/O方式,相对于select来说,epoll没有描述符个数限制,使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

1.1: epoll_create

函数创建一个epoll句柄,参数size表明内核要监听的描述符数量。调用成功时返回一个epoll句柄描述符,失败时返回-1。

1.2: epoll_ctl

函数注册要监听的事件类型。四个参数解释如下:

epfd 表示epoll句柄 op 表示fd操作类型,有如下3种
EPOLL_CTL_ADD 注册新的fd到epfd中
EPOLL_CTL_MOD 修改已注册的fd的监听事件
EPOLL_CTL_DEL 从epfd中删除一个fd fd 是要监听的描述符
event 表示要监听的事件

struct epoll_event

struct epoll_event {
    __uint32_t events;  /* Epoll events */
    epoll_data_t data;  /* User data variable */
};

typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

epoll 下的events可以是以下几个宏的集合:

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

1.3: epoll_wait

等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。如果返回–1,则表示出现错误,需要检查 errno错误码判断错误类型。

第1个参数 epfd是 epoll的描述符。

第2个参数 events则是分配好的 epoll_event结构体数组,epoll将会把发生的事件复制到 events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存。内核这种做法效率很高)。

第3个参数 maxevents表示本次可以返回的最大事件数目,通常 maxevents参数与预分配的events数组的大小是相等的。

第4个参数 timeout表示在没有检测到事件发生时最多等待的时间(单位为毫秒),如果 timeout为0,则表示 epoll_wait在dllist链表中为空,立刻返回,不会等待。

1.4:关于ET、LT两种工作模式

epoll有两种工作模式:LT(水平触发)模式和ET(边缘触发)模式。

默认情况下,epoll采用 LT模式工作,这时可以处理阻塞和非阻塞套接字,而上表中的 EPOLLET表示可以将一个事件改为 ET模式。ET模式的效率要比 LT模式高,它只支持非阻塞套接字。

ET模式与LT模式的区别在于:

当一个新的事件到来时,ET模式下当然可以从 epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字没有新的事件再次到来时,在 ET模式下是无法再次从 epoll_wait调用中获取这个事件的;而 LT模式则相反,只要一个事件对应的套接字缓冲区还有数据,就总能从 epoll_wait中获取这个事件。因此,在 LT模式下开发基于 epoll的应用要简单一些,不太容易出错,而在 ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求得不到响应。默认情况下,Nginx是通过 ET模式使用 epoll的。

2:timerfd

2.1 timerfd_create()

#include <sys/timerfd.h>

int timerfd_create(int clockid, int flags);

timerfd_create()函数创建一个定时器对象,同时返回一个与之关联的文件描述符。

clockid:clockid标识指定的时钟计数器,可选值(CLOCK_REALTIME、CLOCK_MONOTONIC)
CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变,即从UTC1970-1-1 0:0:0开始计时,中间时刻如果系统时间被用户改成其他,则对应的时间相应改变
CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
flags:参数flags(TFD_NONBLOCK(非阻塞模式)/TFD_CLOEXEC(表示当程序执行exec函数时本fd将被系统自动关闭,表示不传递)

2.2 timerfd_settime()

 #include <sys/timerfd.h>

 struct timespec {
     time_t tv_sec;                /* Seconds */
     long   tv_nsec;               /* Nanoseconds */
 };

 struct itimerspec {
     struct timespec it_interval;  /* Interval for periodic timer (定时间隔周期)*/
     struct timespec it_value;     /* Initial expiration (第一次超时时间)*/
 };
 int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);

timerfd_settime()此函数用于设置新的超时时间,并开始计时,能够启动和停止定时器;

fd: 参数fd是timerfd_create函数返回的文件句柄
flags:参数flags为1代表设置的是绝对时间(TFD_TIMER_ABSTIME 表示绝对定时器);为0代表相对时间。
new_value: 参数new_value指定定时器的超时时间以及超时间隔时间
old_value: 如果old_value不为NULL, old_vlaue返回之前定时器设置的超时时间,具体参考timerfd_gettime()函数
** it_interval不为0则表示是周期性定时器。
it_value和it_interval都为0表示停止定时器

2.3 timerfd_gettime()函数

int timerfd_gettime(int fd, struct itimerspec *curr_value);

timerfd_gettime()函数获取距离下次超时剩余的时间

curr_value.it_value 字段表示距离下次超时的时间,如果改值为0,表示计时器已经解除
改字段表示的值永远是一个相对值,无论TFD_TIMER_ABSTIME是否被设置 curr_value.it_interval 定时器间隔时间

uint64_t exp = 0;
read(fd, &exp, sizeof(uint64_t)); 
//可以用read函数读取计时器的超时次数,改值是一个8字节无符号的长整型

3:示例

/************************************************************************
    > File Name: timerfd.c
    > Author: kayshi
    > Mail: kayshi2019@qq.com
    > Created Time: Tue 20 Oct 2020 11:39:46 AM CST
 ************************************************************************/

#include <stdio.h>
#include <sys/timerfd.h>
#include <time.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <assert.h>

const int MAXNUM = 20;

int main()
{
    struct itimerspec new_value;
    struct timespec now;
    uint64_t exp;
    ssize_t s;

    int ret = clock_gettime(CLOCK_REALTIME, &now);
    assert(ret != -1);

    new_value.it_value.tv_sec  = 5;//第一次定时时间
    new_value.it_value.tv_nsec = now.tv_nsec;

    new_value.it_interval.tv_sec = 1;//后面每次间隔的时间
    new_value.it_interval.tv_nsec = 0;

    int timefd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK);//创建定时对象并生成文件描述符timefd 
    assert(timefd != -1);

    ret = timerfd_settime(timefd, 0, &new_value, NULL);//设置这个描述符的定时使时间
    assert(ret != -1);

    printf("timer started\n");

    int epollfd = epoll_create(1);//创建一个监测一个描述符epoll的句柄描述符
    struct epoll_event ev;
    struct epoll_event events[MAXNUM];
    ev.data.fd = timefd;//设置要监测的文件描述符的值
    ev.events = EPOLLIN | EPOLLET;//设置描述符的事件
    epoll_ctl(epollfd, EPOLL_CTL_ADD, timefd, &ev);//把定时器描述符添加到epoll检测队列中,函数注册要监听的事件类型为ev中的events

    for(;;)
    {
        int num = epoll_wait(epollfd, events, MAXNUM, 0);//等待事件的产生,并把检测到的信息保存到events这个结构体中
        assert(num >= 0);

        for(int i = 0; i < num; i++)
        {
            if(events[i].events & EPOLLIN)//检测到文件符可以读的事件
            {
                if(events[i].data.fd == timefd)//检测到的文件描述符的值与被检测的文件描述符的值相等
                {
                    s = read(events[i].data.fd, &exp, sizeof(uint64_t));
                    assert(s == sizeof(uint64_t));
                    printf("here is timer\n");
                }
            }
        }
    }
    close(timefd);
    close(epollfd);
    return 0;

}

这段代码的含义:通过timerfd_create创建一个定时器的文件描述符,这个描述符有一个特性,只有时间达到定时的值,这个描述符才可以读。上面的描述符timefd ,在结构体中设置为第一次可读的时间是5S后面每间隔1S就可以读。后面通过epoll的函数来检测这个描述符,当这个描述符出现可读时(events[i].events为EPOLLIN )就开始一些动作。

结果

kayshi@ubuntu:~/code/Test$ ./a.out 
timer started
here is timer
here is timer
here is timer
here is timer
here is timer
here is timer
here is timer
here is timer
here is timer
here is timer
here is timer
here is timer

执行程序后等待5s第一次打印here is timer,然后每隔1s打印一次here is timer

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值