Linux使用定时器timerfd 和 eventfd接口实现进程线程通信

body, table{font-family: 微软雅黑; font-size: 13.5pt}
table{border-collapse: collapse; border: solid gray; border-width: 2px 0 2px 0;}
th{border: 1px solid gray; padding: 4px; background-color: #DDD;}
td{border: 1px solid gray; padding: 4px;}
tr:nth-child(2n){background-color: #f8f8f8;}


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\n";

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\n";

}

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\n";

//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\n";

}

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

原文地址:https://www.cnblogs.com/meihao1203/p/9368398.html

时间: 2024-08-01 00:00:14

Linux使用定时器timerfd 和 eventfd接口实现进程线程通信的相关文章

[转] linux新的API signalfd、timerfd、eventfd使用说明

http://blog.csdn.net/gdutliuyun827/article/details/8460417 三种新的fd加入linux内核的的版本: signalfd:2.6.22 timerfd:2.6.25 eventfd:2.6.22 三种fd的意义: signalfd:传统的处理信号的方式是注册信号处理函数:由于信号是异步发生的,要解决数据的并发访问,可重入问题.signalfd可以将信 号抽象为一个文件描述符,当有信号发生时可以对其read,这样可以将信号的监听放到selec

Linux中的lo回环接口详细介绍

1.linux的网络接口之扫盲 (1)网络接口的命名 这里并不存在一定的命名规范,但网络接口名字的定义一般都是要有意义的.例如: eth0: ethernet的简写,一般用于以太网接口. wifi0:wifi是无线局域网,因此wifi0一般指无线网络接口. ath0: Atheros的简写,一般指Atheros芯片所包含的无线网络接口. lo: local的简写,一般指本地环回接口. (2)网络接口如何工作 网络接口是用来发送和接受数据包的基本设备. 系统中的所有网络接口组成一个链状结构,应用层

linux 内核定时器详解

原文摘自:http://www.linux-cn.com/html/linux/kernel/20070412/1886.shtml Linux内核2.4版中去掉了老版本内核中的静态定时器机制,而只留下动态定时器.相应地在timer_bh()函数中也不再通过run_old_timers()函数来运行老式的静态定时器.动态定时器与静态定时器这二个概念是相对于Linux内核定时器机制的可扩展功能而言的,动态定时器是指内核的定时器队列是可以动态变化的,然而就定时器本身而言,二者并无本质的区别.考虑到静

8.Linux多网卡绑定、子接口

8.Linux多网卡绑定.子接口 ·mii-tool eth0,查看网卡速度.状态.物理连接, ·ethtool eth0,查看网卡物理特性,-i 查看网卡驱动信息,-S 查看网卡状态, ·IP别名:linux支持在一个物理网卡上配置多个ip地址,用来实现类似子接口之类的功能, ·centos/RHEL系统默认会启用NetworkManager对网卡管理,以方便用户使用(网络小图标), ·如果使用子接口需要禁用NetworkManager,service NetworkManager stop,

模仿linux内核定时器代码,用python语言实现定时器

大学无聊的时候看过linux内核的定时器,现在已经想不起来了,也不知道当时有没有看懂,现在想要模仿linux内核的定时器,用python写一个定时器,已经想不起来它的设计原理了,找了一篇blog,linux 内核定时器 timer_list详解. 看了好一会才有些明白,开始参照着用python写了一个.如果在设计服务器的时候,有大量需要精确到秒和秒以下的事件,自己写一个定时器,维护一个类似与内核timer_vec的数据结构,处理服务的定时事件,还是蛮高效的. 附上python代码,github:

【整理】--Linux 内核定时器

一.LINUX内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个函数的一种机制,其实现位于 <linux/timer.h> 和 kernel/timer.c 文件中. 被调度的函数肯定是异步执行的,它类似于一种“软件中断”,而且是处于非进程的上下文中,所以调度函数必须遵守以下规则: 1) 没有 current 指针.不允许访问用户空间.因为没有进程上下文,相关代码和被中断的进程没有任何联系. 2) 不能执行休眠(或可能引起休眠的函数)和调度. 3) 任何被访问的数据结构

Linux下c开发 之 线程通信(转)

Linux下c开发 之 线程通信(转) 1.Linux“线程” 进程与线程之间是有区别的,不过Linux内核只提供了轻量进程的支持,未实现线程模型.Linux是一种“多进程单线程”的操作系统.Linux本身只有进程的概念,而其所谓的“线程”本质上在内核里仍然是进程. 大家知道,进程是资源分配的单位,同一进程中的多个线程共享该进程的资源(如作为共享内存的全局变量).Linux中所谓的“线程”只是在被创建时clone了父进程的资源,因此clone出来的进程表现为“线程”,这一点一定要弄清楚.因此,L

linux网络编程学习笔记之五 -----并发机制与线程?

进程线程分配方式 简述下常见的进程和线程分配方式:(好吧,我仅仅是举几个样例作为笔记...并发的水太深了,不敢妄谈...) 1.进程线程预分配 简言之,当I/O开销大于计算开销且并发量较大时,为了节省每次都要创建和销毁进程和线程的开销.能够在请求到达前预先进行分配. 2.进程线程延迟分配 预分配节省了处理时的负担,但操作系统管理这些进程线程也会带来一定的开销.由此,有个折中的方法是,当某个处理须要花费较长时间的时候,我们创建一个并发的进程或线程来处理该请求.实现也非常easy,在主线程中定时,定

linux网络编程学习笔记之五 -----并发机制与线程池

进程线程分配方式 简述下常见的进程和线程分配方式:(好吧,我只是举几个例子作为笔记...并发的水太深了,不敢妄谈...) 1.进程线程预分配 简言之,当I/O开销大于计算开销且并发量较大时,为了节省每次都要创建和销毁进程和线程的开销.可以在请求到达前预先进行分配. 2.进程线程延迟分配 预分配节省了处理时的负担,但操作系统管理这些进程线程也会带来一定的开销.由此,有个折中的方法是,当某个处理需要花费较长时间的时候,我们创建一个并发的进程或线程来处理该请求.实现也很简单,在主线程中定时,定时到期,