zoukankan      html  css  js  c++  java
  • 文件I/O和标准I/O

    转载:https://blog.csdn.net/kyang_823/article/details/79496561 

    一、文件I/O和标准I/O
    文件I/O:文件I/O也称为不带缓冲的I/O(unbuffered I/O)。不带缓冲指的是每个read,write都调用内核中的一个系统调用。也就是一般所说的低级磁盘I/O,遵循POSIX相关标准,任何兼容POSIX标准的操作系统上都支持文件I/O。——是操作系统提供的基本IO服务,与OS绑定,特定于Linux或Unix平台。

    标准I/O:标准I/O是ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头文件中的定义,不依赖系统内核,所以移植性强。又称为高级磁盘I/O,遵循ANSI C相关标准。只要开发环境中有标准I/O库,标准I/O就可以使用。(Linux 中使用的是glibc,它是标准C库的超集。不仅包含ANSI C中定义的函数,还包括POSIX标准中定义的函数。因此,Linux 下既可以使用标准I/O,也可以使用文件I/O)。标准I/O库处理很多细节,例如缓冲分配,以优化长度执行I/O等。

    文件I/O:读写文件时,每次操作都会执行相关系统调用。这样处理的好处是直接读写实际文件,坏处是频繁的系统调用会增加系统开销。使用时一般用户需要自己维护一个用户缓冲区,不过在Linux系统中,都使用内核高速缓冲用于提高效率,因此文件I/O的读写系统调用实际上是在内核缓冲区和进程的用户缓冲区之间进行的数据复制。

    标准I/O:可以看成在文件I/O的基础上由标准I/O库封装并维护了缓冲机制(流缓冲,都在用户空间)。比如调用fopen函数,不仅打开一个文件,而且建立了一个流缓冲(读写模式下将建立两个缓冲区),还创建了一个包含文件和缓冲区相关信息的FILE结构,从而先读写流缓冲区,必要时再访问实际文件,从而减少了系统调用的次数,使用库函数在用户空间的流缓冲上和用户交互的效率高于直接从内核读写数据的效率,因此提高了I/O效率。

    带缓存的I/O操作是C标准库实现的,第一次调用带缓存的文件操作函数时标准库会自动分配内存(通常是调用malloc在用户空间上分配堆内存)作为流缓存并且读出一段固定大小的内容存储在缓存中。所以以后每次的读写操作并不是针对磁盘上的文件直接进行的,而是针对标准库的流缓存的。何时从磁盘中读取文件或者向磁盘中写入文件有标准库的机制控制。

    文件I/O:所有I/O函数都是围绕文件描述符进行的。当打开一个文件时,即返回一个文件描述符,后续的I/O操作也都使用该文件描述符进行操作。可以访问不同类型的文件如普通文件、设备文件和管道文件等。

    标准I/O:所有操作都是围绕流(stream)进行的。当用标准I/O库打开或创建一个文件时,即将一个流和一个文件相关联。通常只用来访问普通文件(???)。

    不带缓冲I/O:指进程不提供缓冲(但内核还是提供缓冲的),每调用一次write或read函数,直接进行系统调用。系统内核对磁盘的读写都会提供一个块缓冲(也被称为内核高速缓冲),用write函数对其写数据时,直接调用系统调用,将数据写入到块缓冲,当块缓冲满时才会数据写入磁盘。

    如果要写数据到文件上(就是写入磁盘),内核先将数据写入到内核中所设的缓冲区,假如这个缓冲储存器的长度是100个字节,调用系统调用 ssize_t write (int fd,const void * buf,size_t count); 写操作时,设每次写入长度count=10个字节,那么需要调用10次系统调用才能把内核缓冲区写满,之前数据还是在缓冲区,并没有写入到磁盘,缓冲区满时才进行实际上的IO操作,即数据写入到磁盘。因此“不带缓冲”不是没有缓冲而是没有直接写进磁盘。

    带缓冲I/O:指进程提供一个流缓冲,当用fwrite函数往磁盘写数据时,先把数据写入流缓冲区中,当达到一定条件(比如流缓冲区满,或主动刷新流缓冲)时才会把数据一次送往内核提供的块缓冲,再经块缓冲写入磁盘。(即双重缓冲)

    上面的例子,内核缓冲区长度100字节,调用不带缓冲的IO函数write()需要调用10次系统调用,这样系统效率低。而使用标准I/O函数时,用户层建立另一个缓冲区(流缓冲),假设流缓冲的长度是50字节,用标准C库函数fwrite()将数据写入这个流缓冲区,流缓冲区满50字节后再一次调用系统调用write()将数据写入内核缓冲内,如果内核缓冲也被填满,那么内核缓冲区内数据就被写入到文件(实质是磁盘)。由此可以看出: ① 标准IO操作最终还是要调用无缓冲IO系统调用(带缓冲I/O本身就是在不带缓冲I/O基础上提供缓冲实现的),它们并不直接读写磁盘 ; ② 增加用户/流缓冲区可以减少系统调用的次数。

    正常情况下,和磁盘交互的读写文件的大致流程:

    当应用程序尝试读取某块数据的时候, ① 如果这块数据已经存放在页缓存(也就是上面提到的内核高速缓存)中,那么这块数据就可以立即返回给应用程序,而不需要经过实际的物理读盘操作。 ② 如果数据在应用程序读取之前并未被存放在页缓存中,那么就需要先将数据从磁盘读到页缓存中去。

    对于写操作来说,应用程序也会将数据先写到页缓存中去(如果是调用标准库I/O进行写操作,那么首先是写到标准库的流缓冲区,在一定条件之后,再写到页缓冲内;如果是系统调用,那么直接写到页缓冲内),数据是否被立即写到磁盘上取决于应用程序所采用的写操作机制:

    如果用户采用同步写机制,那么数据会立即从页缓存写到磁盘上,应用程序会一直等到数据被写完为止;
    如果用户采用延迟写机制,那么应用程序就完全不需要等到数据全部被写到磁盘,数据只要写到页缓存中就可以了。在延迟写机制的情况下,操作系统会定期地将放在页缓存中的数据刷到磁盘上。
    如果用户采用异步写机制。在数据完全写到磁盘上的时候会通知应用程序。与异步写机制不同,延迟写机制在数据完全写到磁盘上的时候不通知应用程序,因此延迟写机制本身就存在数据丢失的风险,而异步写机制则不会有这方面的担心。
    无缓存IO操作的数据流向:数据——内核缓存区——磁盘
    标准IO操作的数据流向:数据——流缓存区——内核缓存区——磁盘

    标准I/O中,一般由系统选择缓存的长度,并自动分配。标准I/O库在关闭流的时候自动释放缓存。

    在标准I / O库中,一个效率不高的不足之处是需要复制的数据量。 当使用每次一行函数fgets和fputs时,通常需要复制两次数据:一次是在内核高速缓存和标准I/O缓存之间(当调用read和write时),第二次是在标准I/O缓存(通常系统分配和管理)和用户程序中的缓存(fgets的参数就需要一个用户行缓存指针)之间。

    程序在读写文件时既可以调用C标准I/O库函数,也可以直接调用底层POSIX标准的的Unbuffered I/O函数,那么用哪一组函数好呢?

    用Unbuffered I/O函数每次读写都要进内核,调一个系统调用比调一个用户空间的函数要慢很多,所以使用时在用户空间开辟I/O缓冲区还是必要的,此时用C标准I/O库函数就比较方便,并且省去了自己管理I/O缓冲区的麻烦。
    用C标准I/O库函数要时刻注意I/O缓冲区的存在会使得和实际文件有可能不一致,在必要时需调用fflush() 。
    我们知道Unix的传统是Everything is a file,I/O函数不仅用于读写常规文件,也用于读写设备,比如终端或网络设备。在读写设备时通常是不希望有缓冲的,例如向代表网络设备的文件写数据就是希望数据通过网络设备发送出去,而不希望只写到缓冲区里就算完事儿了,当网络设备接收到数据时应用程序也希望第一时间被通知到,所以设备的编程通常直接调用Unbuffered I/O函数。
    fflush将流所有未写的数据送入(刷新)到内核(内核缓冲区),fsync将所有内核缓冲区的数据写到文件(磁盘)。至于究竟写到了文件中还是内核缓冲区中对于进程来说是没有差别的,如果进程A和进程B打开同一文件,进程A写到内核I/O缓冲区中的数据从进程B也能读到,因为内核空间是进程共享的。而C标准库的I/O缓冲区则不具有这一特性,因为进程的用户空间是完全独立的。

  • 相关阅读:
    Android中Tomcat的简单配置和使用
    Android Toast 总结(五种用法)
    软件工程—软件开发生命周期
    Android四大组件之BroadcastReceiver
    Android之 Fragment
    Android 四大组件之Acticity
    java 注解Annotation
    Android Intent的使用
    Android 调用webService(.net平台)
    关于TouchEvent中出现异常:MessageQueue-JNI问题
  • 原文地址:https://www.cnblogs.com/liuye007/p/10387567.html
Copyright © 2011-2022 走看看