高效IO——五种IO模型概念和非阻塞IO
Posted 两片空白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高效IO——五种IO模型概念和非阻塞IO相关的知识,希望对你有一定的参考价值。
目录
前言:
内存于外设进行数据交互叫做IO。
IO的过程中主要由两个动作,一个是等待,一个是拷贝。
比如:
读IO,就是在等待可以读数据的条件,条件成立将数据从内核空间拷贝到用户空间。
写IO,就是在等待可以写数据的条件,条件成立将数据从用户空间拷贝到内核空间。
高效IO的本质是:单位时间内,尽可能的减少等待的比重。
一.五种IO模型
- 阻塞IO:在内核数据准备好之前,系统调用会一直等待。
所有套接字,默认方式都是等待。阻塞IO是最常见的IO模型。
- 非阻塞IO:如果内核还未将数据准备好,系统调用仍会直接放回,并且返回WOULDBLOCK错误码。
非阻塞IO需要程序员编码时,以循环的方式反复去尝试读写文件描述符,这个过程叫轮询。这对CPU来说是一种较大的浪费,一般只有在特定的情况下才使用。
- 信号驱动IO:内核将数据准备好之后,使用SIGIO信号,通知进程进行IO操作。
SIGIO信号:文件描述符准备就绪, 可以开始进行输入/输出操作,默认捕捉动作是忽略。我们可以自定义捕捉动作为recvfrom或者sendto。当信号到来时,就可以将数据从内核拷贝到用户,或者从用户拷贝到内核。
注意:数据拷贝的过程还是需要进程来做。
- IO多路转接:通过系统调用,可以同时等待多个文件描述符的就绪状态。至少一个就绪,就可以进行读和写了。
通过调用select/pol/epoll可以实现同一时刻等待多个文件描述符的就绪状态。
select/pol/epoll也是负责等待,那为什么效率提高了呢?
因为同一时间等待多个文件,文件就绪的概率增加了,等待的时间就减少了。
当等待完毕后,调用read或者write进行IO就不需要等待了。
- 异步IO:由内核在数据拷贝完成时,通知进程,使用数据。
不需要进程等待和将数据空内核空间拷贝到用户空间,这些动作都是内核做的,当内核拷贝完成后,通知进程来使用数据即可。
这个和信号驱动IO不同,信号驱动IO,进程需要自己将数据从内核拷贝到用户。
总结:
前四种IO归类为同步IO,最后一个归类为异步IO。
任何IO过程中,都包含两个动作,一个是等待,另外一个是拷贝,在实际生活中,等待消耗的时间远远大于拷贝的时间。让IO高效,最核心的办法就是让等待的时间尽量少。
二.同步通信和异步通信对比
同步通信和异步通信关注的是消息通信机制。
- 同步通信:就是在发出一个调用时,在没有得到结果之前,该调用不会返回。一旦返回就得到返回值。调用者主动等待这个调用的结果。需要调用者自己参与。
- 异步通信:调用在发出后,调用直接返回,没有返回结果。而被调用者通过状态,通知来通知调用者,或者通过回调函数处理这个调用。不需要调用者自己参与。
拿上面的信号驱动IO举例,信号驱动IO的情况很像异步IO,但是,信号通信IO最后还需要调用者对数据进行拷贝,所以属于同步IO。
在多线程和多进程中,也有同步和互斥的概念。和这里是完全不相同的。
- 进程或线程的同步,是进程和线程之间的一种制约关系。
- 多线程之间的同步,是让多个线程之间,可以按照一定次序运行。使得条件不满足时等待,满足时,可以立马执行。调高效率。
三.阻塞和非阻塞
阻塞和非阻塞,是指条件不满足时,等待方式的不同。
- 阻塞:在调用结果返回前,当前进程会被挂起等待,进程被放入等待队列,状态设为非R。调用线程只有在得到结果后才返回。
- 非阻塞:在不能得到结果之前,当前线程不会被挂起等待,而是会直接返回。
四.其它高级IO
非阻塞IO,记录锁,系统V流机制,IO多路转接(IO多路复用),readv和writev函数以及存储映射IO(mmap),这些统称为高级IO。
下面介绍两种IO,非阻塞IO和IO多路转接
4.1 非阻塞IO
一个文件描述符,默认情况下都是阻塞IO。调用fcntl函数可以改变文件描述符为非阻塞IO。
#include<unistd.h>
#include<fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
参数 | 作用 |
fd | 打开的文件描述符 |
cmd | 具体操作 |
... | 可变参数 |
fcntl函数的功能:
- 复制一个现有的描述符,对应cmd = F_DUPFD
- 获得/设置文件描述符标记,对应cmd = F_GETFD / F_SETFD
- 获得/设置文件状态标记,对应cmd = F_GETFL / F_SETFL
- 获得/设置异步IO所有权,对应cmd = F_GETOWN / F_SETOWN
- 获得/设置记录锁,对应cmd = F_GETLK / F_SETLK或F_SETLKW
我们可以使用第三个功能,获得/设置文件状态标记,就可以将一个文件描述符设置为非阻塞。
使用如下:
void SetNoBlack(int fd){
//获得文件状态
int fl = fcntl(fd,F_GETFL);
if(fl < 0){
perror("fcntl error\\n");
return;
}
//设置文件状态
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
- 使用F_GETFL将当前的文件描述符属性取出来,是一个位图。
- 然后使用F_SETFL将文件描述符设置回去,设置回去的同时加上 O_NONBLOCK参数。
轮询方式读取标准输入:
轮询方式读取:read当读条件不满足是,实际是阻塞的。当设置为非阻塞后,不断循环调用read,就是在轮询检测read条件是否满足。
说明:read非阻塞状态下,当返回值为小于0时,说明是读的条件不满足,而不是read调用失败。当读条件不满足时,不仅read的返回值回小于0,还会将全局变量错误码errno设置为EAGAIN(一个宏,值为11)。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
void SetNoBlack(int fd){
//获得文件状态
int fl = fcntl(fd,F_GETFL);
if(fl < 0){
perror("fcntl error\\n");
return;
}
//设置文件状态
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
int main(){
//将标准输入设置为非阻塞
SetNoBlack(0);
char c = 0;
//轮询检测read
while(1){
sleep(1);
//printf("begin to read\\n");
ssize_t n = read(0, &c, 1);
if(n > 0){
printf("%c\\n",c);
}
//并不是read错误,而是read的条件不满足
else if(n < 0 && errno == EAGAIN){
printf("read cond is not met....\\n");
}
else{
perror("read error\\n");
}
printf("------------------\\n");
}
return 0;
}
为了看起来方便,我将多路转接IO写在了另外一篇博客。
以上是关于高效IO——五种IO模型概念和非阻塞IO的主要内容,如果未能解决你的问题,请参考以下文章