(1)选择== >时间复杂度O(n)它只知道有一个I/O事件,但不知道是哪些流(可能有一个、多个,甚至全部)。我们只能不分青红皂白地轮询所有的流,找出能读写数据的流,进行操作。所以sele
(1)选择== >时间复杂度O(n)
它只知道有一个I/O事件,但不知道是哪些流(可能有一个、多个,甚至全部)。我们只能不分青红皂白地轮询所有的流,找出能读写数据的流,进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间越长。
(2)poll== >时间复杂度O(n)
Poll本质上与select相同。它将用户传入的数组复制到内核空,然后查询每个fd对应的设备状态。但是,它没有最大连接数限制,因为它是基于链表存储的。
(3)epoll== >时间复杂度O(1)
epoll可以理解为事件轮询。与繁忙轮询和不加选择的轮询不同,epoll会通知我们哪个流有什么I/O事件。所以我们说epoll其实是事件驱动的(每个事件都与fd相关联),这个时候我们对这些流的操作才有意义。(复杂度降低到O(1))
Select、poll和epoll都是IO复用机制。I/O多路复用是一种可以监控多个描述符的机制。一旦描述符就绪(通常是读或写就绪),它就可以通知程序执行相应的读写操作。但是select、poll、epoll本质上都是同步I/O,因为读写事件就绪后都需要负责读写,也就是说这个读写过程是阻塞的,而异步I/O不需要负责读写。异步I/O的实现将负责把数据从内核复制到用户空。
epoll和select均可提供多通道I/O多路复用解决方案。现在的Linux内核什么都可以支持,其中epoll是Linux特有的,而select应该是POSIX规定的,一般操作系统都是这样实现的。
选择:
本质上,select是为了设置或检查存储fd标志位的数据结构,以便进行下一次处理。这样做的缺点是:
1.单个进程可以监控的FD的数量是有限的,也就是说,可以监控的端口的大小是有限的。
一般来说,这个数字和系统内存关系很大,具体数字可以通过cat /proc/sys/fs/file-max查询。32位计算机的默认数量是1024。64位计算机的默认值是2048。
2.扫描socket的时候是线性扫描,也就是采用轮询的方式,效率很低:
当套接字较多时,每次select()通过遍历FD_SETSIZE套接字完成调度,无论哪个套接字是活动的,都会被再次遍历。这样会浪费大量的CPU时间。如果能为套接字注册一个回调函数,并在它们活动时自动完成相关操作,就可以避免轮询,这正是epoll和kqueue所做的。
3.需要维护一个存储大量FD的数据结构,在转移这个结构时会导致user空和cores 空之间的复制成本很高。
轮询:
Poll本质上与select相同。它将用户传入的数组复制到内核空,然后查询每个fd对应的设备状态。如果设备准备好了,它将一个项目添加到设备等待队列中,并继续遍历。如果在遍历所有fd后没有找到就绪设备,它将挂起当前进程,直到设备就绪或主动超时。被唤醒后,它会再次遍历fd。这个过程经历了很多不必要的遍历。
它对最大连接数没有限制,因为它是基于链表存储的,但它也有一个缺点:
1.大量的fd数组作为一个整体在用户状态和内核地址空之间复制,不管这样的复制是否有意义。
2.poll还有一个特点就是“横向触发”。如果报告了fd但未处理,将在下一次轮询中再次报告。
epoll:
Epoll有两种触发模式:EPOLLLT和EPOLLET。LT是默认模式,ET是“高速”模式。在LT模式下,只要这个fd中有可读的数据,每次epoll_wait都会返回其事件提醒用户操作程序,而在ET (Edge Trigger)模式下,只会提示一次,直到下一次数据流入时才会再次提示,不管fd中是否有可读的数据。因此,在ET模式下,当你读取一个fd时,必须读出它的缓冲区,也就是说,你总是读取到read的返回值小于请求值,或者遇到EAGAIN错误。另一个特点是epoll使用“event”的ready通知方式,通过epoll_ctl注册fd。一旦fd准备好了,内核就会使用一个类似于callback的回调机制来激活fd,epoll_wait就可以接收到通知了。
为什么epoll有EPOLLET触发模式?
如果采用EPOLLLT模式,一旦系统中有大量你不需要读写的就绪文件描述符,那么每次调用epoll_wait都会返回,这将大大降低处理器检索它所关心的就绪文件描述符的效率。在EPOLLET的边沿触发模式下,当被监控文件描述符上发生读写事件时,epoll_wait()将通知处理程序进行读写。如果这次没有读写完所有数据(比如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是只通知你一次,直到文件描述符上出现第二次读写事件才会通知你!!!这种模式比水平触发更高效,系统也不会充斥大量你不关心的现成文件描述符
EPOLL的优势:
1.最大并发连接数没有限制,能开的FD上限远大于1024(1g内存上大概能监听10万个端口);
2。效率提升不是一种轮询方式,不会随着FD数的增加而降低。只有活动且可用的FD将调用回调函数;
Epoll最大的优点是它只关心你的“活动”连接,而不管连接的总数。所以在实际网络环境中,Epoll的效率会比select和poll高很多。
3.内存复制,使用mmap()文件映射内存,加快与kernel 空的消息传输;也就是说,epoll使用mmap来减少复制开销。
select、poll和epoll之间的区别总结:
1.支持一个进程可以打开的最大连接数。
挑选
单个进程可以打开的最大连接数由FD_SETSIZE宏定义,其大小为32个整数(在32位机器上,大小为3232,类似地,在64位机器上,FD_SETSIZE为3264)。当然,我们可以修改它,重新编译内核,但是性能可能会受到影响,这需要进一步的测试。
投票
Poll本质上与select相同,但是它对最大连接数没有限制,因为它是基于链表存储的。
使用
虽然连接数有上限,但是非常大。1G内存的机器上可以打开10万个左右的连接,2G内存的机器上可以打开20万个左右的连接。
2.FD急剧增加引起的IO效率问题
挑选
因为每次调用都会线性遍历连接,随着FD的增加,会出现遍历速度慢的“线性降级性能问题”。
投票
同上
使用
因为在epoll内核中的实现是基于每个fd上的回调函数,只有活动套接字才会主动调用回调,所以当活动套接字很少时,使用epoll不存在前两者线性下降的性能问题,但当所有套接字都活动时,可能会出现性能问题。
3.消息传递模式
挑选
当内核需要向用户空传递消息时,就需要内核副本。
投票
同上
使用
epoll是通过在内核和用户空之间共享内存来实现的。
摘要:
综上所述,在选择select、poll、epoll时,要根据具体的使用场合和自身的特点。
1。从表面上看,epoll的性能最好,但当连接数较少且所有连接都非常活跃时,select和poll的性能可能会比epoll好。毕竟epoll的通知机制需要很多函数回调。
2。select效率很低,因为它每次都需要轮询。但是,低效率是相对的,可以根据情况通过好的设计来改善。
关于这三个IO复用的用法,前三个总结写的很清楚,都是通过服务器echo程序测试的。连接如下:
选择选择摘要:io多路复用
轮询的轮询摘要:O多路复用
Epoll:io多路复用的epoll摘要
对比今天这三个IO复用,参考网上和书上的资料,整理如下:
1。选择实现
SELECT的调用过程如下:
(1)使用copy_from_user将fd_set从user 空复制到kernel 空。
(2)注册回调函数__pollwait
(3)遍历所有的fd,调用它们对应的轮询方法(对于socket,这个轮询方法是sock_poll,sock_poll会根据情况调用tcp_poll、udp_poll或者datagram_poll)
(4)以tcp_poll为例。它的核心实现是__pollwait,就是上面注册的回调函数。
(5)_ poll wait的主要工作是将当前(当前进程)挂在设备的等待队列中。不同的设备有不同的等待队列。对于tcp_poll,它的等待队列是SK->;Sk_sleep(注意,在等待队列中挂起一个进程并不意味着该进程已经进入睡眠状态)。设备收到消息(网络设备)或填充文件数据(磁盘设备)后,会唤醒队列上等待睡眠的进程,然后当前的会被唤醒。
(6)当6)poll方法返回时,会返回一个描述读写操作是否就绪的掩码,并根据这个掩码给fd_set赋值。
(7)如果已经遍历了所有的fd,并且没有返回可读可写的mask mask,则调用schedule_timeout(即当前)的进程正在调用select并进入睡眠。当设备驱动程序可以读写自己的资源时,它会唤醒队列中等待睡眠的进程。如果在一定的超时时间(由schedule_timeout指定)后没有人醒来,调用select的进程将再次醒来以获取CPU,然后再次遍历fd以确定是否有就绪的fd。
(8)将fd_set从kernel 空复制到user 空中。
摘要:
SELECT的几个缺点:
(1)每次调用select,都需要将fd集从用户态复制到内核态。当有许多FD时,这种开销会很大
(2)同时,每次调用select都需要遍历内核中传递的所有fd,在fd很多的情况下开销很大
(3)SELECT支持的文件描述符数量太少。默认值是1024
2轮询实现
poll的实现与select非常相似,只是fd集的描述方式不同。poll用pollfd结构代替select的fd_set结构,其他都差不多。管理多个描述符也是根据描述符的状态进行轮询和处理,但是poll对文件描述符的最大数量没有限制。Poll和select还有一个缺点就是包含大量文件描述符的数组是在用户的状态和内核的地址空之间复制的,不管这些文件描述符是否准备好,它的开销都是随着文件描述符数量的增加而线性增加的。
3、epoll
由于epoll是对select和poll的改进,应该可以避免上述三个缺点。epoll是怎么解决的?在此之前,我们先来看看epoll与select和poll的调用接口的区别。select和poll都只提供一个功能——SELECT或poll功能。并且epoll提供了三个函数,epoll_create、epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;Epoll_ctl是注册监听的事件类型;Epoll_wait是等待事件。
对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次在epoll句柄中注册一个新的事件(epoll_ctl中指定了epoll_ctl _ add),所有的fd都会被复制到内核中,而不是在epoll_wait重复复制。Epoll保证每个fd在整个过程中只被复制一次。
对于第二个缺点,与select或poll不同,epoll的解决方案并不是每次都将电流依次加入fd对应的设备等待队列,而是只在epoll_ctl发生时挂起电流一次(这个时间是必不可少的),并为每个fd分配一个回调函数。当设备准备好并唤醒等待队列上的等待者时,将调用该回调函数,并且该回调函数将就绪fd添加到就绪链表中)。epoll_wait的工作其实就是检查这个ready链表中是否有ready fd(使用schedule_timeout()休眠一段时间,判断一段时间的效果类似于select实现中的step 7)。
对于第三个缺点,epoll没有这样的限制。它支持的最大FD限制是可以打开的最大文件数。这个数字一般比2048年大很多。比如1GB内存的机器上大概10万。具体数字可以通过cat /proc/sys/fs/file-max查询。一般来说,这个数字和系统内存有很大关系。
摘要:
(1)select,poll实现需要不断的自己轮询所有的fd集,直到设备准备好,期间睡眠和唤醒可能会交替多次。实际上,epoll还需要调用epoll_wait来不断轮询就绪列表,睡眠和唤醒可能会交替多次。然而,当设备就绪时,它调用回调函数,将就绪fd放入就绪列表,并唤醒在epoll_wait中进入睡眠状态的进程。虽然都要休眠和交替,但是select和poll在“清醒”时都要遍历整个fd集,而epoll在“清醒”时只需要判断就绪链表是否空,节省了大量的CPU时间。这就是回调机制带来的性能提升。
(2)select,poll每次调用都将fd set从用户态复制到内核态,在设备等待队列中挂起current一次,而epoll只复制一次,在等待队列中挂起current一次(在epoll_wait的开头,注意这里的等待队列不是设备等待队列,只是epoll内部定义的等待队列)。这样也能节省不少开支。