缓冲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. 浅谈NSBundle

    图片.xib等资源文件无法直接封入静态库,要想在静态库中使用他们,就必须借助于bundle 那么什么是bundle呢? 简单来说,bundle就是一个内部结构按照标准规则组织的特殊目录,即direct ...

  2. Leetcode 119 Pascal's Triangle II 数论递推

    杨辉三角,这次要输出第rowIndex行 用滚动数组t进行递推 t[(i+1)%2][j] = t[i%2][j] + t[i%2][j - 1]; class Solution { public: ...

  3. shell切割日志脚本

    #!/bin/bash set -e source /etc/bashrc cd `dirname $` linenum=`wc -l userinfolist.txt | awk '{print $ ...

  4. Activiti 删除key值相同的所有不同版本的流程定义

    package com.mycom.processDefinition; import java.io.File; import java.io.IOException; import java.io ...

  5. eclipse安装activiti工作流插件

    方式一:在有网络的情况下,安装流程设计器步骤如下: 1.点击eclipse上方工具栏的Help,选择Install New Software 2.弹出如下窗口,然后填写插件名称和安装地址 Name: ...

  6. cocos2d-x 2.x版本接入bugly的总结

    最开始项目使用的是自己DIY的很简陋的上报系统,后来改成google breakpad来上报,发现其实都做的不太理想,游戏引擎因为版本历史问题存在一些崩溃问题.后来3.x接入了bugly,我这边抽了几 ...

  7. C# 动态修改dll的签名 以及修改引用该dll文件的签名

    在读取RedisSessionStateProvider配置 提到用mono ceil 来修改程序集以及它的签名,里面GetPublicKey 和GetPubliKeyToken 方法里面那个字符串的 ...

  8. 使用cxf开发webservice应用时抛出异常

    在使用cxf开发webservice应用时,报出了类似下面的错误 JAXB: [javax.xml.bind.UnmarshalException: unexpected element (uri:& ...

  9. 使用vim 查看二进制文件

    Vim 可以用来查看和编辑二进制文件 vim -b egenea-base.ko 加上-b参数,以二进制打开 然后输入命令 :%!xxd -g 1 切换到十六进制模式显示

  10. Unity3D常用代码总结

    1 GUI汇总 function OnGUI() { GUI.Label(Rect(1,1,100,20),"I'm a Label"); //1 GUI.Box(Rect(1,2 ...