linux开发各种I/O操作简析,以及select、poll、epoll机制的对比

简介: linux开发各种I/O操作简析,以及select、poll、epoll机制的对比

作者:良知犹存

转载授权以及围观:欢迎添加微信公众号:羽林君


IO 概念区分

四个相关概念:

  • 同步(Synchronous)
  • 异步( Asynchronous)
  • 阻塞( Blocking )
  • 非阻塞( Nonblocking)

阻塞I/O

阻塞,就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。

在linux中,默认情况下所有的socket都是阻塞的,一个典型的读操作流程大概是这样:4edc953e2c684bbe819ffa954c899c08.png

当用户进程调用了 read()/recvfrom() 等系统调用函数,它会进入内核空间中,当这个网络I/O没有数据的时候,内核就要等待数据的到来,而在用户进程这边,整个进程会被阻塞,直到内核空间返回数据。当内核空间的数据准备好了,它就会将数据从内核空间中拷贝到用户空间,此时用户进程才解除阻塞的的状态,重新运行起来。

所以,阻塞I/O的特点就是在IO执行的两个阶段(用户空间与内核空间)都被阻塞了。

非阻塞I/O

   非阻塞,就是调用我(函数),我(函数)立即返回。阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。

有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回,它还会抢占CPU去执行其他逻辑,也会主动检测I/O是否准备好。

执行的模型如下:

4edc953e2c684bbe819ffa954c899c08.png

能看到,非阻塞I/O的特点是用户进程需要不断的 主动询问 内核空间的数据准备好了没有。

同步I/O


   在操作系统中,程序运行的空间分为内核空间和用户空间,用户空间所有对io操作的代码(如文件的读写、socket的收发等)都会通过系统调用进入内核空间完成实际的操作。

   而且我们都知道CPU的速度远远快于硬盘、网络等I/O。在一个线程中,CPU执行代码的速度极快,然而,一旦遇到I/O操作,如读写文件、发送网络数据时,就需要等待 I/O 操作完成,才能继续进行下一步操作,这种情况称为同步 I/O。

其实所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。

   实际工作中我们却很少使用同步I/O,因为当你读写某个文件,进行I/O操作时候,如果数据没有及时回应到,那么系统就会将当前执行读写的线程挂起来等待数据的读取完成,而其他需要CPU执行的代码就无法被当前线程执行,这就是同步I/O的弊端。仅仅因为一个I/O操作就会阻塞当前线程,导致其他代码无法执行,当然我们遇到这样时候会选择用多线程或者多进程来并发执行代码。

      但是多线程和多进程也无法根除这种阻塞问题,因为系统内存大小的限制,所以系统不能无限的增加线程和进程。此外过多的线程和进程,就会导致系统切换线程和进程的开销变大,真正运行代码时间就会变少,这样子系统性能也会严重下降。

异步I/O

   简单来说就是,用户不需要等待内核完成实际对io的读写操作就直接返回了。

   当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

I/O过程主要分两个阶段:

1.数据准备阶段

2.内核空间复制回用户进程缓冲区空间

无论阻塞式IO还是非阻塞式IO,都是同步IO模型,区别就在与第一步是否完成后才返回,但第二步都需要当前进程去完成,异步IO呢,就是从第一步开始就返回,直到第二步完成后才会返回一个消息,也就是说,异步能够让你在第一步时去做其它的事情。4edc953e2c684bbe819ffa954c899c08.png

同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞

阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回

因为异步IO把IO的操作给了内核,让内核去操作,同步IO的话,需要等待IO操作从内核态的数据缓冲区拷贝到用户态的数据缓冲区,所以此时的同步IO是阻塞的。

多路复用I/O


   多路复用I/O就是我们说的 select,poll,epoll 等操作,复用的好处就在于 单个进程 就可以同时处理 多个 网络连接的I/O,能实现这种功能的原理就是 select、poll、epoll 等函数会不断的 轮询 它们所负责的所有 socket ,当某个 socket 有数据到达了,就通知用户进程。


一般在Linux下我们会有以下几种的字符设备读写方式,下面是一个使用的对比:


1、查询方法:一直在查询,不断去查询是否有事件发生,整个过程都是占用CPU资源,非常消耗CPU资源。


2、中断方式:当有事件发生时,就去跳转到相应事件去处理,CPU占用时间少。


