Linux IO模型

github 地址https://github.com/liujunsheng0/notes/blob/master/linux/io模型.md

概念说明

  • 用户空间和内核空间(user space and kernel space)

    **操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。**为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。

    简单来说,内核空间是 Linux 内核的运行空间,仅供内核使用,用户空间是用户程序的运行空间,供各个进程使用。为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不受影响。

  • 进程切换

    为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。

    从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:

    1. 保存处理机上下文,包括程序计数器和其他寄存器
    2. 更新PCB信息
    3. 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列
    4. 选择另一个进程执行,并更新其PCB
    5. 更新内存管理的数据结构。
    6. 恢复处理机上下文
  • 进程阻塞

    在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。

    可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。

  • 文件描述符(file descriptor,简称fd)

    文件描述符是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。

    文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

    linux下一切皆文件

  • 缓存IO(标准IO)

    缓存 IO 又被称作标准 IO,大多数文件系统的默认 IO 操作都是缓存 IO。

    在 Linux 的缓存 IO 机制中,操作系统会将 IO 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

Linux IO 模型

对于一次IO访问(以read举例),数据先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间(用户空间)。所以说,当一个read操作发生时,它会经历两个阶段:

  1. 等待内核中的数据准备完成
  2. 将内核中的数据拷贝到进程(用户空间)中

网络IO本质上就是socket的读取,socket在linux系统被抽象为流,IO可以理解为对流的操作。

对于socket而言,一次IO访问过程如下,

  1. 等待网络上的数据到达,然后被复制到内核的缓冲区
  2. 将数据从内核缓冲区复制到应用进程中

网络应用需要处理的无非就是两大类问题:网络IO,数据计算。两者相比,网络IO的延迟,给应用带来的性能瓶颈大于后者

网络IO的模型大致有如下几种:

  • 同步模型(synchronous IO)
  • 阻塞IO(bloking IO)
  • 非阻塞IO(non-blocking IO)
  • 多路复用IO(multiplexing IO)
  • 信号驱动式IO(signal-driven IO)
  • 异步IO(asynchronous IO)

同步阻塞IO(blocking IO)

  • 场景

    我和女友点完餐后,不知道什么时候能做好,只好坐在餐厅里面等,直到做好,然后吃完才离开。女友本想逛街,但是不知道饭能什么时候做好,只好和我一起在餐厅等,而不能去逛街,直到吃完饭才能去逛街,中间等待做饭的时间浪费掉了。这就是典型的阻塞。

  • 网络模型

    同步阻塞 IO 模型是最常用的一个模型,也是最简单的模型。 在linux中,默认情况下所有的socket都是阻塞的。它符合人们最常见的思考逻辑。

    阻塞就是进程挂起,CPU处理其它进程去了。

    在这个IO模型中,用户空间的应用程序执行系统调用recvform,这会导致应用程序阻塞,什么也不干,直到内核数据准备好,并且将数据从内核复制到应用进程,最后应用进程再处理数据。在等待数据到处理数据的两个阶段,整个进程都被阻塞。不能处理别的网络IO。调用应用程序处于一种不再消费 CPU 而只是简单等待响应的状态。

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

  • 流程描述

    1. 用户进程发起系统调用 recv/recvfrom,内核开始准备数据,数据拷贝到内和缓冲区中是需要时间的,在此时间内,用户进程会被阻塞(进程自己主动选择的阻塞)

      以网络IO为例,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候内核就要等待数据

    2. 内核数据准备好,将数据从内核拷贝至用户空间(也就是应用进程),拷贝也是需要时间的…

    3. 数据从内核-> 用户空间完成后,内核返回结果,应用进程解除阻塞,开始处理数据

  • 特点

    IO执行的两个阶段都被阻塞了

    缺点:性能差

同步非阻塞

  • 场景

    我女友不甘心白白在这等,又想去逛商场,又担心饭好了。所以我们逛一会,回来询问服务员饭好了没有,来来回回好多次,饭都还没吃都快累死了啦。这就是非阻塞。需要不断的询问,是否准备好了,即通过轮询的方式询问不断询问

  • 网络模型

    发起recvform系统调用后,进程并没有阻塞,内核会马上返回结果给内核,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环进行recvform系统调用。这个过程被称之为轮询。

    轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,将数据从内核拷贝至用户空间,进程仍然是属于阻塞的状态

    在linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程如图所示:

  • 流程描述

    当用户进程发起非阻塞的系统调用recvfrom时,如果

    1. 内核中的数据没有准备好

      并不会阻塞用户进程,而是立刻返回一个error。从用户进程角度讲,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好。

    2. 内核中的数据准备好了

      立即返回数据准备好了,应用进程会再次发起系统调用,将数据由内核拷贝到用户空间,然后返回,应用进程处理数据

    应用进程不断重复上述过程

  • 特点

    用户进程需要不断的主动询问内核数据好了没有。

    优点:应用进程阻塞时间减少

    缺点:任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。

