linux下IO模式

相关文章:

基础概念

用户空间和内核空间

现在操作系统都采用虚拟寻址,处理器先产生一个虚拟地址,通过地址翻译成物理地址(内存的地址),再通过总线的传递,最后处理器拿到某个物理地址返回的字节。

对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。

进程上下文切换

为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为称为进程的切换(调度)。

一个进程切换到另外一个进程会经历如下步骤,是比较耗资源的:

  1. 保持当前进程的上下文
  2. 切换全局目录以安装一个新的地址空间
  3. 恢复进程B的上下文。

进程的阻塞

正在执行的进程,由于期待的某些事件未发生,由系统自动执行阻塞,使自己由运行状态变为阻塞状态。进程进入阻塞状态,不占用CPU资源。

直接IO和缓存IO

缓存IO又被称为标准IO,大多数文件系统的默认IO操作都是缓存IO。在Linux的缓存IO机制中,以write为例,数据会先拷贝到进程缓存区,再拷贝到内核缓存区,然后写入到磁盘中。对于直接IO机制来说,还是以write为例,数据少了一步,不会拷贝到进程缓存区,而是会直接拷贝到内核缓存区,然后写入到磁盘中。

IO模式

对于一次IO访问,数据先会被拷贝到操作系统内核的缓存区,然后从系统内核的缓存区拷贝到应用程序的缓存区,最后交给进程。所以说当一次IO访问发生时,它会经历两个阶段:

  • 等待数据准备
  • 将数据从内核拷贝到进程中

正因为这两个阶段,linux系统产生了下面五种网络模式方案:

  • 阻塞IO(blocking IO)
  • 非阻塞IO(nonblocking IO)
  • IO多路复用(IO multiplexing)
  • 信号驱动IO(signal driven IO),不常用。
  • 异步IO(asynchronous IO)

BIO模型

如图,一次BIO的访问步骤如下:

  1. 进程发起read,进行recvfrom系统调用;
  2. 内核开始第一阶段,进行数据的准备(从磁盘拷贝到缓存区),需要耗费一定时间。
  3. 与此同时,进程组合,等待数据返回。
  4. 直到数据从内核拷贝到用户空间,内核返回结果,进程解除阻塞。

内核准备数据和数据从内核拷贝到进程内存地址是阻塞的。

例子

老李去火车站买票,排队三天买到一张票。
耗费:在车站吃喝拉撒睡 3天,其他事一件没干。

NIO模型

可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读的时候,流程如下:

如图,一次NIO访问如下步骤:

  1. 当用户进程发起读操作时,如果内核数据还未准备好
  2. 那么它并不会阻塞用户进程,而是立刻返回一个error,从用户角度来说,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。
  3. 当用户进程判断到结果是一个error时,它就知道数据还未准备好,于是它可以再次发起read操作。一旦内核中的数据准备好了,并且又一次收到了用户进程的system call。
  4. 那么它马上就将数据拷贝到了用户内存,然后返回。

所以,NIO的特点是用户进程在内核准备数据的阶段需要不断的主动询问数据准备好了么。

例子

老李去火车站买票,隔12小时去火车站问有没有退票,三天后买到一张票。耗费:往返车站6次,路上6小时,其他时间做了好多事。

IO多路复用

IO多路复用实际上就是用select,poll,epoll监听多个IO对象,当IO对象有变化(有数据)的时候就会通知用户进程。好处是单个进程可以处理多个socket。

  1. 用户进程调用了select,那么整个进程会被阻塞;
  2. 而同时,内核会监控所有select负责的socket;
  3. 当任何一个socket中的数据准备好的时候,select就会返回;
  4. 这个时候当用户进程调用read操作,将数据从内核拷贝到用户进程;

IO多路复用的特点是,通过一种机制一个进程能同时等待多个文件描述符,而这些套接字描述符其中的任意一个进入就绪状态,select()函数就可以返回。

select/epoll的优势并不是对单个连接能处理的更快,而在处理更多的连接。

实际上,IO多路复用,对于每个socket,一般都设置为NIO,但是,整个用户的进程其实还是一直阻塞的,只不过进程是被select这个函数阻塞,而不是被socket IO给阻塞。

select/poll/epoll区别

IO多路复用的本质就是用select/poll/epoll,去监听多个socket对象。

  • select是不断轮询去监听的socket,socket个数有限制,一般为1024个;
  • poll还是采用轮询的方式监听,不过没有个数限制;
  • Epoll并不是采用轮询的方式监听,而是当socket有变化时通过回调的方式主动告知用户进程。

例子

  1. select/poll

    老李去火车站买票,委托黄牛,然后每隔6小时电话黄牛询问,黄牛三天内买到票,然后老李去火车站交钱领票。
    耗费:打电话

  2. epoll

    老李去火车站买票,委托黄牛,黄牛买到后即通知老李去领,然后老李去火车站交钱领票。
    耗费:无需打电话

AIO模型

步骤如下:

  1. 用户进程发起read操作后,立刻就可以去做别的事情
  2. 另一方面,内核角度,当它收到AIO的read请求,首先会立即返回,而不会对用户进程产生任何阻塞;
  3. 内核等待数据准备完成,然后将数据拷贝到用户内存,当这一切都做完的时候,内核会给用户进程发送一个信号,告诉它read操作完成了。

例子

老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李并快递送票上门。
耗费:无需打电话

0%