zoukankan      html  css  js  c++  java
  • Linux使用定时器timerfd 和 eventfd接口实现进程线程通信

    timerfd是Linux提供的一个定时器接口。这个接口基于文件描述符,通过文件描述符的可读事件进行超时通知,所以能够被用于select/poll/epoll的应用场景。timerfd是linux内核2.6.25版本中加入的接口。
    可以实现定时器的功能,将定时器抽象为文件描述符,当定时器到期时可以对其read,这样也可以放到监听队列的主循环中。timerfd有数据可读要把它读走,不然定时器失效
    #include <sys/timerfd.h>
        int timerfd_create(int clockid, int flags);
        int timerfd_settime(int fd, int flags,
                            const struct itimerspec *new_value,
                            struct itimerspec *old_value);
    timerfd_create
    ●功能:该函数生成一个定时器对象,返回与之关联的文件描述符。
    ●参数详解:
    -clockid: 可设置为
           CLOCK_REALTIME:相对时间,从1970.1.1到目前的时间。更改系统时间会更改获取的值,它以系统时间为坐标。
           CLOCK_MONOTONIC:绝对时间,获取的时间为系统重启到现在的时间,更改系统时间对其没有影响。
    -flags: 可设置为
           TFD_NONBLOCK(非阻塞),
           TFD_CLOEXEC(同O_CLOEXEC)
           linux内核2.6.26版本以上都指定为0
    timerfd_settime
    功能:该函数能够启动和停止定时器
    ●参数详解
    -fd: timerfd对应的文件描述符
    -flags:
          0表示是相对定时器
          TFD_TIMER_ABSTIME表示是绝对定时器
    -new_value:设置超时时间,如果为0则表示停止定时器。
    -old_value:
          一般设为NULL, 不为NULL,则返回定时器这次设置之前的超时时间
    操作
    read:读取缓冲区中的数据,其占据的存储空间为sizeof(uint_64),表示超时次数。
        
    select/poll/epoll:当定时器超时时,会触发定时器相对应的文件描述符上的读操作,IO复用操作会返回,然后再去对该读事件进行处理。
    struct itimerspec {
                  struct timespec it_interval; /* Interval for periodic timer */间隔时间
                  struct timespec it_value;    /* Initial expiration */初始到期时间
              };

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


    eventfd的主要是用于进程或者线程间通信(如通知/等待机制的实现)。
    实现了线程之间事件通知的方式,eventfd的缓冲区大小是sizeof(uint64_t);向其write可以递增这个计数器,read操作可以读取,并进行清零;eventfd也可以放到监听队列中,当计数器不是0时,有可读事件发生,可以进行读取。
    #include <sys/eventfd.h>
    int eventfd(unsigned int initval, int flags);
    参数解释:
    ●如果是2.6.26或之前版本的内核,flags 必须设置为0。
    initval:初始化计数器值,该值保存在内核.
    flags支持以下标志位:
      - EFD_NONBLOCK 类似于使用O_NONBLOCK标志设置文件描述符。
      - EFD_CLOEXEC  类似open以O_CLOEXEC标志打开,O_CLOEXEC 应该表示执行exec()时,之前通过open()打开的文件描述符会自动关闭.
    返回值:函数返回一个文件描述符,与打开的其他文件一样,可以进行读写操作。
    操作:
    read:如果计数器A的值不为0时,读取成功,获得该值。如果A的值为0,非阻塞模式时,会直接返回失败,并把error置为EINVAL;如果为阻塞模式,一直会阻塞到A为非0为止。
    write:将缓冲区写入的8字节整形值加到内核计数器上,即会增加8字节的整数在计数器A上,如果其值达到0xfffffffffffffffe时,就会阻塞(在阻塞模式下),直到A的值被read。

    write操作,写入的数据会加到计数器上,read操作读走计数器上的值后会把计数器置为0.



    #include <poll.h>
        int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    fds      可以传递多个结构体,也就是说可以监测多个驱动设备所产生的事件,只要有一个产生了请求事件,就能立即返回
    nfds     监测驱动文件的个数
    timeout  超时时间,单位为ms -1永久等待,0立即返回

    events:
    POLLIN       有数据可读
    POLLRDNORM   有普通数据可读,等效与POLLIN
    POLLPRI      有紧迫数据可读
    POLLOUT      写数据不会导致阻塞
    POLLER       指定的文件描述符发生错误
    POLLHUP      指定的文件描述符挂起事件
    POLLNVAL     无效的请求,打不开指定的文件描述符
    返回值:
    有事件发生   返回revents域不为0的文件描述符个数(也就是说事件发生,或者错误报告)
    超时        返回0;
    失败      返回-1,并设置errno为错误类型
    失败返回-1的错误码->EINTR 请求的事件之前产生一个信号,调用可以重新发起。
    // #define EINTR 4   /* Interrupted system call */
    struct pollfd {
                   int   fd;      
    /* file descriptor *//* 文件描述符 */
                   short events;  
    /* requested events *//* 请求的事件类型,监视驱动文件的事件掩码 */
                   short revents; 
    /* returned events *//* 驱动文件实际返回的事件 */
               };

    poll机制就是给定一段时间,在这一段时间内程序处于睡眠状态一直等待某一个资源,它会在两种情况下返回
    ①时间到了.
    ②等到了资源.

    等待期间将进程休眠,利用事件驱动来唤醒进程,将更能提高CPU的效率。

    #include <stdlib.h>
    int atoi(const char *nptr);
    long atol(const char *nptr);
    long long atoll(const char *nptr);


    example:
    eventfd.cpp
    #include<iostream>
    #include<unistd.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/eventfd.h>
    #include<sys/poll.h>
    #define handle_error(msg)
               do{
                       perror(msg);
                       exit(EXIT_FAILURE);
                    }while(0)
    using namespace std;
    int main(int argc,char** argv)
    {
            uint64_t u;
            int efd = eventfd(10,0);  // 计数器的初值为10,第二个参数写0
            //最新的进程/线程通信
            if(-1==efd)
            {
                    handle_error("eventfd");
            }
            int ret = fork();
            if(0==ret)
            {
                    for(int j=1;j<argc;++j)
                    {
                            u = atoll(argv[j]);
                            cout<<"child writing "<<u<<" to efd"<<endl;
                            ssize_t s = write(efd,&u,sizeof(uint64_t));  // 会把写入的数据累加到计数器上
                            if(s!=sizeof(uint64_t))
                            {
                                    handle_error("write");
                            }
                    }
                    cout<<"child completed write loop ";
                    exit(EXIT_SUCCESS);
            }
            else
            {
                    //sleep(2);  // 等子进程创建好,并循环完毕
                    for(int i=0;i<argc-1;++i)
                    {
                            ssize_t s = read(efd,&u,sizeof(uint64_t));  // 读完计数器会自动清0
                            if(s!=sizeof(uint64_t))
                            {
                                    handle_error("read");
                            }
                            cout<<"parent read "<<u<<" from efd ";
                    }
                    exit(EXIT_SUCCESS);
            }
    }

    //父进程在睡眠2s的过程中,子进程已经完成了for循环,向内核的计数器上写了1,2,3;
    //加上初值10,一共就是16
    //child writing 1 to efd
    //child writing 2 to efd
    //child writing 3 to efd
    //child completed write loop
    //parent read 16 from efd

    //父进程不睡眠,for循环读走所有输入
    //父进程先于子进程运行,读走了计数器的值10,计数器此时值变为0,后面父进程读操作处于阻塞状态
    //等到子进程写入1的时候唤醒父进程,父进程接着读。
    //$>./a.out 1 2 3
    //parent read 10 from efd
    //child writing 1 to efd
    //parent read 1 from efd
    //child writing 2 to efd
    //parent read 2 from efd
    //$>child writing 3 to efd

    // 结果这样是因为父进程循环次数,第一次来就读了一次,所有最后子进程写3的时候父进程已经退出了
    eventfd+poll.cpp
    #include<iostream>
    #include<unistd.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/eventfd.h>
    #include<sys/poll.h>
    #include<sys/wait.h>
    #define handle_error(msg)
               do{
                       perror(msg);
                       exit(EXIT_FAILURE);
                    }while(0)
    using namespace std;
    int main(int argc,char** argv)
    {
            uint64_t u;
            int efd = eventfd(0,0);  // 计数器的初值为0,第二个参数写0
            //最新的进程/线程通信
            if(-1==efd)
            {
                    handle_error("eventfd");
            }
            int ret = fork();
            if(0==ret)
            {
                    for(int j=1;j<argc;++j)
                    {
                            u = atoll(argv[j]);
                            cout<<"child writing "<<u<<" to efd"<<endl;
                            ssize_t s = write(efd,&u,sizeof(uint64_t));
                            if(s!=sizeof(uint64_t))
                            {
                                    handle_error("write");
                            }
                    }
                    cout<<"child completed write loop ";
                    //exit(EXIT_SUCCESS);
            }
            else
            {
                    struct pollfd ppfd;
                    ppfd.fd = efd;
                    ppfd.events = POLLIN;
                    for(int i=1;i<argc;++i)
                    {
                            poll(&ppfd,1,-1);  // 事件在等待ppfd可读,等待过程中父进程睡眠(休眠) 
            // 事件发生就唤醒,接着向下执行
            // 没有这个读一次就会卡住
                            ssize_t s = read(efd,&u,sizeof(uint64_t));
                            if(s!=sizeof(uint64_t))
                            {
                                    handle_error("read");
                            }
                            cout<<"parent read "<<u<<" from efd ";
                    }
                    wait(NULL);
            }
    }
    //child writing 1 to efd
    //parent read 1 from efd
    //child writing 2 to efd
    //parent read 2 from efd
    //child writing 3 to efd
    //parent read 3 from efd
    //child completed write loop
    timerfd.cpp
    #include<iostream>
    #include<sys/timerfd.h>
    #include<unistd.h>
    #include<stdio.h>
    #include<string.h>
    #include<time.h>
    using namespace std;
    int main()
    {
            int timerfd = timerfd_create(CLOCK_REALTIME,0);
            if(-1==timerfd)
            {
                    perror("timerfd_create");
                    return -1;
            }
            struct itimerspec new_value;
            memset(&new_value,0,sizeof(itimerspec));
            new_value.it_value.tv_sec = 5; //初始到期时间
            new_value.it_interval.tv_sec = 3;  //间隔时间
            int ret = timerfd_settime(timerfd,0,&new_value,NULL);
            if(-1==ret)
            {
                    perror("timerfd_settimer");
                    return -1;
            }
            long int timeNum;
            time_t t;
            time(&t);  // 获取当前秒数
            // 读到的是个8位的整数
            cout<<ctime(&t)<<endl;
            while(ret = read(timerfd,&timeNum,8),time(&t),cout<<ctime(&t))  // 时间到,timerfd可读,要把数据读走,不然阻塞,定时器没法正常工作了
            {
                    cout<<"timerfd read cnt "<<timeNum<<endl<<endl;
            }
    }
    //Fri Apr 20 11:12:22 2018
    //
    //Fri Apr 20 11:12:27 2018
    //timerfd read cnt 1
    //
    //Fri Apr 20 11:12:30 2018
    //timerfd read cnt 1
    //
    //Fri Apr 20 11:12:33 2018
    //timerfd read cnt 1
    //
    //Fri Apr 20 11:12:36 2018
    //timerfd read cnt 1
    //
    //Fri Apr 20 11:12:39 2018
    //timerfd read cnt 1
    //
    //Fri Apr 20 11:12:42 2018
    //timerfd read cnt 1
    //
    //^C
    timerfd+poll.cpp
    #include<iostream>
    #include<time.h>
    #include<poll.h>
    #include<sys/timerfd.h>
    #include<string.h>
    #include<unistd.h>
    using namespace std;
    int main()
    {
            int timerfd = timerfd_create(CLOCK_REALTIME,0);
            struct itimerspec new_value;
            memset(&new_value,0,sizeof(new_value));
            new_value.it_interval.tv_sec = 1;  //时间间隔
            new_value.it_value.tv_sec = 5;  //初始时间
            timerfd_settime(timerfd,0,&new_value,NULL);
            struct pollfd fd;
            bzero(&fd,sizeof(pollfd));
            fd.fd = timerfd;
            fd.events = POLLIN;
            time_t t;
            time(&t);
            cout<<ctime(&t)<<endl;
            while(1)
            {
                    int ret = poll(&fd,1,-1);  // 永久等待时间发生
                    if(ret>0)
                    {
                            time(&t);
                            cout<<ctime(&t)<<endl;
                            long long int tmp;
                            read(timerfd,&tmp,sizeof(long long int));  // 读走数据,不然会一直触发
                    }
                    else
                    {
                            return -1;
                    }
            }
    }
    //Fri Apr 20 11:29:48 2018
    //
    //Fri Apr 20 11:29:53 2018
    //
    //Fri Apr 20 11:29:54 2018
    //
    //Fri Apr 20 11:29:55 2018
    //
    //Fri Apr 20 11:29:56 2018
    //
    //^C


  • 相关阅读:
    MySQL性能调优——索引详解与索引的优化
    Linux命令之文件搜索
    MySQL中的行级锁,表级锁,页级锁
    MySQL存储引擎
    Linux软链接和硬链接
    linux学习笔记
    在浏览器中输入一个网址后,发生了什么?
    二叉排序树
    有序表查找
    为view设置虚线边框
  • 原文地址:https://www.cnblogs.com/meihao1203/p/9368398.html
Copyright © 2011-2022 走看看