IO多路复用

  • 场景

    如果每个人都像我和女友一样,等饭期间出去逛街,过一阵就来询问服务员饭好没,服务员估计会累死。所以

    餐厅开发了一款名为"开饭了"的app,可查看当前饭菜准备状态,当饭菜全部做好了时,app会发送通知,这样我和女友逛街时,就不用去询问服务员了,直接安装这个app就好了。这就是典型的IO多路复用,每个用餐人员看app就能知道自己的饭是否已经做完。

  • 网络模型

    **问题:**由于同步非阻塞方式需要不断主动轮询,轮询会消耗大量的CPU时间。而 “后台” 可能有多个任务在同时进行,如果每个进程都去轮询,会消耗更多的CPU时间。

    **解决方案:**大神们想到只要有一个专门的进程(暂时叫中间进程)去轮询内核中每个进程的数据准备状态,每个进程再与这个中间进程交互,从而获取内核数据准备状态,这就是所谓的IO多路复用。

    阻塞I/O只能阻塞一个I/O操作,而I/O复用模型能够阻塞多个I/O操作,所以才叫做多路复用

    UNIX/Linux 下的 select、poll、epoll 就是干这个的,select、poll、poll 的好处在于单个进程能处理多个网络连接的IO。

    以select为例,处理过程大致如下

  • 流程描述,以select为例

    1. 用户进程调用select,用户进程阻塞
      • 数据准备好,通知用户进程(并不是通知所有用户进程- -,而是通知发起调用的用户进程)
      • 无数据准备好,阻塞直到超时
    2. 用户进程发起系统调用recvfrom,将数据由内核复制到用户空间

    IO多路复用多了一个select的系统调用,所以,如果处理的连接数不是很高的话,使用select的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。

    初看好像IO多路复用没什么用,其实select、poll、epoll的优势在于可以以较少的代价来同时监听处理多个IO,select、poll、epoll的优势在于能处理更多的连接

  • 特点

    1. 阻塞I/O只能阻塞一个I/O操作,而I/O复用模型能够阻塞多个I/O操作
    2. 在IO多路复用中,如上图所示,整个用户进程其实是一直被阻塞的。只不过进程是被select这个函数阻塞,而不是被recvfrom给阻塞。所以IO多路复用是阻塞在select,poll,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上。将数据从内核空间复制到用户空间也是阻塞的。

    所以从整个IO过程来看,他们是顺序执行的,因此可以归为同步模型(synchronous)。都是进程主动等待且向内核检查状态。

    同步是需要主动等待消息通知,而异步则是被动接收消息通知,通过回调、通知、状态等方式来被动获取消息。IO多路复用在阻塞到select阶段时,用户进程是主动等待并调用select函数获取数据就绪状态消息,并且其进程状态为阻塞。所以,把IO多路复用归为同步阻塞模式。

select就像一个中介,每个人都找中介获取自己所需要的信息。

异步非阻塞IO

  • 场景

    女友不想逛街,餐厅又太吵了,回家好好休息一下。于是我们叫外卖,打个电话点餐,然后我和女友可以在家好好休息一下,饭好了送货员送到家里来。这就是典型的异步,只需要打个电话说一下,然后可以做自己的事情,饭好了就送来了。

  • 网络模型

    相对于同步IO,异步IO不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的。

    Linux提供了AIO库函数实现异步,但是用的很少。目前有很多开源的异步IO库,例如libevent、libev、libuv。异步过程如下图所示:

  • 流程描述

    用户进程发起aio_read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何阻塞。然后,内核会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,内核会给用户进程发送一个signal或执行一个基于线程的回调函数来完成这次 IO 处理过程,告诉它read操作完成了。

    在 Linux 中,通知的方式是"信号":

    1. 如果这个进程正在用户态忙着做别的事(例如在计算两个矩阵的乘积),那就强行打断之,调用事先注册的信号处理函数,这个函数可以决定何时以及如何处理这个异步任务。由于信号处理函数是突然闯进来的,因此跟中断处理程序一样,有很多事情是不能做的,因此保险起见,一般是把事件 “登记” 一下放进队列,然后返回该进程原来在做的事。

    2. 如果这个进程正在内核态忙着做别的事,例如以同步阻塞方式读写磁盘,那就只好把这个通知挂起来了,等到内核态的事情忙完了,快要回到用户态的时候,再触发信号通知。

    3. 如果这个进程现在被挂起了,例如无事可做 sleep 了,那就把这个进程唤醒,下次有 CPU 空闲的时候,就会调度到这个进程,触发信号通知。

    异步 API 说来轻巧,做来难,这主要是对 API 的实现者而言的。Linux 的异步 IO(AIO)支持是 2.6.22 才引入的,还有很多系统调用不支持异步 IO。Linux 的异步 IO 最初是为数据库设计的,因此通过异步 IO 的读写操作不会被缓存或缓冲,这就无法利用操作系统的缓存与缓冲机制。

IO多路复用

IO多路复用是指内核一旦发现进程(上文所说的中间进程)指定的一个或者多个IO条件准备读取,它就通知该进

程(通知的是上文所说的那个中间进程,而不是用户进程)。IO多路复用适用如下场合:

(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。

(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。

(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。

(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。

(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

Linux支持IO多路复用的系统调用有select、poll、epoll,这些调用都是内核级别的。但select、poll、epoll本质上

都是同步I/O,先是阻塞住等待就绪的socket,然后阻塞将数据从内核拷贝到用户内存的过程。

select,poll,epoll区别

selectpollepoll
操作方式遍历遍历回调
底层实现数组链表哈希表
IO效率每次调用都进行线性遍历,时间复杂度为O(n)每次调用都进行线性遍历,时间复杂度为O(n)事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到rdllist里面。时间复杂度O(1)
最大连接数1024(x86)或 2048(x64)无上限无上限
fd拷贝每次调用select,都需要把fd集合从用户态拷贝到内核态每次调用poll,都需要把fd集合从用户态拷贝到内核态调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝

学习链接

https://segmentfault.com/a/1190000003063859

https://www.jianshu.com/p/486b0965c296

https://blog.csdn.net/wjtyy/article/details/46373089

https://www.jianshu.com/p/dfd940e7fca2

http://www.cnblogs.com/Anker/archive/2013/08/14/3258674.html

https://blog.csdn.net/lixungogogo/article/details/52219951

https://cloud.tencent.com/developer/article/1005481

https://zhuanlan.zhihu.com/p/22834126

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值