linux网络编程--eventfd

eventfd 在内核版本,2.6.22以后有效。查看内核版本可以用命令 uname -r。



eventfd类似于管道的概念,可以实现线程间的事件通知,类似于pipe。而eventfd 是一个比 pipe 更高效的线程间事件通知机制,一方面它比 pipe 少用一个 file descriper,节省了资源;另一方面,eventfd 的缓冲区管理也简单得多,全部“buffer”一共只有8字节,不像pipe那样可能有不定长的真正buffer。
eventfd的缓冲区大小是sizeof(uint64_t)也就是8字节,它是一个64位的计数器,写入递增计数器,读取将得到计数器的值,并且清零。


涉及API:
#include 
int eventfd(unsigned int initval, int flags);

这个函数会创建一个事件对象 (eventfd object), 用来实现进程(线程)间的等待/通知(wait/notify) 机制. 内核会为这个对象维护一个64位的计数器(uint64_t)。
并且使用第一个参数(initval)初始化这个计数器。调用这个函数就会返回一个新的文件描述符(event object)。2.6.27版本开始可以按位设置第二个参数(flags)。
flags 有如下的一些宏可以使用:

EFD_NONBLOCK:功能同open(2) 的O_NONBLOCK,设置对象为非阻塞状态,如果没有设置这个状态的话,read(2)读eventfd,并且计数器的值为0 就一直堵塞在read调用当中,要是设置了这个标志, 就会返回一个 EAGAIN 错误(errno = EAGAIN)。效果也如同 额外调用select(2)达到的效果。


EFD_CLOEXEC顾名思义是在执行 exec() 调用时关闭文件描述符,防止文件描述符泄漏给子进程。


如果是2.6.26或之前版本的内核,flags 必须设置为0。


创建这个对象后,可以对其做如下操作。


write 将缓冲区写入的8字节整形值加到内核计数器上。


read 读取8字节值, 并把计数器重设为0. 如果调用read的时候计数器为0, 要是eventfd是阻塞的, read就一直阻塞在这里,否则就得到 一个EAGAIN错误。
如果buffer的长度小于8那么read会失败, 错误代码被设置成 EINVAL。

close 当不需要eventfd的时候可以调用close关闭, 当这个对象的所有句柄都被关闭的时候,内核会释放资源。 为什么不是close就直接释放呢, 如果调用fork 创建
进程的时候会复制这个句柄到新的进程,并继承所有的状态。

(ps:也就是说,在write之后没有read,但是又write新的数据,那么读取的是这两次的8个字节的和,在read之后再write,可以完成read和write之间的交互)


下面看一个简单的eventfd的示例:

#include 
#include 
#include 
#include 
#include              /* Definition of uint64_t */

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

int
main(int argc, char *argv[])
{
   int efd = eventfd(0, 0);
   int j;
   if (efd == -1)
       handle_error("eventfd");

   int ret = fork();
   if(ret == 0) //child
   {
       for (j = 1; j < 10 ; j++) {
           printf("Child writing %d to efd\n", j);
           uint64_t one = j;
           ssize_t s = write(efd, &one, sizeof one);
           if (s != sizeof one)
               handle_error("write");
       }
       printf("Child completed write loop\n");

       exit(EXIT_SUCCESS);
   }
   else  //parent
   {
       sleep(2);
       uint64_t one;
       ssize_t s = read(efd, &one, sizeof one);
       if (s != sizeof one)
           handle_error("read");
       printf("Parent read %llu from efd\n",(unsigned long long)one);
       exit(EXIT_SUCCESS);
   }
}


输出:

linux网络编程--eventfd_第1张图片
解释:

这个例子很简单,创建一个eventfd用于父子进程之间通信,子进程写入数据,然后父进程读取。例子并没有什么实际意义。


我们再看一个稍微复杂一点的例子,使用epoll和eventfd:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define EPOLL_MAX_NUM 10

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

int efd = -1;

void *read_thread(void *arg)
{
	int ret = 0;
	uint64_t count = 0;
	int ep_fd = -1;
	struct epoll_event events[EPOLL_MAX_NUM];

	if (efd < 0)
	{
		printf("efd not inited.\n");
		return;
	}

	ep_fd = epoll_create(1024);
	if (ep_fd < 0)
	{
		handle_error("epoll_create fail: ");
	}

	{
		struct epoll_event read_event;

		read_event.events = EPOLLIN;
		read_event.data.fd = efd;  //add eventfd to epoll

		ret = epoll_ctl(ep_fd, EPOLL_CTL_ADD, efd, &read_event);
		if (ret < 0)
		{
			handle_error("epoll ctl failed:");
		}
	}

	while (1)
	{
		ret = epoll_wait(ep_fd, &events[0], EPOLL_MAX_NUM, -1);
		if (ret > 0)
		{
			int i = 0;
			for (; i < ret; i++)
			{	/*
				if (events[i].events & EPOLLHUP)
				{
					printf("epoll eventfd has epoll hup.\n");
				}
				else if (events[i].events & EPOLLERR)
				{
					printf("epoll eventfd has epoll error.\n");
				}
				else */
				 if (events[i].events & EPOLLIN)
				{
					int event_fd = events[i].data.fd;
					ret = read(event_fd, &count, sizeof(count));
					if (ret < 0)
					{
						handle_error("read fail:");
					}
					else
					{
						struct timeval tv;

						gettimeofday(&tv, NULL);
						printf("success read from efd, read %d bytes(%llu) at %lds %ldus\n",
							   ret, count, tv.tv_sec, tv.tv_usec);
					}
				}
			}
		}
		else if (ret == 0)
		{
			/* time out */
			printf("epoll wait timed out.\n");
			break;
		}
		else
		{
			handle_error("epoll wait error:");
		}
	}

}

int main(int argc, char *argv[])
{
	pthread_t pid = 0;
	uint64_t count = 0;
	int ret,i;

	efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
	if (efd < 0)
	{
		handle_error("eventfd failed.");
	}

	ret = pthread_create(&pid, NULL, read_thread, NULL);
	if (ret < 0)
	{
		handle_error("pthread create:");
	}

	for (i = 0; i < 5; i++)
	{
		count = 4;
		ret = write(efd, &count, sizeof(count));
		if (ret < 0)
		{
			handle_error("write event fd fail:");
		}

		sleep(1);
	}
	
	printf("write_end\n");	
	
	pthread_join(pid, NULL);

	close(efd);
	return 0;
}


你可能感兴趣的:(网络编程,linux,eventfd,pipe)