I/O基础
什么是IO
在UNIX世界一切皆文件,文件就是一串二进制流。不管是Socket,管道对计算机来说一切都是文件,一切都是流。在信息交换的过程中,计算机都是对这些流进行数据的收发操作,简称为I/O操作。
计算机有那么多流,怎么知道要操作哪个流
是通过操作系统内核创建文件描述符(File Descriptor,FD)来标识的,一个FD就是一个非负整数,对这个整数的操作就是对这个文件(流)的操作。我们创建一个Socket,通过系统调用会返回一个FD。
I/O交互流程
举例读取磁盘数据交互流程
- 等待网络数据到达网卡,然后将数据读取到内核缓冲区。
- 从内核缓冲区复制数据,然后拷贝到用户空间。
操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据。因为Linux使用的虚拟内存机制,必须通过系统调用请求Kernel来协助完成I/O操作,内核会为每个I/O设备维护一个缓冲区。
I/O有内存I/O、网络I/O和磁盘I/O三种,通常我们说的是后两者。
五种I/O通信模型
阻塞I/O模型
- 用户进程调用了recvfrom系统调用,内核开始准备数据,用户进程被阻塞
- 内核数据准备好,拷贝到用户空间,返回结果,用户进程解除阻塞状态
非阻塞I/O模型
- 用户进程发起read操作,如果内核中数据没有准备好,会立刻返回一个error.用户进程多次尝试read直到内核数据准备好。
- 内核数据准备好,拷贝到用户空间,返回结果,用户进程解除阻塞状态
多路复用I/O模型
- 多个进程的I/O注册到一个复用器上,当用户进程调用该Selector,Selector会监听注册进来的所有I/O,如果Selector监听的所有I/O在内核缓冲区都没有可读数据,select调用进程会被阻塞,而当任一内核缓冲区有可读数据时,select调用就会返回,而后select调用进程可以自己或通知另外的进程再次发起读取I/O,读取内核中准备好的数据,多个进程注册I/O后,只有一个select调用进程被阻塞。
信号驱动I/O模型
信号驱动I/O是指进程预先告知内核,向内核注册一个信号处理函数,然后用户进程返回不阻塞,当内核数据就绪时会发送一个信号给进程,用户进程便在信号处理函数中调用I/O读取数据。从上图可以看出,实际上I/O内核拷贝到用户进程的过程还是阻塞的,信号驱动I/O并没有实现真正的异步,因为通知到进程之后,依然由进程来完成I/O操作。
异步I/O模型
用户进程发起aio_read操作后,给内核传递与read相同的描述符、缓冲区指针、缓冲区大小三个参数及文件偏移,告诉内核当整个操作完成时,如何通知我们立刻就可以开始去做其他的事;而另一方面,从内核的角度,当它收到一个aio_read之后,首先它会立刻返回,所以不会对用户进程产生任何阻塞,内核会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,内核会给用户进程发送一个信号,告诉它aio_read操作完成。
异步I/O的工作机制是:告知内核启动某个操作,并让内核在整个操作完成后通知我们,这种模型与信号驱动I/O模型的区别在于,信号驱动I/O模型是由内核通知我们何时可以启动一个I/O操作,这个I/O操作由用户自定义的信号函数来实现,而异步I/O模型由内核告知我们I/O操作何时完成。
同步和异步
同步和异步其实是指CPU时间片的利用,主要看请求发起方对消息结果的获取是主动发起的还是被动通知的。
阻塞和非阻塞
当我们调用了一个函数后,在等待这个函数返回结果之前,当前的线程是处于挂起状态还是运行状态。
阻塞和非阻塞指的是客户端等待消息处理时本身的状态,是挂起还是继续干别的。同步和异步指的是对于消息结果是客户端主动获取的,还是由服务端间接推送的