最高效的进(线)程间通信机制--eventfd

我们常用的进程(线程)间通信机制有管道,信号,消息队列,信号量,共享内存,socket等等,其中主要作为进程(线程)间通知/等待的有管道pipe和socketpair。线程还有特别的condition。

今天来看一个liunx较新的系统调用,它是从LINUX 2.6.27版本开始增加的,主要用于进程或者线程间的通信(如通知/等待机制的实现)。


首先来看一下函数原型:

[cpp] view plain copy
  1. #include <sys/eventfd.h>  
  2.   
  3.        int eventfd(unsigned int initval, int flags);  

下面是它man手册中的描述,我照着翻译了一遍(我英语四级434,你们要是怀疑下文的话,可以直接去man eventfd ^^):


eventfd()创建了一个"eventfd object",能在用户态用做事件wait/notify机制,通过内核取唤醒用户态的事件。这个对象保存了一个内核维护的uint64_t类型的整型counter。这个counter初始值被参数initval指定,一般初值设置为0。


它的标记可以有以下属性:

EFD_CLOECEX,EFD_NONBLOCK,EFD_SEMAPHORE。

在linux直到版本2.6.26,这个flags参数是没用的,必须指定为0。


它返回了一个引用eventfd object的描述符。这个描述符可以支持以下操作:

read:如果计数值counter的值不为0,读取成功,获得到该值。如果counter的值为0,非阻塞模式,会直接返回失败,并把errno的值指纹EINVAL。如果为阻塞模式,一直会阻塞到counter为非0位置。

write:会增加8字节的整数在计数器counter上,如果counter的值达到0xfffffffffffffffe时,就会阻塞。直到counter的值被read。阻塞和非阻塞情况同上面read一样。

close:这个操作不用说了。


重点是支持这个:

 poll(2), select(2) (and similar)
              The returned file descriptor supports poll(2) (and analogously epoll(7)) and select(2), as follows:
              *  The file descriptor is readable (the select(2) readfds argument; the poll(2) POLLIN flag) if the  counter  has  a
                 value greater than 0.
              *  The  file descriptor is writable (the select(2) writefds argument; the poll(2) POLLOUT flag) if it is possible to
                 write a value of at least "1" without blocking.
              *  If an overflow of the counter value was detected, then select(2) indicates the  file  descriptor  as  being  both
                 readable  and  writable,  and  poll(2)  returns a POLLERR event.  As noted above, write(2) can never overflow the
                 counter.  However an overflow can occur if 2^64 eventfd "signal posts" were performed by the KAIO subsystem (the‐
                 oretically possible, but practically unlikely).  If an overflow has occurred, then read(2) will return that maxi‐
                 mum uint64_t value (i.e., 0xffffffffffffffff).
              The eventfd file descriptor also supports the other file-descriptor multiplexing APIs: pselect(2) and ppoll(2)
.


它的内核代码实现是这样子的:

[cpp] view plain copy
  1. int eventfd_signal(struct eventfd_ctx *ctx, int n)  
  2. {  
  3.     unsigned long flags;  
  4.   
  5.     if (n < 0)  
  6.         return -EINVAL;  
  7.     spin_lock_irqsave(&ctx->wqh.lock, flags);  
  8.     if (ULLONG_MAX - ctx->count < n)  
  9.         n = (int) (ULLONG_MAX - ctx->count);  
  10.     ctx->count += n;  
  11.     if (waitqueue_active(&ctx->wqh))  
  12.         wake_up_locked_poll(&ctx->wqh, POLLIN);  
  13.     spin_unlock_irqrestore(&ctx->wqh.lock, flags);  
  14.   
  15.     return n;  
  16. }  

本质就是做了一次唤醒,不用read,也不用write,与eventfd_write的区别是不用阻塞。


说了这么多,我们来看一个例子,理解理解其中的含义:

[cpp] view plain copy
  1. #include <sys/eventfd.h>  
  2. #include <unistd.h>  
  3. #include <stdlib.h>  
  4. #include <stdio.h>  
  5. #include <stdint.h>             /* Definition of uint64_t */  
  6.   
  7. #define handle_error(msg) \  
  8.     do { perror(msg); exit(EXIT_FAILURE); } while (0)  
  9.   
  10. int  
  11. main(int argc, char *argv[])  
  12. {  
  13.     int efd, j;  
  14.     uint64_t u;  
  15.     ssize_t s;  
  16.       
  17.     if (argc < 2) {  
  18.         fprintf(stderr, "Usage: %s <num>...\n", argv[0]);  
  19.         exit(EXIT_FAILURE);  
  20.     }     
  21.   
  22.     efd = eventfd(0, 0);   
  23.     if (efd == -1)   
  24.         handle_error("eventfd");  
  25.       
  26.     switch (fork()) {  
  27.         case 0:  
  28.             for (j = 1; j < argc; j++) {  
  29.             printf("Child writing %s to efd\n", argv[j]);  
  30.             u = strtoull(argv[j], NULL, 0);   
  31.             /* strtoull() allows various bases */  
  32.      s = write(efd, &u, sizeof(uint64_t));  
  33.             if (s != sizeof(uint64_t))  
  34.                 handle_error("write");  
  35.             }  
  36.             printf("Child completed write loop\n");  
  37.             exit(EXIT_SUCCESS);  
  38.         default:  
  39.             sleep(2);  
  40.   
  41.             printf("Parent about to read\n");  
  42.             s = read(efd, &u, sizeof(uint64_t));  
  43.             if (s != sizeof(uint64_t))  
  44.                 handle_error("read");  
  45.             printf("Parent read %llu (0x%llx) from efd\n",  
  46.                    (unsigned long long) u, (unsigned long long) u);  
  47.             exit(EXIT_SUCCESS);  
  48.   
  49.         case -1:  
  50.             handle_error("fork");  
  51.   }  
  52. }  
输出:

$ ./a.out 1 2 4 7 14
           Child writing 1 to efd
           Child writing 2 to efd
           Child writing 4 to efd
           Child writing 7 to efd
           Child writing 14 to efd
           Child completed write loop
           Parent about to read
           Parent read 28 (0x1c) from efd

注意:这里用了sleep(2)保证子进程循环写入完毕,得到的值就是综合28。如果不用sleep(2)来保证时序,当子进程写入一个值,父进程会立马从eventfd读出该值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/385016.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

malloc,calloc,realloc

与堆操作相关的两个函数 malloc #include<stdio.h> #include<stdlib.h> #include<string.h>int main() {char *p malloc(10); //内存随机&#xff0c;未做处理int i;for(i 0; i < 10: i){printf(“%d “,p[i]);} free(p);return 0; } 运行结果&…

Linux内核同步机制之completion

内核编程中常见的一种模式是&#xff0c;在当前线程之外初始化某个活动&#xff0c;然后等待该活动的结束。这个活动可能是&#xff0c;创建一个新的内核线程或者新的用户空间进程、对一个已有进程的某个请求&#xff0c;或者某种类型的硬件动作&#xff0c;等等。在这种情况下…

可变参数函数(一)

一个函数可以接受不定数的参数个数&#xff0c;这就是可变参数函数&#xff0c;比较常见的比如printf(),scanf()&#xff1b; printf(const char* format,…); printf(“%d”,i); printf(“%s”,s); printf(“the number is %d,stirng is :%s”,i,s); 变量参数函数的简单实现&a…

Linux内核线程kernel thread详解--Linux进程的管理与调度

