I/O条件就绪是指:数据已经到达,此时调用读函数一定会成功;或设备已经可以承接输出操作,此时调用写函数一定会成功。I/O条件就绪发生在调用具体的I/O函数之前,I/O就绪后如非对设备内核对象(Windows下为文件句柄,Linux下为文件描述符)进行特殊设置(如设置设备内核对象为非阻塞模式),则发出的I/O调用过程其实是用户缓冲区与内核缓冲区之间进行数据拷贝的过程,这个过程是阻塞的。
异步I/O是指:先通过调用I/O函数向内核发起I/O请求(这些函数调用后都会立即返回),内核将内核缓冲区中的数据拷贝至用户缓存,或将用户缓存中的数据拷贝至内核缓存并将内核缓存中的数据输出到设备,然后通知用户程序(如Windows中使预先设定的事件内核对象变为有信号状态,或通过完成端口的方式)。所以,用户向内核发起异步I/O请求时需要告诉内核的有:文件描述符、用户缓冲区指针、用户缓冲区大小、完成通知方式等。
应用进程需要有一种预先告知内核的能力,使得内核一旦发现应用进程指定的一个或多个设备内核对象I/O条件就绪时就通知用户进程,这个能力就是I/O复用,I/O复用函数可以使用select或poll(Linux)。
当应用层发起一个读请求时,比如从网卡读数据:
recv(s,buf,len,0);
这个len仅仅代表buf缓冲区的长度,并不表示此次必须要读到len个字节才返回。当发起recv调用时,recv内部将检查内核缓冲区中是否有数据,如果有,封闭内核缓冲区,然后将内核缓冲区中的数据拷贝至用户缓冲区(即buf),然后离开临界区,以便内核继续向内核缓冲区追加数据,或让其它读函数进入临界区读取数据,然后函数返回。如果内核缓冲区没有数据,则需要循环检测,这也是为什么当对方没有给自己发数据时,recv调用将会等待的原因。下面用伪代码大概描述读数据的实现过程:
void Read()
{
while (1)
{
进入临界区;
if (内核缓冲区中的数据量 > 0)
将内核缓冲区的数据拷贝至用户缓冲区;
更新相关变量的值(如存放内核缓冲区中的字节数的变量);
离开临界区;
break;
离开临界区;
}
}