缓冲I/O和非缓冲I/O

文件读写主要牵涉到了如下五个操作:打开、关闭、读、写、定位。在Linux系统中,提供了两套API,

  • 一套是C标准API:fopen、fclose、fread、fwrite、fseek,
  • 另一套则是POSIX定义的系统API:open、close、read、write、seek。

其中POSIX定义的API是系统API,而C标准API是基于系统API的封装,并且提供了额外的缓冲的功能。因此也可以把它们叫做缓冲I/O函数和非缓冲I/O函数。

除了前面介绍的这几个缓冲IO函数外,C标准库里面还提供了一系列封装的IO函数:如puts、putchar、printf等。

为什么要有增加缓冲区这个功能呢?主要是因为IO操作时,操作系统要从用户态转换为内核态的,而这个转换过程相对来说比较慢,因此可以通过缓冲的形式减少转换到内核态的次数。

那么,缓冲IO函数又是如何工作的呢?

  • 当用fopen打开文件时,除了分配文件句柄外,还额外申请了一个缓冲区。
  • 读文件时,会首先读到缓冲区中,然后返回用户需要的部分,多余的部分仍然放在缓冲区,下次再读的时候可以直接从缓冲区中返回。
  • 写文件时,会先写到缓冲区中,等缓冲区满后再统一写到文件中。

那么,我们该如何选择哪一组I/O函数呢?

  • 非缓冲I/O函数每次读写都要进内核,调一个系统调用比调一个用户空间的函数要慢很多,所以在用户空间开辟I/O缓冲区还是必要的。
  • 用缓冲I/O库函数要时刻注意I/O缓冲区和实际文件有可能不一致,在必要时需调用fflush()。

I/O函数也用于读写设备,比如终端或网络设备。此时通常需要更快的响应,一般不使用缓冲I/O函数。

PS:严格来讲,就算是POSIX的I/O函数,仍然是有内核I/O缓冲的,所以write也不一定是直接写到文件的,也可能写到内核I/O缓冲区中,至于究竟写到了文件中还是内核缓冲区中对于进程来说是没有太大差别的,我们不用太关注这一点。

阻塞I/O和非阻塞I/O

文件读写通常有阻塞和非阻塞两种方式,其中阻塞方式是我们比较常见的一种方式,此时函数会阻塞至操作完成。例如,对于如下一个等待用户输入字符串,并在屏幕上输出的例子:

#include <unistd.h>
#include <stdlib.h> int main(void)
{
char buf[10];
int n = read(STDIN_FILENO, buf, 10);
write(STDOUT_FILENO, buf, n);
return 0;
}

执行该函数时,read函数会一直阻塞到在屏幕上输入数据并回车(此时STDIN有数据可用)为止。

阻塞IO有一个很大的问题是:无法实现并发。当同时进行多个IO操作的时候,前面的文件数据不可用的时候(往往是Socket之类的IPC操作),后面的IO操作无法执行。

非阻塞IO则可以很好的解决这个问题,要使用非阻塞IO操作,需要在open的时候制定O_NONBLOCK标志。这样,如果设备暂时没有数据可读就返回-1,调用者应该试着再读一次(again)。这种行为方式称为轮询(Poll),调用者只是查询一下,而不是阻塞在这里死等,这样可以同时监视多个设备:

#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h> int main(void)
{
char buf[10];
int fd, n; fd = open("/dev/tty", O_RDONLY|O_NONBLOCK); while (1)
{
n = read(fd, buf, 10);
if (n >= 0)
break; sleep(1);
} write(STDOUT_FILENO, buf, n);
close(fd);
return 0;
}

PS:为了示例函数简单,我这里没有考虑异常情况(如open失败)的处理,而这些是在实际项目中是必不可少的。

非阻塞I/O有一个缺点,如果所有设备都一直没有数据到达,调用者则需要反复查询,这样会一直占着cpu不放。因此,在使用非阻塞I/O时,通常不会在一个while循环中一直不停地查询(这称为Tight Loop),而是每延迟等待一会儿来查询一下,以免做太多无用功,在延迟等待的时候可以调度其它进程执行。

但是,这样又引入了一个新的问题,可能导致数据读取的不够及时,就拿我前面的例子来说,我在每次循环的时候Sleep了一秒。如果刚开始Sleep的时候数据可用,但此时却无法立即响应,需要到Sleep结束后钟才能输出结果。

要解圆满解决这个问题,则需要用到select函数,它可以阻塞地同时监视多个设备,还可以设定阻塞等待的超时时间,由于select多见于socket编程场景,这里不大好举例,后续如果会介绍socket编程的时候再详细介绍它,要了解它的工作原理可以看一下这篇文章select,多路同步I/O模型。

