不知道怎么入门Selet、Poll、Epoll?看这篇文章就够了
liebian365 2024-10-30 04:47 25 浏览 0 评论
前置知识:
- 用户空间和内核空间
- 进程切换
- 进程的阻塞
- 文件描述符
- 缓存 IO
- 中断
如果对以上概念还有不清楚的地方的话,可以先手动百科下再继续往下看~
I/O多路复用
首先我们来了解一下文件描述符:在Unix哲学中:“Everything is a file."Socket、pipe都可以看做文件,并且有自己独一无二的文件描述符(缩写为fd),而文件常常面临着读数据和写数据。
然后我们来了解一下I/O的概念:I/O分别是input和output的缩写,也就是读数据和写数据,我们通过write可以往文件中写数据,而通过read从文件中读取数据。
那阻塞I/O是什么意思呢?如果在文件描述符中的数据还没有准备好的时候:例如在还没有任何数据的时候,发送了read()调用,进程就会堵塞,
那有没有什么好办法?我们可以同时监听多个fd,在有fd就绪的时候进行通知,没有的时候就堵塞?有,那就是I/O多路复用!
I/O多路复用就像是公司的收发室小哥,会替你处理中通、顺丰、韵达、申通…等等各家快递公司的快递,等哪家的快递到了,再打电话通知你拿~这个时候你就有很多时间在办公室愉快地摸鱼啦!
那在操作系统里,该如何设计这样的收发室小哥呢?
- 当有一个fd就绪时进行通知(等待小哥给你发短信)
- 都不可用?在有可用的文件描述符之前一直处于堵塞状态(专心在工位摸鱼)
- 唤醒:哪个fd可用了?(收到短信,下楼拿快递)
- 处理完所有的I/O就绪的fd,没有堵塞(拿完所有的快递,没有摸鱼)
- 返回第一步重新开始(上楼,回到工位,继续摸鱼)
UNIX公司里提供了select、poll、epoll、kqueue这几位年纪、性格和能力都有所差异的收发室小哥,让我们分别介绍下这几位小哥的工作特点吧!
select
select是最早出现在UNIX里的多路复用有关的系统调用:
#include<sys/select.h>
int selcet( int n,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout
);
FD_CLR(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_SET(int fd, fd_set *set);
FD_ZERO(fd_set *set);
readfds、writefds、exceptfds分别表示select所监视的不同类型的文件描述符,分别等待不同的事件:监视是否有数据可读、监视是否有数据可写、是否有异常etc。第一个参数n呢,等于所有集合中最大的文件描述符的值加一。当selet成功返回时,readfds、writefds、exceptfds都会写改为只包含对应类型的I/O就绪的文件描述。举个例子,readfds对应的是中通快递,而显示有编号1、2、3的快递在发往我们收发室,在select检查快递的时候,只有2到了,因此readfds集合就只返回编号为2的快递。参数timeout是指向timeval结构体的指针:
#include<sys/time.h>
struct timeval{
long tv_sec;
long tv_usec;
}
如果该参数不是NULL,在tv_sec秒tv_usec毫秒过后select系统调用就会返回。
FD_ZERO(fd_set *fdset):清空fdset与所有文件描述符的联系。
FD_SET(int fd, fd_set *fdset):建立文件描述符fd与fdset的联系。
FD_CLR(int fd, fd_set *fdset):清除文件描述符fd与fdset的联系。
FD_ISSET(int fd, fd_set *fdset):检查fd_set联系的文件描述符fd是否可读写,>0表示可读写。
typedef __kernel_fd_set fd_set;
#undef __FD_SETSIZE
#define __FD_SETSIZE 1024
typedef struct {
unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))];
} __kernel_fd_set;
select()的机制中提供一种fd_set的数据结构,fd_set是一个struct, 其内部只有一个 由16个元素组成的unsigned long数组,其实就是常见的bitmap,这个数组一共可以表示16 × 64=1024位, 每一位用来表示一个 fd, 这也就是 select针对读,定或异常每一类最多只能有 1024个fd 限制的由来。当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一Socket或文件可读。
select机制的问题
1.单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,这个大小是由宏控制的,进行改变很麻烦(在linux内核头文件中,有这样的定义:#define __FD_SETSIZE 1024),并且由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差;
2.内核 / 用户空间内存拷贝问题:select需要复制大量的句柄数据结构,产生巨大的开销;
3.select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调用还是会将这些文件描述符通知进程。
poll
早期淘宝购物并不发达,所以收发室的快件并不是很多,select轮询上限1024个快件的工作方式也够用了。但随着网络购物的发展,收发室的快递越来越多,select模式下fd_set的长度限制就开始成为了致命缺陷。
吸取了select的教训,新引入的poll就不再采用定长数组来保存所监控的fd信息了,而是采用可以改变长度的链表,没有了连接数目的限制,poll可以支持高并发的需求。除此以外,poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。
poll用来保存事件的数据结构:
#include <poll.h>
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};
使用poll函数进行操作:
#include <poll.h>
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
fds:一个pollfd队列的队头指针,我们先把需要监视的文件描述符和他们上面的事件放到这个队列中
nfds:队列的长度
timeout:事件操作,设置指定正数的阻塞事件,0表示非阻塞模式,-1表示永久阻塞。
epoll:
设想一个场景,双十一到了,公司总共有一万份快递要来,而实际上每一段时间只会有几十个或者几百个快递到达收发室,也就是说,每一时刻工作人员只需要处理一万份快递中的一小部分快递,那么如何才能高效的处理这种场景呢?如果继续采用select和poll轮询的方法,收发室工作人员一定会累坏吧。实际上,在Linux 2.4版本以前, unix公司就是这么做的。Linux 2.4版中,引入了epoll,它在Linux内核中申请了一个简易的文件系统,把原先的一个select或poll调用分成了3部分,相当于把select和poll的工作分给三个人来做:
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
- 调用epoll_create建立一个epoll对象(在epoll文件系统中给这个句柄分配资源);
- 调用epoll_ctl向epoll对象中添加这100万个连接的套接字;
- 调用epoll_wait收集发生事件的连接。
这样只需要在进程启动时建立1个epoll对象,并在需要的时候向它添加或删除连接就可以了,因此,在实际收集事件时,epoll_wait的效率就会非常高,因为调用epoll_wait时并没有向它传递这100万个连接,内核也不需要去遍历全部的连接。
当进程调用epoll_create方法的时候,Linux内核就会创建一个eventpoll结构体,对于这个结构体我们主要需要关注两个成员:
struct eventpoll {
...
struct rb_root rbr;
struct list_head rdllist;
...
};
我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外,还会再建立一个rdllist双向链表,用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个rdllist双向链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。
所有添加到epoll中的事件都会与设备(如网卡)驱动程序建立回调关系,也就是说相应事件的发生时会调用这里的回调方法。这个回调方法在内核中叫做ep_poll_callback,它会把这样的事件放到上面的rdllist双向链表中。
在epoll中,对于每一个事件都会建立一个epitem结构体:
struct epitem {
...
//红黑树节点
struct rb_node rbn;
//双向链表节点
struct list_head rdllink;
//事件句柄等信息
struct epoll_filefd ffd;
//指向其所属的eventepoll对象
struct eventpoll *ep;
//期待的事件类型
struct epoll_event event;
...
}; // 这里包含每一个事件对应着的信息。
因此当我们调用epoll_wait检查是否有就绪的事件时,只是检查eventpoll对象中的rdllist双向链表中是否有epitem, 如果非空的话,就将这里的时间复制到用户态内存中,同时返回时间的数量。因此epoll_wait的效率很高。而epoll_ctl对epoll对象中的事件进行增删查改时,由于使用了红黑树,因此查找事件也是十分高效的。
总结
Linux系统中存在着select、poll、epoll三种系统调用,针对高并发,且任一时间只有少数socket是活跃的场景epoll的性能要好很多。 但是如果在并发量低,socket都比较活跃的情况下,select/poll就不见得比epoll慢了(就像我们常常说快排比插入排序快,但是在特定情况下这并不成立)。并且由于历史原因(先来后到),select因为兼容性比较好,仍然在被大量应用中。
相关推荐
- 精品博文嵌入式6410中蓝牙的使用
-
BluetoothUSB适配器拥有一个BluetoothCSR芯片组,并使用USB传输器来传输HCI数据分组。因此,LinuxUSB层、BlueZUSB传输器驱动程序以及B...
- win10跟这台计算机连接的前一个usb设备工作不正常怎么办?
-
前几天小编闲来无事就跑到网站底下查看粉丝朋友给小编我留言询问的问题,还真的就给小编看到一个问题,那就是win10跟这台计算机连接的一个usb设备运行不正常怎么办,其实这个问题的解决方法时十分简单的,接...
- 制作成本上千元的键盘,厉害在哪?
-
这是稚晖君亲自写的开源资料!下方超长超详细教程预警!!全文导航:项目简介、项目原理说明、硬件说明、软件说明项目简介瀚文智能键盘是一把我为自己设计的——多功能、模块化机械键盘。键盘使用模块化设计。左侧的...
- E-Marker芯片,USB数据线的“性能中枢”?
-
根据线缆行业的研究数据,在2019年搭载Type-C接口的设备出货量已达到20亿台,其中80%的笔记本电脑和台式电脑采用Type-C接口,50%的智能手机和平板电脑也使用Type-C接口。我们都知道,...
- ZQWL-USBCANFD二次开发通讯协议V1.04
-
修订历史:1.功能介绍1.1型号说明本文档适用以下型号: ZQWL-CAN(FD)系列产品,USB通讯采用CDC类实现,可以在PC机上虚拟出一个串口,串口参数N,8,1格式,波特率可以根据需要设置(...
- win10系统无法识别usb设备怎么办(win10不能识别usb)
-
从驱动入手,那么win10系统无法识别usb设备怎么办呢?今天就为大家分享win10系统无法识别usb设备的解决方法。1、右键选择设备管理器,如图: 2、点击更新驱动程序,如图: 3、选择浏览...
- 微软七月Win8.1可选补丁有内涵,含大量修复
-
IT之家(www.ithome.com):微软七月Win8.1可选补丁有内涵,含大量修复昨日,微软如期为Win7、Win8.1发布7月份安全更新,累计为6枚安全补丁,分别修复总计29枚安全漏洞,其中2...
- 如何从零开始做一个 USB 键盘?(怎么制作usb)
-
分两种情况:1、做一个真正的USB键盘,这种设计基本上不涉及大量的软件编码。2、做一个模拟的USB键盘,实际上可以没有按键功能,这种的需要考虑大量的软件编码,实际上是一个单片机。第一种设计:买现成的U...
- 电脑识别U盘失败?5个实用小技巧,让你轻松搞定USB识别难题
-
电脑识别U盘失败?5个实用小技巧,让你轻松搞定USB识别难题注意:有些方法会清除USB设备里的数据,请谨慎操作,如果不想丢失数据,可以先连接到其他电脑,看能否将数据复制出来,或者用一些数据恢复软件去扫...
- 未知usb设备设备描述符请求失败怎么解决
-
出现未知daousb设备设备描述符请求失du败解决办zhi法如下:1、按下Windows+R打开【运行】;2、在版本运行的权限输入框中输入:services.msc按下回车键打开【服务】;2、在服务...
- 读《飘》47章20(飘每章概括)
-
AndAhwouldn'tleaveMissEllen'sgrandchildrenfornotrashystep-patobringup,never.Here,Ah...
- 英翻中 消失的过去 37(消失的英文怎么说?)
-
翻译(三十七):消失的过去/茱迪o皮考特VanishingActs/JodiPicoult”我能做什么?“直到听到了狄利亚轻柔的声音,我才意识到她已经在厨房里站了好一会儿了。当她说话的时候,...
- RabbitMQ 延迟消息实战(rabbitmq如何保证消息不被重复消费)
-
现实生活中有一些场景需要延迟或在特定时间发送消息,例如智能热水器需要30分钟后打开,未支付的订单或发送短信、电子邮件和推送通知下午2:00开始的促销活动。RabbitMQ本身没有直接支持延迟...
- Java对象拷贝原理剖析及最佳实践(java对象拷贝方法)
-
作者:宁海翔1前言对象拷贝,是我们在开发过程中,绕不开的过程,既存在于Po、Dto、Do、Vo各个表现层数据的转换,也存在于系统交互如序列化、反序列化。Java对象拷贝分为深拷贝和浅拷贝,目前常用的...
- 如何将 Qt 3D 渲染与 Qt Quick 2D 元素结合创建太阳系行星元素?
-
Qt组件推荐:QtitanRibbon:遵循MicrosoftRibbonUIParadigmforQt技术的RibbonUI组件,致力于为Windows、Linux和MacOSX提...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)