操作系统-IO模型

操作系统-IO模型

  • 用户空间及内核空间
  • Linux 网络 IO 模型
    • 阻塞IO(Blocking IO)
    • 非阻塞IO(Non-Blocking IO)
    • IO复用(IO Multiplexing)
      • 文件描述符fd
      • select
    • 信号驱动的IO(Singal Driven IO)
    • 异步IO
    • 5中IO模型的对比
  • 参考文档

用户空间及内核空间

我们知道现代操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接访问内核,保证内核的安全,操作系统将虚拟存储空间划分为两部分——一部分为内核空间,一部分为用户空间。针对Linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF)供内核使用,称为内核空间;而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF)供各个进程使用,称为用户空间。每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

寻址空间是用来干嘛的?和内存空间有何差别?

Linux寻址空间分配如下图所示:

  • 0xC0000000~0xFFFFFFFF,内核空间,1GB
  • 0x00000000~0xBFFFFFFF,用户控件,3GB
    操作系统-IO模型_第1张图片
    有了用户空间和内核空间,整个Linux内部结构可以分为三部分,从最底层到最上层依次是:硬件–>用户空间–>内核空间。如下图所示:
    操作系统-IO模型_第2张图片
    需要注意的细节问题,从上图可以看出内核的组成:
  1. 内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是用户空间还是内核空间,他们都处于虚拟空间中。
  2. Linux使用两级保护机制,0级供内核使用,3级供用户程序使用。

Linux 网络 IO 模型

我们都知道,为了OS的安全性等的考虑,进程是无法直接操作IO设备的,其必须通过系统调用请求内核来协助完成IO动作,而内核会为每个IO设备维护一个buffer。如下图所示:
操作系统-IO模型_第3张图片
整个请求过程为:用户进程发起请求,内核接收到请求后,从IO设备中获取数据到buffer中,再将buffer中的数据copy到用户进程的地址空间,该用户进程获取到数据后再响应客户端

在整个请求过程中,数据输入至buffer需要时间,而从buffer复制数据至进程也需要时间。因此根据在这两段时间内等待方式的不同,IO动作可以分为以下五种模式:

  • 阻塞IO(Blocking IO)
  • 非阻塞IO(Non-Blocking IO)
  • IO复用(IO Multiplexing)
  • 信号驱动的IO(Singal Driven IO)
  • 异步IO(Asynchrnous IO)

说明:如果想了解更多可能需要linux/unix方面的知识了,可自行去学习一些网络编程原理,不过对大多数java程序员来说,不需要了解底层细节,知道个概念就行。本文最重要的参考文献是Richard Stevens的“UNIX® Network Programming Volume 1, Third Edition: The Sockets Networking ”,6.2节“I/O Models ”。
操作系统-IO模型_第4张图片
操作系统-IO模型_第5张图片
记住如下两点很重要:

  1. 等待数据准备 (Waiting for the data to be ready)
  2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)

阻塞IO(Blocking IO)

在Linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:
操作系统-IO模型_第6张图片
当用户进程调用了recvfrom这个系统调用,内核就开始了IO的第一个阶段:等待数据准备。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候内核就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当内核一直等到数据准备好了,它就会将数据从内核中拷贝到用户进程,然后内核返回拷贝结果,用户进程才解出block的状态,重新运行起来。所以Blocking IO的特点是在IO执行的两个阶段都被Block了

-------------------------- note ---------------------------

Linux下的Socket模式都是Block的。

阻塞IO——在IO执行的两个阶段都被阻塞。

非阻塞IO(Non-Blocking IO)

在Linux下,可用通过设置Socket使其变为Non-Blocking。当对一个Non-Blocking Socket执行读操作时,流程如下图:
操作系统-IO模型_第7张图片
当用户进程调用recvfrom时,系统不会阻塞用户进程,而是立刻返回一个ewouldblock错误,从用户进程角度讲,并不需要等待,而是马上就得到了一个结果。用户进程判断标志是ewouldblock时,就知道数据还没准备好,于是它就可以去做其他的事了。于是它可以再次发送recvfrom,一旦内核中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到用户进程,然后返回。当一个应用程序在一个循环里非阻塞调用recvfrom,我们称之为轮询。应用程序不断轮询内核,看看是否已经准备好了某些操作,这通常是浪费CPU时间,并不影响用户进程。

-------------------------- note ---------------------------

非阻塞IO——用户进程轮询CPU,CPU立即返回(成功或失败),用户进程与CPU非阻塞,但浪费CPU时间。

IO复用(IO Multiplexing)

IO复用(IO Multiplexing)这个词可能有点陌生,但是如果我说select、poll,大概就都能明白了,有些地方也称这种IO方式为事件驱动的IO(Event Driven IO)。我们都知道,select/epoll 的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是 select/epoll 这个function会不断的轮询所负责的所有Socket,当某个socket有数据到达了,就通知用户进程。它的流程图如下:
操作系统-IO模型_第8张图片
当用户进程调用了select,那么整个进程就会被block,而同时,内核会监视select所负责的所有socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,就数据从内核拷贝到用户进程。
这个图和 Blocking IO 的图其实并没有太大的区别,事实上,还更差一些。因为这里需要使用两个 System Call(select 和 recvfrom),而 bolocking IO 只使用了一个 system call(recvfrom)。但是,用 select 的优势在于它可以同时处理多个 Connection。(多说一句,所以,如果处理的连接数量不是很高的话,使用 select/epoll 的 web server 不一定比使用 multi-threading + blocking IO 的 web server 性能更好,可能延迟还更大。select/epoll的优势不是在于单个连接能处理得更快,而是在于能处理更多的连接。
在 IO Multiplexing Model 中,实际上,对于每一个Socket,一般都设置成 non-blocking。但是,如上图所示,整个用户的 process 其实是一直被 block 的,只不过 process 是被 select 这个函数 block,而不是被 socket IO 给 block。

-------------------------- note ---------------------------

IO复用与非阻塞IO的区别是什么?
IO复用的线程使用select轮询socket,但socket本身是非阻塞的,线程只会阻塞于select函数;而非阻塞IO在没有数据时直接返回错误,是非阻塞的。

文件描述符fd

Linux内核将所有的外部设备都看作一个文件来操作,那么我们对外部设备的操作都可以看做对文件的操作。我们对一个文件的读写,都通过调用内核提供的系统调用来进行,内核给我们返回一个 file descriptor(fd,文件描述符),而对一个 socket 的读写也会有响应的描述符,称为 socketfd(socket 描述符)。描述符就是一个数字,指向内核中的一个结构体(文件路径、数据区等一些属性),我们的应用程序对文件的读写就是通过对描述符进行读写来完成。

select

基本原理:select函数监视的文件描述符分3类,分别是 writefds、readfds 和 exceptfds。调用后 select 函数会阻塞,直到有描述符就绪(有数据可读、可写或者有except)或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当 select 函数返回后,可以通过遍历 fdset,来找到就绪的描述符。
缺点

  1. select最大的缺陷就是单个进程所打开的FD的数量是有限制的,它有FDSETSIZE设置,32位ji

信号驱动的IO(Singal Driven IO)

异步IO

5中IO模型的对比

参考文档

NIO相关基础篇

你可能感兴趣的:(操作系统)