目录
1 I/O基本概念
1.1 IO概念
1.2 同步和异步
1.3 阻塞和非阻塞
2 五种I/O模型
2.1 阻塞IO
2.2 非阻塞I/O
2.3 多路复用I/O
编辑 2.4 信号驱动式I/O
编辑
2.5 异步I/O模型编辑
3 五种I/O模型比较
4 练习
1 I/O基本概念
1.1 IO概念
- I/O即数据的读取(接收)或写入(发送)操作
- 通常用户进程中的一个完整I/O分为两个阶段
用户进程空间<-->内核空间
内核空间<-->设备空间(磁盘、网卡等)
- I/O分为内存I/O、网络I/O和磁盘I/O三种
1.2 同步和异步
- 对于一个线程的请求调用来讲,同步和异步的区别在于是否要等这个请求出最终结果
- 对于多个线程而言,同步或异步就是线程间的步调是否要一致、是否要协调
- 同步也经常用在一个线程内先后两个函数的调用上
- 异步就是一个请求返回时一定不知道结果,还得通过其他机制来获知结果,如:主动轮询或被动通知
1.3 阻塞和非阻塞
- 阻塞与非阻塞与等待消息通知时的状态(调用线程)有关
- 阻塞和同步是完全不同的概念。同步是对于消息的通知机制而言,阻塞是针对等待消息通知时的状态来说的
- 进程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态
线程在运行过程中,可能由于以下几种原因进入阻塞状态:
- 线程通过调用sleep方式进休眠状态
- 线程调用一个在I/O上被阻塞的操作,即该操作在输入/输出操作完成前不会返回到它的调用者
- 线程试图得到一个锁,而该锁正被其他线程持有,于是只能进入阻塞状态,等到获取了同步锁,才能恢复执行
- 线程在等待某个触发条件
可能阻塞套接字的Linux Sockets API调用分为以下四种
- 输入操作
- 输出操作
- 接受连接accept
- 外出连接connect
2 五种I/O模型
2.1 阻塞IO
内核也有数据缓存区,数据好了再拷贝到应用的缓存区中,包括read,accept、connect等
2.2 非阻塞I/O
示例
read
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#define FIFO_NAME "/tmp/myfifo"
int main(int argc, char *argv[]) {
int fd, ret;
char buf[BUFSIZ] = {};
// 创建有名管道
if (mkfifo(FIFO_NAME, 0666) == -1) {
perror("mkfifo");
exit(0);
}
fd = open(FIFO_NAME, O_RDONLY|O_NONBLOCK); //非阻塞方式NONBLOCK
if(fd < 0) {
perror("open");
exit(0);
}
while(1) {
// do {
ret = read(fd, buf, BUFSIZ);
// } while (ret < 0 && errno == EAGAIN); //如果读失败了,并且原因是EAGAIN才会反复轮询读
// if(ret < 0){ //如果满足EGAIN但是,但是RET<0,是其他异常情况
// perror("read");
// exit(0);
// }
if(buf[0] == '#')
break;
printf("Read from pipe: %s\n", buf);
}
// 关闭管道并删除有名管道文件
close(fd);
unlink(FIFO_NAME);
return 0;
}
该程序通过使用read函数从文件描述符fd中读取数据,存储在buf中。BUFSIZ是一个常量,表示可以读取的最大字节数。当读取成功时,read函数返回实际读取的字节数,如果返回值小于0,则表示读取出现错误。
程序使用循环来反复读取数据,直到遇到以#开头的字符串,才会跳出循环。在读取过程中,如果读取失败了,并且原因是EAGAIN,则会一直轮询读取,直到成功读取为止。
注释掉的部分是对异常情况的处理,如果出现其他异常情况,则会输出错误信息并退出程序。
write函数仅用来测试,无特别说明
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/myfifo"
int main(void) {
int fd;
char buf[BUFSIZ];
// 打开有名管道并进行读写操作
fd = open(FIFO_NAME, O_WRONLY);
if( fd < 0 ) {
perror("open");
exit(0);
}
while(1) {
fgets(buf, BUFSIZ, stdin);
if (write(fd, buf, BUFSIZ) < 0 ) {
perror("write");
exit(0);
}
if(buf[0] == '#')
break;
}
close(fd);
return 0;
}
2.3 多路复用I/O
与read区别,可以监听多个文件描述符,有的可能有数据,有的可能没有数据。
如果有数据准备好的描述符,返回可读条件,如哪个文件描述符好了。
如果有数据了,调用recvfrom拷贝数据报,再进行处理。如果有多个文件描述符就执行多个这样的过程。
2.4 信号驱动式I/O
注册完就可以干别的事情了,类似异步操作。
上面4种,在数据报准备好之后拷贝数据的时候都是阻塞的,都算作是同步IO,都需要recv读一下。
2.5 异步I/O模型
内核无数据到数据报拷贝完成,这两部都是非阻塞的,应用程序干干什么就干什么。
3 五种I/O模型比较
4 练习
画出5种I/O模型调用过程