3、poll方式: 中断方式虽然占用CPU资源少,但是在应用程序上需要不断在死循环里面执行读取函数,应用程序不能去做其它事情。poll机制解决了这个问题,当有事件发生时,才去执行读read函数,按键事件没有按下时<如果规定了时间,超过时间后返回无按键信息>,去执行其它的处理函数。

   这里我们能够看到poll使用的优势,select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

   我们再说一下select,poll和epoll这几个IO复用方式,这时你就会了解它们为什么是同步IO了,以epoll为例,在epoll开发的服务器模型中,epoll_wait()这个函数会阻塞等待就绪的fd,将就绪的fd拷贝到epoll_events集合这个过程中也不能做其它事(虽然这段时间很短,所以epoll配合非阻塞IO是很高效也是很普遍的服务器开发模式--同步非阻塞IO模型)。有人把epoll这种方式叫做同步非阻塞(NIO),因为用户线程需要不停地轮询,自己读取数据,看上去好像只有一个线程在做事情,也有人把这种方式叫做异步非阻塞(AIO),因为毕竟是内核线程负责扫描fd列表,并填充事件链表的,个人认为真正理想的异步非阻塞,应该是内核线程填充事件链表后,主动通知用户线程,或者调用应用程序事先注册的回调函数来处理数据,如果还需要用户线程不停的轮询来获取事件信息,就不是太完美了,所以也有不少人认为epoll是伪AIO,还是有道理的。

select函数

 该函数准许进程指示内核等待多个事件中的任何一个发送,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。select的调用过程如下所示:

4edc953e2c684bbe819ffa954c899c08.png

select的几大缺点:

(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

(3)select支持的文件描述符数量太小了,默认是1024

   poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

   epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

   epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?在此之前,我们先看一下epoll和select和poll的调用接口上的不同, select和poll都只提供了一个函数select或者poll函数。而epoll提供了三个函数, epoll create,epoll cti和epoll wait , epoll create是创建一个epol句柄 ; epoll ctl是注册要监听的事件类型; epoll wait则是等待事件的产生。

   对于第一-个缺点, epoll的解决方案在epoll ctl函数中。每次注册新的事件到epoll句柄中时(在epoll ctI中指定EPOLL CTL ADD) ,会把所有的fd拷贝进内核,而不是在epoll wait的时候重复拷贝。epoll保证 了每个fd在整个过程中只会拷贝一次。

   对于第二个缺点, epoll的解决方案不像select或poll- -样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一-个回调函数 ,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入-一个就绪链表)。epoll wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd (利用schedule_ timeout0实现睡一会,判断一会的效果 ,和select实现中的第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集合从用户态往内核态拷贝一-次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。

在选择select,poll,epoll时要根据具体的使用场合以及这三种方式的自身特点

1、表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。

2、select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善

这就是我分享的select,poll,epoll,其中参考了很多人的文章,如果大家有什么更好的思路,也欢迎分享交流哈。



目录
相关文章
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
405 0
|
9月前
|
Ubuntu 搜索推荐 Linux
详解Ubuntu的strings与grep命令:Linux开发的实用工具。
这就是Ubuntu中的strings和grep命令,透明且强大。我希望你喜欢这个神奇的世界,并能在你的Linux开发旅程上,通过它们找到你的方向。记住,你的电脑是你的舞台,在上面你可以做任何你想做的事,只要你敢于尝试。
456 32
|
11月前
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
3142 77
|
9月前
|
Linux
【Linux】 Linux文件I/O常见操作技巧
以上就是Linux文件I/O操作的一些技巧,接纳它们,让它们成为你在Linux世界中的得力伙伴,工作会变得轻松许多。不过记住,技巧的运用也需要根据实际情况灵活掌握,毕竟,最适合的才是最好的。
276 28
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
628 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
614 0
Linux C/C++之IO多路复用(poll,epoll)
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】
|
5月前
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
632 1
二、Linux文本处理与文件操作核心命令
|
5月前
|
Linux
linux命令—stat
`stat` 是 Linux 系统中用于查看文件或文件系统详细状态信息的命令。相比 `ls -l`,它提供更全面的信息,包括文件大小、权限、所有者、时间戳(最后访问、修改、状态变更时间)、inode 号、设备信息等。其常用选项包括 `-f` 查看文件系统状态、`-t` 以简洁格式输出、`-L` 跟踪符号链接,以及 `-c` 或 `--format` 自定义输出格式。通过这些选项,用户可以灵活获取所需信息,适用于系统调试、权限检查、磁盘管理等场景。
406 137
|
5月前
|
安全 Ubuntu Unix
一、初识 Linux 与基本命令
玩转Linux命令行,就像探索一座新城市。首先要熟悉它的“地图”,也就是/根目录下/etc(放配置)、/home(住家)这些核心区域。然后掌握几个“生存口令”:用ls看周围,cd去别处,mkdir建新房,cp/mv搬东西,再用cat或tail看文件内容。最后,别忘了随时按Tab键,它能帮你自动补全命令和路径,是提高效率的第一神器。
975 57