内核线程为什么需要内核线程Linux内核可以看作一个服务进程(管理软硬件资源&#xff0c;响应用户进程的种种合理以及不合理的请求)。 内核需要多个执行流并行&#xff0c;为了防止可能的阻塞&#xff0c;支持多线程是必要的。 内核线程就是内核的分身&#xff0c;一个分身可以处…

可变参数函数(二)

函数样例&#xff1a; #include<stdio.h> #include<stdlib.h> #include<stdarg.h>double add(int n,...) {int i 0;double sum 0;va_list argptr;va_start(argptr,n);for(i 0 ; i < n; i){double d va_arg(argptr,double);printf("%d argument …

Linux 内核网络协议栈 ------sk_buff 结构体 以及 完全解释 (2.6.16)

在2.6.24之后这个结构体有了较大的变化&#xff0c;此处先说一说2.6.16版本的sk_buff&#xff0c;以及解释一些问题。一、先直观的看一下这个结构体~~~~~~~~~~~~~~~~~~~~~~在下面解释每个字段的意义~~~~~~~~~~~[cpp] view plaincopyprint?struct sk_buff { /* These…

可变参数输出(三)

Linux C关于输出函数的定义&#xff1a; int printf(const char *format,…); int vprintf(const char * format,va_list ap); int vfprintf(FILE *stream,cosnt char *format,va_list ap); int vsprintf(char *str,const char *format,va_list ap); int vsnprintf(char *str,s…

最常用的设计模式---适配器模式(C++实现)

适配器模式属于结构型的设计模式&#xff0c;它是结构型设计模式之首&#xff08;用的最多的结构型设计模式&#xff09;。 适配器设计模式也并不复杂&#xff0c;适配器它是主要作用是将一个类的接口转换成客户希望的另外一个接口这样使得原本由于接口不兼容而不能一起工作的那…

Linux 简单打印日志(二)

#include<stdio.h> #include<stdlib.h> #include<string.h> #include<time.h> //#include<windows.h> #include <unistd.h> // linux下头文件#define FILE_MAX_SIZE (1024*1024)void get_local_time(char* buffer){time_t rawtime;struct …

程序随笔——C++实现的一个线程池

1.线程池简介 我们知道在线程池是一种多线程处理形式&#xff0c;处理过程中我们将相应的任务提交给线程池&#xff0c;线程池会分配对应的工作线程执行任务或存放在任务队列中&#xff0c;等待执行。 面向对象编程中&#xff0c;创建和销毁对象是需要消耗一定时间的&#xff0…

线程池原理及创建并C++实现

本文给出了一个通用的线程池框架&#xff0c;该框架将与线程执行相关的任务进行了高层次的抽象&#xff0c;使之与具体的执行任务无关。另外该线程池具有动态伸缩性&#xff0c;它能根据执行任务的轻重自动调整线程池中线程的数量。文章的最后&#xff0c;我们给出一个简单示例…

Linux 打印简单日志(一)

简单日志输出&#xff1a; #include<stdio.h> #include<string.h> #include<stdlib.h>void write(char* filename,char* szStr){FILE* fp;fp fopen(filename,"at");if(fp ! NULL){fwrite(szStr,256,1,fp); //fclose(fp);fp NULL;} }int main(int…

c++简单线程池实现

线程池&#xff0c;简单来说就是有一堆已经创建好的线程&#xff08;最大数目一定&#xff09;&#xff0c;初始时他们都处于空闲状态&#xff0c;当有新的任务进来&#xff0c;从线程池中取出一个空闲的线程处理任务&#xff0c;然后当任务处理完成之后&#xff0c;该线程被重…

Linux 打印可变参数日志

实现了传输进去的字符串所在的文档&#xff0c;函数和行数显示功能。 实现了将传入的可变参数打印到日志功能。 #include<stdio.h> #include<stdarg.h> #include<string.h>const char * g_path "/home/exbot/wangqinghe/log.txt"; #define LOG(fm…

C++强化之路之线程池开发整体框架(二)

一.线程池开发框架 我所开发的线程池由以下几部分组成&#xff1a; 1.工作中的线程。也就是线程池中的线程&#xff0c;主要是执行分发来的task。 2.管理线程池的监督线程。这个线程的创建独立于线程池的创建&#xff0c;按照既定的管理方法进行管理线程池中的所有线程&#xf…

vfprintf()函数

函数声明&#xff1a;int vfprintf(FILE *stream, const char *format, va_list arg) 函数参数&#xff1a; stream—这是指向了FILE对象的指针&#xff0c;该FILE对象标识了流。 format—c语言字符串&#xff0c;包含了要被写入到流stream中的文本。它可以包含嵌入的format标签…

Makefile(二)

将生产的.o文件放进指定的文件中&#xff08;先创建该文件夹&#xff09; src $(wildcard ./*.cpp) obj $(patsubst %.cpp,./output/%.o,$(src))target test$(target) : $(obj)g $(obj) -o $(target) %.o: %.cppg -c $< -o output/$.PHONY:clean clean:rm -f $(target) $…

TCP粘包问题分析和解决(全)

TCP通信粘包问题分析和解决&#xff08;全&#xff09;在socket网络程序中&#xff0c;TCP和UDP分别是面向连接和非面向连接的。因此TCP的socket编程&#xff0c;收发两端&#xff08;客户端和服务器端&#xff09;都要有成对的socket&#xff0c;因此&#xff0c;发送端为了将…

UML类图符号 各种关系说明以及举例

UML中描述对象和类之间相互关系的方式包括&#xff1a;依赖&#xff0c;关联&#xff0c;聚合&#xff0c;组合&#xff0c;泛化&#xff0c;实现等。表示关系的强弱&#xff1a;组合>聚合>关联>依赖 相互间关系 聚合是表明对象之间的整体与部分关系的关联&#xff0c…

寻找数组中第二大数

设置两个数值来表示最大数和第二大数&#xff0c;在循环比较赋值即可 //找给定数组中第二大的数int get_smax(int *arr,int length) {int max;int smax;if(arr[0] > arr[1]){max arr[0];smax arr[1];}else{max arr[1];smax arr[0];}for(int i 2; i < length; i){if(…