IO模型
目前IO模型主要经历了以下五种:
1)阻塞IO
2)非阻塞IO
3)IO复用(select和poll)
4)信号驱动IO(sigio)
5)异步IO(aio_)
内核空间和用户空间:
由于操作系统都包括内核空间和用户空间(或者说内核态和用户态),内核空间主要存放的是内核代码和数据,是供系统进程使用的空间。而用户空间主要存放的是用户代码和数据,是供用户进程使用的空间。目前Linux系统简化了分段机制,使得虚拟地址与线性地址总是保持一致,因此,Linux系统的虚拟地址也是0~4G。Linux系统将这4G空间分为了两个部分:将最高的1G空间(从虚拟地址0xC0000000到0xFFFFFFFF)供内核使用,即为“内核空间”,而将较低的3G空间(从虚拟地址 0x00000000到0xBFFFFFFF)供用户进程使用,即为“用户空间”。同时由于每个用户进程都可以通过系统调用进入到内核空间,因此Linux的内核空间可以认为是被所有用户进程所共享的,因此对于一个具体用户进程来说,它可以访问的虚拟内存地址就是0~4G。另外Linux系统分为了四种特权级:0~3,主要是用来保护资源。0级特权最高,而3级则为最低,系统进程主要运行在0级,用户进程主要运行在3级。
一般来说,IO操作都分为两个阶段,就拿套接口的输入操作来说,它的两个阶段主要是:
1)等待网络数据到来,当分组到来时,将其拷贝到内核空间的临时缓冲区中
2)将内核空间临时缓冲区中的数据拷贝到用户空间缓冲区中
1、阻塞IO
默认情况下,所有套接口都是阻塞的。
假如recvfrom函数是一个系统调用:
说明:任何一个系统调用都会产生一个由用户态到内核态切换,再从内核态到用户态切换的过程,而进程上下文切换是通过系统中断程序来实现的,需要保存当前进程的上下文状态,这是一个极其费力的过程。
2、非阻塞IO
当我们把套接口设置成非阻塞时,就是由用户进程不停地询问内核某种操作是否准备就绪,这就是我们常说的“轮询”。这同样是一件比较浪费CPU的方式。
3、IO复用
我们常用到的IO复用,主要是select和poll。这里同样是会阻塞进程的,但是这里进程是阻塞在select或者poll这两个系统调用上,而不是阻塞在真正的IO操作上。
另外还有一点不同于阻塞IO的就是,尽管看起来与阻塞IO相比,这里阻塞了两次,但是第一次阻塞在select上时,select可以监控多个套接口上是否已有IO操作准备就绪的,而不是像阻塞IO那种,一次性只能监控一个套接口。
4、信号驱动IO
信号驱动IO就是说我们可以通过sigaction系统调用注册一个信号处理程序,然后主程序可以继续向下执行,当我们所监控的套接口有IO操作准备就绪时,由内核通知触发前面注册的信号处理程序执行,然后将我们所需要的数据从内核空间拷贝到用户空间。
5、异步IO
异步IO与信号驱动IO最主要的区别就是信号驱动IO是由内核通知我们何时可以进行IO操作了,而异步IO则是由内核告诉我们IO操作何时完成了。具体来说就是,信号驱动IO当内核通知触发信号处理程序时,信号处理程序还需要阻塞在从内核空间缓冲区拷贝数据到用户空间缓冲区这个阶段,而异步IO直接是在第二个阶段完成后内核直接通知可以进程后续操作了。
综上所述,我们发现 前四种IO模型的主要区别是在第一阶段,因为它们的第二阶段都是在阻塞等待数据由内核空间拷贝到用户空间;而异步IO很明显与前面四种有所不同,它在第一阶段和第二阶段都不会阻塞。具体参考如下:
最后,总结下同步IO与异步IO的区别:
1)同步IO操作会引起进程阻塞直到IO操作完成。
2)异步IO操作不引起进程阻塞。
因此,由上面定义可以看出,阻塞IO、非阻塞IO、IO复用、信号驱动IO都是属于同步IO,而异步IO模型才与异步IO定义所匹配。
-------------------------
No pains, no gains