《UNIXLinux程序设计教程》一3.7 非阻塞I/O

简介: 本节书摘来自华章出版社《UNIXLinux程序设计教程》一 书中的第3章,第3.7节,作者:赵克佳 沈志宇,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

3.7 非阻塞I/O

前面几节已介绍了完成各种I/O的系统调用,如read()、write()、open()等,这些系统调用在默认情形下均是阻塞的,也就是说,调用必须等待操作完成,即读写到数据,才能返回。但在有些应用中往往还有需要非阻塞I/O的情形。本节我们讨论使得这些调用成为非阻塞的方法。
UNIX系统调用根据阻塞还是非阻塞分为两类:一类是所谓的“慢”系统调用,其他的则归为另一类。慢系统调用是以下有可能被永久阻塞的调用:
调用read()读管道、终端设备或网络设备文件时,如果数据不出现则可能永久阻塞调用者;调用write()写同样的文件,如果数据不能立即被接收也可能永久阻塞调用者。
打开一个文件将阻塞直至出现某种条件。例如,打开一个终端设备将等待相连的调制解调器回应;打开一个只写的FIFO,但没有其他进程打开它用于读。
读写一个具有强制锁(10.1节)的文件。
某些ioctl()操作。
某些进程间通信函数(参见第11章)。
默认情况下,关于I/O的系统调用总是阻塞的,调用必须等待操作完成,即读写到数据才能返回。但是慢系统调用并不包括所有阻塞I/O,这里包括的只是可能永久阻塞的系统调用。例如,磁盘I/O有关的系统调用就不是慢系统调用,尽管读写磁盘文件也可能临时阻塞调用者。
非阻塞I/O可以使得I/O操作如open()、read()或write()不会被永久阻塞,如果操作不能完成,这些调用将立即返回并给出错误码指明操作可能被阻塞。
对于给定的文件描述字,有两种方法指定非阻塞I/O:
1)在open()时指定O_NONBLOCK文件状态标志。
2)对已经打开的描述字,调用fcntl()函数设置O_NONBLOCK文件状态标志。
例3-7 在非阻塞I/O情况下传输数据,当读写不能立即完成时会返回–1并置errno为EAGAIN。这时,往往需要再次调用read()或write()。我们来看一个非阻塞I/O的程序例子,这个例子使用了程序3-6给出的函数set_nonblock_flag()来设置和清除O_NONBLOCK标志。
程序3-7 非阻塞I/O之例

#include "ch03.h"
#include "p3-6.c"
#define B_SIZE 100000
char buf[B_SIZE];
char fmt[] = "%d Hi :( -> :) ---aha!---";
int main(void)
{
   int nbytes=0, j=0, nwrite, ntimes = 1, success=0;
   char *ptr;
   if(set_nonblock_flag(STDOUT_FILENO, 1) < 0)     /* 设置无阻塞I/O */
      err_exit("set nonblock flag failed ");   
   while (nbytes+sizeof(fmt) < B_SIZE) {
      sprintf(&buf[nbytes],fmt,j++);
      nbytes += sizeof(fmt);
   }
   /* 将buf中的数据写至标准输出,直至全部写出 */
   for(ptr=buf; nbytes >0; ntimes++){
      errno = 0;
      nwrite = write(STDOUT_FILENO, ptr, nbytes);
      if(nwrite < 0){
         fprintf(stderr,"\n%d  nwrite=%d, error=%d ",ntimes,nwrite,errno);
         perror("");
      } else {
         fprintf(stderr,"\n%d  nwrite=%d, error=%d ",ntimes,nwrite,errno);
         ptr += nwrite;
         success++;
         nbytes -= nwrite;
      }
   }
   printf("success=%d\n",success);
   exit(0);
}

程序3-7以非阻塞方式将buf中的数据全部写到标准输出文件。因为是非阻塞方式,write()操作有可能由于阻塞而没有完成或者只写了一部分数据就返回。为了保证buf中的数据全部写至标准输出文件,我们将write()放置在一个循环内,并以写完nbytes字节作为循环终止条件。
执行这个程序时,若标准输出文件是磁盘文件,则只需一次write()便可写出全部数据:

%a.out>file
1 nwrite=99996, error=0
%ls 杔 file
-rw-r--r-- 1 zkj zkj 99996 Jun 22 21:15 l

但是,若标准输出文件是终端,则会多次调用write(),其中有一些写出部分数据,有一些则返回错误。这是因为当执行下一个write()时,终端还来不及显示完前面输出的数据而以EAGIN错误返回。

