Linux网络编程 - Eventfd的使用
概述
linux系统提供了各种各样的IPC,管道,信号,消息队列,信号量,共享内存,socket等,各有各的应用场景,今天我们来讲一个linux系统提供的系统调用eventfd
,这个系统调用比较新,从linux内核 2.6.22
版本加入到内核中的。主要是为了高效的利用系统资源实现通知的管理和送达.
在linux系统中,eventfd是一个用来通知事件的文件描述符,是由内核向用户空间的应用发送通知的机制,可以有效地被用来实现用户空间的事件/通知驱动的应用程序。简而言之,eventfd就是用来触发事件通知的。
eventfd详解
函数原型:
|
|
调用接口eventfd()
创建一个eventfd对象,或者也可以理解打开一个eventfd类型的文件,类似普通文件的open操作。
该对象是一个内核维护的无符号64位整型计数器,初始化为initval
的值。
第二个参数flags
在linux 2.6.26之前版本并没有使用,必须初始化为0,在2.6.27之后的版本flag
才被使用。
flags
是以下三个标志位OR
结果:
EFD_CLOEXEC(2.6.27~)
: eventfd()返回一个文件描述符,如果该进程被fork的时候,这个文件描述符也会被复制过去,这个时候就会有多个描述符指向同一个eventfd对象,如果设置了这个标志,则子进程在执行exec的时候,会自动清除掉父进程的这个文件描述符。EFD_NONBLOCK(2.6.27~)
:文件描述符会被设置为O_NONBLOCK
,如果没有设置这个标志位,read操作的时候将会阻塞直到计数器中有值,如果设置了这个这个标志位,计数器没有值得时候也会立刻返回-1.EFD_SEMAPHORE(2.6.30~)
: 这个标志位会影响read操作。具体可以参考read
方法.
操作方法
read
读取计数器的值。
- 如果计数器中的值大于0
- 设置了
EFD_SEMAPHORE
标志位,则返回1,且计数器中的值减去1. - 没有设置
EFD_SEMAPHORE
标志位,则返回计数器中的值,且计数器设置为0.
- 设置了
- 如果计数器中的值为0
- 设置了
EFD_NONBLOCK
标志位就直接返回-1. - 没有设置
EFD_NONBLOCK
标志位就会一直阻塞直到计数器中的值大于0.
- 设置了
write
向计数器中写入值。
- 如果写入的值和小于
0xFFFFFFFFFFFFFFFE
,则写入成功。 - 如果写入的值和大于
0xFFFFFFFFFFFFFFFE
- 设置了
EFD_NONBLOCK
标志位就直接返回-1 - 如果没有设置
EFD_NONBLOCK
标志位,则会一直阻塞直到read操作执行。
- 设置了
IO多路复用
epoll()/poll()/select(): 支持 IO 多路复用操作
close
关闭文件描述符
使用事例
父子进程间读写示例:
|
|
执行结果:
|
|
典型应用场景及优势
Eventfd
在信号通知的场景下,相对比pipe
有非常大的资源和性能优势,
本质其实是counter(计数器)
和channel(数据信道)
的区别。
-
打开文件数量的差异 由于
pipe
是半双工的传统IPC实现方式,所有两个线程通信需要两个pipe文件描述符,而用eventfd
只需要打开一个文件描述符。 总所周知,文件描述符是系统中非常宝贵的资源,linux的默认值只有1024个而已,pipe
只能在两个进程/线程间使用,面向连接,使用之前就需要创建好两个pipe
,而eventfd
是广播式的通知,可以多对多。 -
内存使用的差别
eventfd
是一个计数器,内核维护的成本非常低,大概是自旋锁+唤醒队列的大小,8个字节的传输成本也微乎其微,而pipe
完全不同,一来一回数据在用户空间和内核空间有多达4次的复制,而且最糟糕的是,内核要为每个pipe分配最少4k的虚拟内存页,哪怕传送的数据长度为0. -
与epoll完美结合
eventfd
设计之初就与epoll
完美结合,支持非阻塞读取等,就是为epoll
而生的,而pipe是Unix时代就有了,那时候不仅没有epoll
,连linux还没诞生。
当pipe只用来发送通知,放弃它,放心的使用eventfd
。
eventfd
配合epoll
才是它存在的原因。