理论
Linux 2.6.27后添加了一个新的特性,就是eventfd,是用来实现多进程或多线程的之间的事件通知的,也可以由内核通知用户空间应用程序事件。
接口
eventfd的创建是通过eventfd函数实现的,返回值即是该eventfd所对应的文件描述符,函数的原型如下所示:
#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);
eventfd在内核里的核心是一个计数器counter,它是一个uint64_t的整形变量counter,初始值为initval。
- initval:创建eventfd时它所对应的64位计数器的初始值;
- flags: eventfd文件描述符的标志,用以改变 eventfd 的行为
- 如果是2.6.26或之前版本的内核,flags 必须设置为0
- 2.6.27以上内核有效
EFD_CLOEXEC (since Linux 2.6.27)
:- 文件被设置成 O_CLOEXEC,创建子进程 (fork) 时不继承父进程的文件描述符。
- FD_CLOEXEC 标志,即 close-on-exec,这样在调用 exec 后会自动关闭文件描述符。因为通常执行另一个程序后,会用全新的程序替换子进程的正文,数据,堆和栈等,原来的文件描述符变量也不存在了,这样就没法关闭没用的文件描述符了。
EFD_NONBLOCK(since Linux 2.6.27)
:- 设置文件描述符为非阻塞的
- 设置了这个标志后,如果没有数据可读,就返回一个 EAGAIN 错误,不会一直阻塞。
EFD_SEMAPHORE (since Linux 2.6.30)
:- 提供类似信号量语义的 read 操作,简单说就是计数值 count 递减 1。可以多次read
操作方法
一切皆为文件是 Linux 内核设计的一种高度抽象,eventfd 的实现也不例外,我们可以使用操作文件的方法操作 eventfd。
read
消费者需要对信号量进行down操作时,调用read从eventfd读即可。
- read函数会从eventfd对应的64位计数器中读取一个8字节的整型变量;
- read函数设置的接收buf的大小不能低于8个字节,否则read函数会出错,errno为EINVAL;
- read函数返回的值是按小端字节序的;
read返回值:
- 如果eventfd设置了
EFD_SEMAPHORE
,那么每次read就会返回1,并且让eventfd对应的计数器减一 - 如果eventfd没有设置EFD_SEMAPHORE,那么每次read就会直接返回计数器中的数值,read之后计数器就会置0。
- 不管是哪一种,当计数器为0时,如果继续read,那么read就会阻塞(如果eventfd没有设置EFD_NONBLOCK)或者返回EAGAIN错误(如果eventfd设置了EFD_NONBLOCK)。
write
-
在没有设置EFD_SEMAPHORE的情况下,write函数会将发送buf中的数据写入到eventfd对应的计数器中,最大只能写入0xffffffffffffffff,否则返回EINVAL错误;
-
在设置了EFD_SEMAPHORE的情况下,write函数相当于是向计数器中进行“添加”,比如说计数器中的值原本是2,如果write了一个3,那么计数器中的值就变成了5。如果某一次write后,计数器中的值超过了0xfffffffffffffffe(64位最大值-1),那么write就会阻塞直到另一个进程/线程从eventfd中进行了read(如果write没有设置EFD_NONBLOCK),或者返回EAGAIN错误(如果write设置了EFD_NONBLOCK)。
除此之外,eventfd还支持select和poll,与一般的读写描述符相类似,这里就不多说了,如下所示:
实践
测试标志
阻塞标志
#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while(0)
int main(int argc, char** argv)
{
int efd;
ssize_t rc;
efd = eventfd(0, 0);
if(-1 == efd){
handle_error("eventfd");
}
uint64_t buf;
printf("wait for read:\n");
rc = read(efd, &buf, sizeof(uint64_t));
printf("read finished: %zd\n", rc);
printf("Parent read %llu from efd\n",(unsigned long long)buf);
return 0;
}
从下图中可以看出,默认是阻塞的,也就是read没有数据可读时会一直停在read处
如果将其设置为非阻塞标志EFD_NONBLOCK
,就不会阻塞了,如果没有数据就直接过EFD_NONBLOCK
#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while(0)
int main(int argc, char** argv)
{
int efd;
ssize_t rc;
efd = eventfd(0, EFD_NONBLOCK);
if(-1 == efd){
handle_error("eventfd");
}
uint64_t buf = 0;
printf("wait for read:\n");
rc = read(efd, &buf, sizeof(uint64_t));
if(rc == -1){
printf("read finished: %zd, error: %s\n", rc, strerror(errno));
}else{
printf("Parent read %llu from efd\n",(unsigned long long)buf);
}
return 0;
}
EFD_SEMAPHORE
在没有设置EFD_SEMAPHORE 之前:
#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while(0)
int main(int argc, char** argv)
{
int efd;
ssize_t rc;
efd = eventfd(0, 0);
if(-1 == efd){
handle_error("eventfd");
}
uint64_t buf = 11;
rc = write(efd, &buf, sizeof(uint64_t));
if(rc != sizeof(uint64_t)){
handle_error("write");
}
printf("Line[%d] wait for read:\n", __LINE__);
rc = read(efd, &buf, sizeof(uint64_t));
if(rc == -1){
printf("Line[%d] read finished: %zd, error: %s\n",__LINE__, rc, strerror(errno));
}else{
printf("Line[%d] read finished: %zd, from efd get: %llu\n",__LINE__, rc,(unsigned long long)buf);
}
printf("Line[%d] wait for read:\n", __LINE__);
rc = read(efd, &buf, sizeof(uint64_t));
if(rc == -1){
printf("Line[%d] read finished: %zd, error: %s\n",__LINE__, rc, strerror(errno));
}else{
printf("Line[%d] read finished: %zd, from efd get: %llu\n",__LINE__, rc,(unsigned long long)buf);
}
return 0;
}
从下面可以看出,不设置EFD_SEMAPHORE
,如果有数据可读,read会一次读完。第二次read时因为没有数据可读,就一直阻塞住
#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while(0)
int main(int argc, char** argv)
{
int efd;
ssize_t rc;
efd = eventfd(0, EFD_SEMAPHORE );
if(-1 == efd){
handle_error("eventfd");
}
uint64_t buf = 3;
rc = write(efd, &buf, sizeof(uint64_t));
if(rc != sizeof(uint64_t)){
handle_error("write");
}
int i = 0;
while (1){
printf(" Line[%d] order [%d] wait for read:\n", __LINE__, i);
rc = read(efd, &buf, sizeof(uint64_t));
if(rc == -1){
printf("Line[%d] read finished: %zd, error: %s\n",__LINE__, rc, strerror(errno));
break;
}else{
printf("Line[%d] read finished: %zd, from efd get: %llu\n",__LINE__, rc,(unsigned long long)buf);
}
i++;
}
return 0;
}
如上面可以看出,设置 EFD_SEMAPHORE
,如果有数据可读, read每次都会读出1来,直到读取完才阻塞。
当然,如果想没有数据读取就退出,那么可以设置非阻塞标志EFD_NONBLOCK
#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while(0)
int main(int argc, char** argv)
{
int efd;
ssize_t rc;
efd = eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK );
if(-1 == efd){
handle_error("eventfd");
}
uint64_t buf = 3;
rc = write(efd, &buf, sizeof(uint64_t));
if(rc != sizeof(uint64_t)){
handle_error("write");
}
int i = 0;
while (1){
printf(" Line[%d] order [%d] wait for read:\n", __LINE__, i);
rc = read(efd, &buf, sizeof(uint64_t));
if(rc == -1){
printf("Line[%d] read finished: %zd, error [%d]: %s ; EAGAIN = [%d]\n",__LINE__, rc,errno, strerror(errno), EAGAIN);
break;
}else{
printf("Line[%d] read finished: %zd, from efd get: %llu\n",__LINE__, rc,(unsigned long long)buf);
}
i++;
}
return 0;
}
父子进程间通信
#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while(0)
int main(int argc, char** argv)
{
int efd, i;
uint64_t buf;
ssize_t rc;
if(argc < 2){
fprintf(stderr, "Usage: %s <num>...\n",argv[0]);
exit(EXIT_FAILURE);
}
efd = eventfd(0, 0);
if(-1 == efd){
handle_error("eventfd");
}
switch(fork()){
case 0:
for(i = 1; i < argc; i++){
printf("Child writing %s to efd\n",argv[i]);
buf = atoll(argv[i]);
rc = write(efd, &buf, sizeof(uint64_t));
if(rc != sizeof(uint64_t)){
handle_error("write");
}
}
printf("Child completed write loop\n");
exit(EXIT_SUCCESS);
default:
sleep(2);
printf("Parent about to read\n");
rc = read(efd, &buf, sizeof(uint64_t));
if(rc != sizeof(uint64_t))
handle_error("read");
printf("Parent read %llu from efd\n",(unsigned long long)buf);
case -1:
handle_error("fork");
}
return 0;
}
线程间唤醒
#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>
void * threadFunc(void *args){
uint64_t buf;
int rc;
while (1){
rc = read(*(int *)args, &buf, sizeof(buf));
if(rc == 8){
printf("notify success\n");
}
printf("rc = %llu, buffer = %lu\n",(unsigned long long)rc, buf);
}
}
int main(int argc, char** argv)
{
pthread_t tid;
int rc;
uint64_t buf = 1;
int efd;
efd = eventfd(0,0); // blocking
if(efd == -1){
perror("eventfd");
}
if(pthread_create(&tid, NULL, threadFunc, &efd) != 0){
perror("pthread_create");
exit(-1);
}
while(1){
rc = write(efd, &buf, sizeof(buf));
if(rc != 8){
perror("write");
}
sleep(2);
}//end while
close(efd);
}
#include <malloc.h>
#include <pthread.h>
#include <zconf.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <wait.h>
#include <sys/eventfd.h>
int main(int argc, char** argv)
{
int efd = eventfd(10, 0);
uint64_t wdata = 0, rdata = 0;
if(read(efd, &rdata, 8) == -1){
perror(NULL);
if(errno != EAGAIN){
return 0;
}
}
printf("Init data %lu\n", rdata); // 10
// 读完了就没有了
wdata = 20;
if(write(efd,&wdata,8) == -1) //父进程写20
{
perror(NULL);
return 0;
}
printf("write data %lu\n", wdata); // 10
if(fork() == 0){
if(read(efd,&rdata,8) == -1) //子进程读计数器
{
perror(NULL);
return 0;
}
printf("child read %lu\n", rdata);
wdata = 30;
if(write(efd,&wdata,8) == -1) //子进程写30
{
perror(NULL);
return 0;
}
printf("child write %lu\n", wdata);
exit(0);
}
wait(NULL);
if(read(efd,&rdata,8) == -1) //父进程读计数器
{
perror(NULL);
return 0;
}
printf("parant read %lu\n", wdata);
close(efd);
return 0;
}