Linux高级编程--05.文件读写的更多相关文章

  1. linux高级编程基础系列:线程间通信

    linux高级编程基础系列:线程间通信 转载:原文地址http://blog.163.com/jimking_2010/blog/static/1716015352013102510748824/ 线 ...

  2. 【UNIX环境高级编程】文件I/O

    [UNIX环境高级编程]文件I/O大多数文件I/O只需要5个函数: open.read.write.lseek以及close 不带缓冲的I/O: 每个read和write都调用内核中的一个系统调用 1 ...

  3. Unix环境高级编程:文件 IO 原子性 与 状态 共享

    参考 UnixUnix环境高级编程 第三章 文件IO 偏移共享 单进程单文件描述符 在只有一个进程时,打开一个文件,对该文件描述符进行写入操作后,后续的写入操作会在原来偏移的基础上进行,这样就可以实现 ...

  4. linux系统编程之文件与io(一)

    经过了漫长的学习,C语言相关的的基础知识算是告一段落了,这也是尝试用写博客的形式来学习c语言,回过头来看,虽说可能写的内容有些比较简单,但是个人感觉是有史起来学习最踏实的一次,因为里面的每个实验都是自 ...

  5. linux系统编程之文件与io(五)

    上一节中已经学习了文件描述符的复制,复制方法有三种,其中最后一种fcntl还并未使用到,关于这个函数,不光只有复制文件描述符的功能,还有其它一些用法,本节就对其进行一一剖析: fcntl常用操作: 这 ...

  6. 【Linux 应用编程】文件IO操作 - 常用函数

    Linux 系统中的各种输入输出,设计为"一切皆文件".各种各样的IO统一用文件形式访问. 文件类型及基本操作 Linux 系统的大部分系统资源都以文件形式提供给用户读写.这些文件 ...

  7. Linux高级编程--07.进程间通信

    每个进程各自有不同的用户地址空间,进程之间要交换数据必须通过在内核中开辟缓冲区,从而实现数据共享. 管道 管道是一种最基本的IPC机制,由pipe函数创建: int pipe(int filedes[ ...

  8. 【UNIX环境高级编程】文件 IO 操作 一 ( open | close | creat | lseek | write | read )

    博客地址 : http://blog.csdn.net/shulianghan/article/details/46980271 一. 文件打开关闭操作相关函数介绍 1. open 函数 (1) op ...

  9. python-IO编程,文件读写

    一.文件读写 1.打开文件 函数:open(name[. mode[. buffering]]) 参数: name:必须:文件的文件名(全路径或执行文件的相对路径.)) mode:可选:对文件的读写模 ...

随机推荐

  1. paip.字符串操作uapi java php python总结..

    paip.字符串操作uapi java php python总结.. java and php 相互转换.. import strUtil>>>  requiry(strUtil.p ...

  2. Java的从浅至深绕坑而行的学习

    package day02; /** * 1:java初学习,避免面试时一些HR挖的坑. * @author biexiansheng * */ public class Test02 { publi ...

  3. sqlserver自定义函数的创建与调用

    sqlserver中有系统提供的函数,像avg.sum.getdate()等,用户还可以自定义函数. 用户自定义的函数包括:标量函数和表值函数,其中标量函数和系统函数的用法一样,表值函数根据主体的定义 ...

  4. iframe父子页面调用小结

     子页面调用父页面 $('#Id', window.parent.document); //调用父页面元素 window.parent.func1(); //调用父页面方法  (子页面同理,需将js方 ...

  5. Android 发布可穿戴设备 SDK 的开发者预览版

    今早上安卓官网查资料,发现网站上赫然显示着"Android Wear"几个大字.难道……?点进去看,果然,Android发布了可穿戴设备的SDK的开发者预览版. 其中这第五张图…… ...

  6. C# 热敏打印机 Socket 网络链接 打印 图片

    C# 热敏打印机 Socket 网络链接 打印 图片 (一) http://www.cnblogs.com/rinack/p/4838211.html C# 热敏打印机 Socket 网络链接 打印 ...

  7. SVN命令模式批量更新多个项目文件

    使用svn作为版本管理是,在一个仓库下边同时建立多个项目,每天上班都需要一个个更新,为了发挥程序员懒的精神,能让电脑做的,绝不手工操作.作为自动化处理,在windows环境,首先想到了bat Tort ...

  8. xml转换之

    1.XStream public static <T> T toBean(String xmlStr, Class<T> cls) { XStream xstream = ne ...

  9. Gradle中ProGuard的配置

    好久没有写博客了…元旦前赶紧写一篇吧… 这些日子琢磨了一下gradle.对比起maven确实在配置上灵活很多,对groovy的支持可以更容易的自定义任务. 由于最近的几个项目中都使用到了moco这个开 ...

  10. 获取发布版SHA1获取