I/O操作如何完成?

由于进程无法直接操作I/O设备,因此必须通过系统调用请求kernel来协助完成I/O操作,内核会为每一个I/O设备维护一个buffer。其工作流程为:

 

对于输入而言,等待(wait)数据输入至buffer中需要时间,在从buffer复制(copy)到用户进程缓存区中也需要时间。因此,根据等待模式不同,I/O动作可以分为5种模式:

linux中的5种I/O模型

1、阻塞I/O模型

在这个模型下。当进程执行系统调用时,如果在用户进程空间的缓存区中没有找到相应的数据,则进程将处于阻塞状态。当内核从磁盘上读取数据到内核空间的buffer中,并且buffer中的数据复制到用户进程空间的缓存区中这段时间里,进程都将处于阻塞状态。当数据到达用户进程缓存区中,进程就会解除阻塞状态,再有进程将数据返回给客户端。
由于进程处于阻塞状态,因此很少占用cpu,只是等待cpu的响应。因此,这种模型可以提高cpu的使用效率。在这种模型下,如果用户进程空间中依然没有数据,此时将进程唤醒,仍然没有什么用,因此,我们将这种睡眠叫做不可中断睡眠。
其工作原理如图所示:

 

2、非阻塞I/O模型

在这种模型下。进程执行系统调用,如果在用户进程空间缓存区中没有找到数据,则进程不会处于阻塞状态。而是进程不断的询问用户进程空间缓存区中是否有数据。如果没有,立即返回EWOULDBLOCK,此时由于进程处于非阻塞状态,因此,该进程可以做其他的事情。
在这种模型下,进程需要不断的询问用户进程缓存区中是否有数据,因此,会造成cpu大量的进行上下文切换(进程切换),会大量的消耗cpu。这种模型很少被人使用。
其工作原理如图所示:

 

3、I/O多路复用

在这种模型下,进程会受阻于select,poll或epoll函数。这两个函数可以阻塞多个I/O操作,可以同时对多个读操作和多个写操作进行检测,直到数据变成可读或可写时,即数据已经加载到buffer中。才真正调用I/O操作函数,然后再将buffer中的数据复制到用户进程缓存区中,然后进程读取其数据,最后返回给客户端。
其工作原理如图所示:

 

4、信号驱动I/O模型

在这种模型,当内核将数据读取到内核空间中的buffer中时,内核会为进程生成一个SIGIO信号。告知进程数据已准备好了,然后进程执行系统调用,将buffer中的数据拷贝至用户进程缓存区中,最后进程读取相应的数据返回给客户端。
其工作原理如图所示:

 

5、异步I/O模型

在这种模型下,数据从磁盘加载到buffer中,再将buffer中的数据复制到用户进程缓存区。当数据复制完成后,内核会发生一个信号给进程,这样进程会在用户进程缓存区中读取数据,然后再返回给客户端。
其工作原理如图所示:

异步I/O模型与信号驱动I/O模型的主要区别在于;信号驱动模型是由内核告知何时启动I/O操作;而异步I/O模型是由内核告知I/O操作何时完成。

 

 

5种不同的I/O模型比较

 

同步I/O:synchronous I/O,引起请求进程阻塞,直到I/O完成。

异步I/O:asynchronous I/O ,不导致请求进程阻塞。 

 

水平触发:隔一段时间通知一次,直到进程看到

边缘触发:只发送一次通知,不管进程是否看到

进程和线程的特点
进程特点:
1、对于多进程模型而言,一个进程响应一个请求
2、进程量大,进程切换次数过多
3、每个进程的地址空间是独立,很多空间是重复的数据,所以内存使用效率较低

线程特点:

1、每个线程响应一个请求:
2、线程依然切换:切换较之进程属于轻量级
3、同一个进程的线程可以共享进程的诸多资源,比如打开的文件;
4、对内存的需求较之进程略有下降;
5、快速切换时会带来线程抖动