IO读写过程
在计算机系统中,IO读写需要用户态和内核态之间的切换,因为IO操作涉及到硬件资源(硬盘、键盘和终端等设备),这些设备是不能被用户态直接访问的。所以当程序需要进行IO读写或其他类似的硬件资源操作时,不能直接执行,需要通过系统调用的过程转入内核态运行。
- 用户空间:只能执行受限的命令(Ring3),而且不能直接调用系统资源,必须通过内核提供的接口来访问
- 内核空间:可以执行特权命令(Ring0),可以调用一切系统资源
IO读写过程
- 写数据:先把用户缓冲数据拷贝到内核缓冲区,然后写入设备
- 读数据:先从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区
读数据时需要先等待内核缓冲区数据就绪,然后将内核缓冲区数据拷贝到用户缓冲区
阻塞IO
阻塞IO:在IO操作期间(数据准备阶段、数据拷贝阶段、内核处理数据阶段),进程都会被阻塞,无法执行其他任务,直到IO操作完成。
阻塞IO的执行流程:
- 应用程序发起阻塞IO请求,应用程序通过系统调用向操作系统内核(kernel)发起阻塞请求
- 内核进行文件描述符的合法性检查,确保文件描述符是有效的并且已经打开的
- 内核等待数据准备完毕,此时CPU处于阻塞状态
- 内核进行数据拷贝
- 读取:内核将磁盘(或其他外设)读取的数据暂存到内核缓冲区中
- 写入:内核将待读取的数据从用户空间的缓冲区拷贝到内核空间的缓冲区
- 内核处理数据
- 读取:内核将数据从内核缓冲区中拷贝到用户空间的缓冲区中
- 写入:内核将内核空间的缓冲区数据写入到磁盘(获取其他外设)
- 内核处理完数据后唤醒进程,并将结果返回给应用程序,继续执行程序后面的代码
非阻塞IO
非阻塞IO:在数据准备阶段,用户程序会不断进行系统调用查询数据是否准备完毕,进程不会被阻塞。
非阻塞IO执行流程
- 应用程序发起非阻塞IO请求,应用程序将IO请求传递给操作系统内核,并立即返回数据就绪状态(未就绪)
- 内核等待数据准备完毕,在此期间进程不会处于阻塞状态,用户程序周期性的检查IO操作的状态,实现轮询
- 数据准备完毕后,当用户程序再次检查IO操作时,内核进行数据拷贝
- 读取:内核将磁盘(或其他外设)读取的数据暂存到内核缓冲区中
- 写入:内核将待读取的数据从用户空间的缓冲区拷贝到内核空间的缓冲区
- 内核处理数据
- 读取:内核将数据从内核缓冲区中拷贝到用户空间的缓冲区中
- 写入:内核将内核空间的缓冲区数据写入到磁盘(获取其他外设)
- 内核处理完数据后,将结果返回给应用程序,继续执行后面的代码
多路复用IO
多路复用IO:使单个线程能够同时监听和处理多个IO事件,可以提高系统的并发性能,减少资源占用和上下文切换的开销。
多路复用IO具体介绍可以查看文章:juejin.cn/post/729185…
信号驱动IO
信号驱动IO:应用程序与内核建立SIGIO的信号关联并设置回调,当内核有文件描述符就绪时,内核会发出SIGIO信号通知应用程序,在内核发出SIGIO信号之前应用程序可以执行其他业务,无需阻塞等待。
信号驱动IO执行流程
- 应用程序注册信号处理函数,使用signal函数或sigaction函数向操作系统注册一个信号处理函数
- 内核接受IO请求,内核等待数据就绪
- 数据就绪后,内核会发送一个信号给应用程序通知IO事件的发生
- 应用程序接收到信号后,就会调用注册的信号处理函数来处理IO事件
- 信号处理函数执行完毕后,控制权会返回到原来的上下文中,应用程序继续执行其他任务
异步IO
异步IO:允许应用程序在进行IO操作时可以继续执行其他任务,而无需等待IO操作完成。
异步IO执行流程
- 应用程序发起异步IO请求,比如调用aio_read函数,创建信号回调函数
- 内核等待数据准备就绪,用户进程不会阻塞,可以执行其他任务
- 当数据准备就绪,内核会将数据从内核空间拷贝到用户空间
- 当数据拷贝完毕后,内核会递交信号,通知应用程序告知IO操作的结果
- 用户程序处理结果
五种IO模型总结
IO操作的两个步骤
- 内核等待数据就绪
- 内核拷贝数据
针对这两个步骤,五种IO模型在这两个步骤中有不同的处理:
- 阻塞IO:两个步骤期间用户进程都阻塞,等待内核拷贝数据完毕后,唤醒用户线程
- 非阻塞IO:内核等待数据就绪期间,用户线程周期性的检查IO操作的状态,实现轮询;内核拷贝数据期间,用户线程阻塞等待内核拷贝数据完毕
- 多路复用IO:一个线程监听多个IO操作,只要有一个数据准备就绪,内核会告知线程进行对应的IO操作,但是如果所有IO操作数据都没有准备就绪,此时线程就被阻塞
- 信号驱动IO:内核等待数据就绪期间,用户线程不会被阻塞,可以执行其他任务,数据就绪后内核会发送一个信号给用户线程;内核拷贝数据期间,用户进程会进入阻塞,等大内核拷贝数据完毕后开始处理数据
- 异步IO:两个步骤期间用户进程都不会阻塞,当数据准备就绪,并且内核拷贝完数据后,内核会发送一个通知给用户线程,用户线程开始处理数据。