%a.out  2>output
... 显示大量写出的内容
--3702 Hi :( -> :) ---aha!---success=43
%cat output     
1  nwrite=4095, error=0 
2  nwrite=4095, error=0 
3  nwrite=2048, error=0 
4  nwrite=-1, error=11 Resource temporarily unavailable
5  nwrite=4095, error=0 
6  nwrite=-1, error=11 Resource temporarily unavailable
7  nwrite=2048, error=0 
...  省略的输出
20  nwrite=2048, error=0 
21  nwrite=-1, error=11 Resource temporarily unavailable
22  nwrite=2048, error=0 
 ...  省略的输出
78  nwrite=1695, error=0 
76  nwrite=-1, error=11 Resource temporarily unavailable
77  nwrite=-1, error=11 Resource temporarily unavailable
78  nwrite=1695, error=0 

这次运行过程中调用了78次write(),其中只有43次成功写出了数据,其余为错误返回,错误码为11,代表资源暂时不能满足导致操作受阻。由于程序使用了循环在所写出的字节数不满足时反复调用write(),从而使得buf中的内容最终被全部写出。
程序3-7的这类循环称为“轮询”,它存在两个问题:在多用户系统中它浪费了不少CPU时间,本来这些用于等待的CPU时间可以做其他事情或者让给其他进程。另外,它仍然没有避免永久阻塞的问题,虽然write()能立即返回,但它可能由于总是不能写出数据而导致出现死循环。为了避免死循环,我们必须设置一定的时间限制,以便时间到时能跳出循环。在10.3节介绍多路I/O时,我们可以看到解决这个问题更有效的方法。

相关文章
|
6月前
|
缓存 网络协议 Unix
Linux(UNIX)五种网络I/O模型与IO多路复用
Linux(UNIX)五种网络I/O模型与IO多路复用
170 0
|
6月前
|
Unix Shell Linux
在Unix/Linux操作系统中,Shell脚本广泛用于自动化任务
在Unix/Linux操作系统中,Shell脚本广泛用于自动化任务
69 2
|
30天前
|
Unix 物联网 大数据
操作系统的演化与比较:从Unix到Linux
本文将探讨操作系统的历史发展,重点关注Unix和Linux两个主要的操作系统分支。通过分析它们的起源、设计哲学、技术特点以及在现代计算中的影响,我们可以更好地理解操作系统在计算机科学中的核心地位及其未来发展趋势。
|
3月前
|
Unix Linux 程序员
Unix:Linux的“逗趣祖师爷”与它的不凡传承
在科技长河中,Unix犹如一颗恒星,既是历史见证者也是未来的启发者。1969年,因程序员肯·汤普森想在他的PDP-7上玩“Space Travel”游戏,意外创造了Unix,以简洁优雅的代码改变了操作系统的世界。进入90年代,林纳斯·托瓦兹受Unix启发,开发了开源免费的Linux,像是Unix调皮的孙子,不仅继承其精髓还增添了开放共享的精神。Unix与Linux之间的传承,就像是智者与追蝶孩童的故事,充满了岁月的智慧与新生的活力,提醒我们科技传奇往往源于不起眼的小事。下次使用Linux时,不妨会心一笑吧!
54 0
|
3月前
|
开发框架 Unix Linux
LangChain 构建问题之在Unix/Linux系统上设置OpenAI API密钥如何解决
LangChain 构建问题之在Unix/Linux系统上设置OpenAI API密钥如何解决
48 0
|
6月前
|
Unix Shell Linux
在Linux和类Unix系统中,Shell提供了多种命令用于用户和权限管理
在Linux和类Unix系统中,Shell提供了多种命令用于用户和权限管理
77 4
|
Unix 大数据 Linux
【Linux is not Unix】Linux前言
【Linux is not Unix】Linux前言
|
6月前
|
Oracle Ubuntu Unix
Unix与Linux区别
Unix: Unix是一个操作系统家族的名称,最早由贝尔实验室(Bell Labs)的肖像电机公司(AT&T)开发。最早的Unix版本是在1969年创建的。 Linux: Linux是由芬兰计算机科学家Linus Torvalds在1991年创建的。它是作为一个免费、开放源代码的Unix克隆而开始的。
101 1
|
6月前
|
Unix Shell Linux
在Unix/Linux Shell中,管道(`|`)和重定向
在Unix/Linux Shell中,管道(`|`)和重定向
91 1
|
6月前
|
Ubuntu Unix Linux
Unix/Linux操作系统的最强入门科普(经典)
Unix/Linux操作系统的最强入门科普(经典)
395 0