Linux五种I/O模型与fcntl非阻塞控制

📅 发布时间:2026/8/18 20:18:50
Linux五种I/O模型与fcntl非阻塞控制
目录1 ~ 五种 I/O 模型1.1 I/O1.1 I/O 模型1.2 阻塞 vs 非阻塞1.3 同步I/O vs 异步I/O2 ~ 非阻塞I/O2.1 fcntl2.2 非阻塞代码1 ~ 五种 I/O 模型1.1 I/OI/Oinput / output输入/输出是指计算机核心处理系统CPU/内存与外部设备外设存储驱动器或网络接口之间的数据传输与通信过程。IO 的效率其实并不高当我们调用recv只有当接收缓冲区中有数据时recv才能够返回不然就会阻塞等待数据就绪。所以I/O 等 拷贝。为什么效率不高就是因为要等而拷贝的时间消耗相比于等待所占的比重就少很多。所以高效IO 就是围绕如何在单位时间减少等待的比重。1.1 I/O 模型1阻塞 I/O在系统调用完成读取或写入之前应用程序进程/线程会一直阻塞暂停执行。2非阻塞 I/O如果底层数据没有准备好系统调用就会立即返回需要线程定期轮询检查状态。3信号驱动 I/O当底层数据准备就绪后内核会主动向应用程序发送信号如SIGIO进行通知。4I/O 多路转接通过select、poll、epoll接口让单个进程/线程同时监控多个文件描述符当任意文件描述符就绪时就触发响应然后应用程序进行I/O。5异步 I/O内核自动完成等待数据就绪以及将数据拷贝到用户空间内存的全过程仅在全部操作完成后通知应用程序。下面我们通过一个钓鱼的例子来加深对5种I/O模型的理解阻塞I/O张三在钓鱼时什么也不干一直盯着鱼鳔看一旦有鱼上钩了就开始往上钓。非阻塞I/O李四在钓鱼时抛竿后就开始刷抖音然后时不时抬头看一个鱼竿的动静没鱼上钩就继续刷抖音有鱼上钩就往上钓。信号驱动I/O王五在钓鱼时在鱼竿上面系一个铃铛然后就也去刷抖音了但是王五只有在铃铛响之后才会开始往上钓鱼。I/O多路转接赵六钓鱼时直接拉来了一车鱼竿全部抛竿后然后过来过去查看鱼竿状态发现哪个有鱼上钩就开始往上钓。异步I/O田七只喜欢吃鱼于是对秘书说“你在这钓鱼我现在去开会钓到之后你打电话叫我我过来接你。”1.2 阻塞 vs 非阻塞1非阻塞I/O的效率是不是比阻塞I/O的效率高如果单纯就I/O 的效率来讲阻塞与非阻塞效率是相同的因为 I/O 等 拷贝两者只是等待的方式不同。但非阻塞I/O 在等待的时间里做了更多事所以非阻塞对于时间的利用率更高。2那么哪一种方式的效率更高呢谁在 I/O 过程中等待的时间最短那么谁的效率就最高。对于赵六而言他一次性监控了大量的鱼竿钓完一杆I/O很快就发现又有鱼上钩了数据就绪。这个过程中赵六没怎么等几乎一直在上钩。所以多路转接 I/O 的效率是最高的。1.3 同步I/O vs 异步I/O阻塞、非阻塞、信号驱动和多路转接 I/O 都属于同步 I/O对于同步 I/O 而言可以简单理解为应用进程或线程一定参与了等待或拷贝中的一个环节或者都参与了。异步I/O 一定没有参与等待和拷贝任意一个环节。2 ~ 非阻塞I/O2.1 fcntl#includeunistd.h#includefcntl.hintfcntl(intfd,intcmd,.../* arg */);fd即要控制的文件描述符。cmd代表你要进行什么操作。第三个参数根据cmd决定传指针或整数。返回值成功由cmd决定失败返回-1错误码被设置。cmd参数F_GETFL获取文件状态标志intflagsfcntl(fd,F_GETFL);if(flags-1){perror(fcntl);}F_SETFL设置文件状态标志// 设置文件描述符为非阻塞// 1. 获取原有标志intflagsfcntl(fd,F_GETFL);// 2. 添加 O_NONBLOCK 非阻塞不能直接赋值要保留原有flagfcntl(fd,F_SETFL,flags|O_NONBLOCK);F_DUPFD复制文件描述符intnewfdfcntl(oldfd,F_DUPFD,low);类似dup()。复制fd返回一个大于等于minfd的新文件描述符。F_SETOWN设置拥有者信号驱动 IO 必备// socket就绪时向这个进程PID发送SIGIO信号fcntl(sockfd,F_SETOWN,getpid());今天我们只用到获取和设置文件描述符状态的功能2.2 非阻塞代码当我们正常从标准输入获取数据默认std::cin是阻塞的即 0 号文件描述符阻塞。// 将指定文件描述符设置为非阻塞 void SetNoblock(int fd) { int fl fcntl(fd, F_GETFL); if(fl 0) { std::cout 出错 std::endl; return; } // 保持原状态不变并将将标准输入设置为非阻塞 fcntl(fd, F_SETFL, fl | O_NONBLOCK); }// 非阻塞IOvoidtest2(){SetNoblock(0);// 将标准输入设置为非阻塞charbuff[1024];while(true){intnread(0,buff,sizeof(buff));if(n0){buff[n-1]0;std::coutbuffstd::endl;}elseif(n0){if(errnoEINTR){std::cout信号中断std::endl;continue;}elseif(errnoEAGAIN|EWOULDBLOCK){std::cout底层数据没有准备好(未就绪)std::endl;sleep(2);continue;}else{std::cout出错std::endl;break;}}elsebreak;// 读到文件末尾}}文件描述符在非阻塞状态下如果再调用读或者写之类的接口就需要对返回值为 -1 这种情况进行更加细致的判断errno EAGAIN||errno EWOULDBLOCK表示底层数据没有准备好下次就绪后再来读。errno EINTR表示信号中断重试即可。其他表示真正出错了。此时如果底层没有数据就绪就会直接返回。