【八】同步、异步、阻塞、非阻塞、用户空间、内核空间、5种io模型

一、先知概念

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

但是我想说一下,链接中的这篇文章,有个概念是错的,它里面贴了一张图,把I/O多路复用归类到异步阻塞中。我认为不存在异步阻塞这种概念。

1.1 用户空间与内核空间

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

1.2 进程切换

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

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

  1. 保存处理机上下文,包括程序计数器和其他寄存器。

  2. 更新PCB信息。

  3. 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。

  4. 选择另一个进程执行,并更新其PCB。

  5. 更新内存管理的数据结构。

  6. 恢复处理机上下文。

注:总而言之就是很耗资源,具体的可以参考这篇文章:进程切换

1.3 进程的阻塞

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的

1.4 文件描述符fd

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

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

1.5 缓存 IO##

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

缓存 IO 的缺点:

数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。

二、同步、异步、阻塞、非阻塞

参考:

https://blog.csdn.net/lengxiao1993/article/details/78154467,该文介绍了同步、异步、阻塞、非阻塞、用户空间、内核空间

我自己的理解就是

阻塞、非阻塞的区别在于:等待结果的过程中,线程/进程是否被挂起、让出了CPU。

同步、异步的区别在于:本次调用的返回是否为一个确切的结果。

而关于同步、异步的理解,鑫大爷的跟我的完全不一样,他的理解是:

同步是由用户空间线程发起调用并等待操作完成。

异步是由用户空间线程发起调用且不等待操作完成,之后由内核空间完成操作并通知用户空间线程。

同步异步区别:同步是由用户空间发起调用并完成操作,异步是由用户空间发起调用内核空间完成操作

三、IO的5中模型

参考:https://blog.csdn.net/historyasamirror/article/details/5778378

对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread)及用户空间,另一个就是系统内核(kernel)。

当一个read操作发生时,它会经历两个阶段:

1 等待数据准备 (Waiting for the data to be ready)

2 将数据从内核拷贝到进程(用户空间)中 (Copying the data from the kernel to the process)

IO Model的区别就是在两个阶段上各有不同的情况。

1.blocking IO  同步阻塞

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

在等待数据和拷贝数据这两个阶段用户进程/线程都被阻塞了。

当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。

而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。

2.nonblocking IO  同步非阻塞

linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

第一阶段等待数据时:用户进程/线程没有被阻塞,没有释放CPU,它在不停的询问内核数据来了没 

当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。

从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。

用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。

第二阶段拷贝数据时:

一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

3.IO multiplexing  多路复用

select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking 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。
 

netty就是这种模型,Java的NIO就是这种模型

4.signal driven IO  信号驱动

5.asynchronous IO 异步非阻塞

第一阶段等待数据和第二阶段拷贝数据,用户进程/线程都不会被阻塞

用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

6.各个IO Model的比较如图所示:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章