16、文件操作系统入门基本概念

1、文件管理系统

  在大多数应用中,文件是一个核心成分,处理实时应用和一些特殊的应用外,应用程序的输入都是通过文件的形式来完成的,实际上,所有应用程序的输出都保存在文件中,这便于信息的长期存储,也便于用户将来通过应用程序访问信息。

  数据或者文件归根结底是存储于物理内存空间上的,操作系统可以通过文件系统方便的管理磁盘上的文件,Linux的文件系统模型如下所示:

Linux文件系统模型

  对于物理内存的访问都是同通过设备驱动程序来进行的,而对设备启动的访问则有两种途径:一种是通过设备驱动本身提供的接口;另一种是通过虚拟文件系统(Vitual File System,VFS)提供给上层应用程序的接口。第一种方式能够让用户进程绕过文件系统字节读写磁盘上的内容,但这个操作会带来极大的不稳定性,因此大部分操作系统包括linux都是使用虚拟文件系统来访问设备驱动的。只有在特殊情况下,才允许用户进程通过设备驱动接口直接访问物理磁盘。

  VFS是虚拟的,不存在的,他和proc文件系统一样,都是只存在于内存而不存在于物理硬件中的,即只有在操作系统运行了以后才能存在,VFS提供的这一种机制可将各种不同的文件系统整合在一起,并提供统一的API接口供上层的引用程序使用。VFS的使用体现了Linux文件系统的最大特点——支持多种不同的文件系统,如XFS、EXT4、EXT3、EXT2、NFTS、VFAT等。

2、文件IO和标准IO

一、先来了解下什么是文件I/O和标准I/O:

文件I/O:文件I/O称之为不带缓存的IO(unbuffered I/O)。不带缓存指的是每个read,write都调用内核中的一个系统调用。也就是一般所说的低级I/O——操作系统提供的基本IO服务,与os绑定,特定于linix或unix平台。

标准I/O:标准I/O是ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头文件中的定义,具有一定的可移植性。标准I/O库处理很多细节。例如缓存分配,以优化长度执行I/O等。标准的I/O提供了三种类型的缓存。

(1)全缓存:当填满标准I/O缓存后才进行实际的I/O操作,对于读操作,直到读入的内容字节数等于缓冲区大小或者文件已经到达结尾,才进行实际的IO操作,将外存文件内容读入缓冲去;对于写操作,直到缓冲区被填满,才进行实际的IO操作,将缓冲区内容写到外存文件中。磁盘文件通常是全缓冲。
(2)行缓存:当输入或输出中遇到新行符时,才调用系统IO函数
(3)不带缓存:stderr就是了。

二、二者的区别

      文件I/O 又称为低级磁盘I/O,遵循POSIX相关标准。任何兼容POSIX标准的操作系统上都支持文件I/O。

  标准I/O被称为高级磁盘I/O,遵循ANSI C相关标准。只要开发环境中有标准I/O库,标准I/O就可以使用。(Linux 中使用的是GLIBC,它是标准C库的超集。不仅包含ANSI C中定义的函数,还包括POSIX标准中定义的函数。因此,Linux 下既可以使用标准I/O,也可以使用文件I/O)。

      在实现对文件的操作上,两者具有显著的区别:

  (1)标准I/O默认采用了缓冲机制,比如调用fopen函数,不仅打开一个文件,而且建立了一个缓冲区(读写模式下将建立两个缓冲区),还创建了一个包含文件和缓冲区相关数据的数据结构。低级I/O一般没有采用缓冲,需要自己创建缓冲区,不过其实在linix或unix系统中,都是有使用称为内核缓冲的技术用于提高效率,读写调用是在内核缓冲区和进程缓冲区之间进行的数据复制。

  (2)从操作的设备上来区分,文件I/O主要针对文件操作,读写硬盘等,它操作的是文件描述符,标准I/O针对的是控制台,打印输出到屏幕等,它操作的是字符流。对于不同设备得特性不一样,必须有不同API访问才最高效。

 

三、两者下的函数

3、文件描述符

  对于内核而言,所有打开的文件都得通过文件描述符引用。文件描述符本质上是一个非负整数,当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符,该文件描述符用于对文件进行读写操作。   在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。在linux下建议使用符号常量STDIN_FILENO、STDOUT_FILENO、STDER_FILENO进行代替,提高程序可读性,这些常量通常定义在头文件<unistd.h>中。

标准文件描述符图如下:

文件打开与文件描述符之间的详细关系可以看博客http://blog.csdn.net/cywosp/article/details/38965239

4、流和FILE对象

  文件IO主要是针对文件描述符的,而标准IO的操作主要是围绕流进行的,当用标准IO打开或创建一个文件时,就使得一个流与对应的文件相结合。标准IO函数fopen返回一个指向FILE对象的指针。当打开一个流时,标准IO函数fopen返回一个指向FILE对象的指针。该对象通常是一个结构,它包含了标准IO库为管理该流所需要的所有信息,包括:用于实际IO的文件描述符、指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等等。
  对于进程,预定了三个流,并且这三个流可以自动的被进程使用,他们是标准输入、标准输出和标准出错。

5、缓冲机制

  基于流的操作最终都会以系统调用的形式进行IO操作,为了提高程序的运行效率,流对象通常会提供缓冲区,以减少系统调用的次数,基于流的IO提供了以下的三种缓存机制。

1、全缓冲

全缓冲就是当输入或输出时,当缓冲区被填满了之后,才会进行实际的I/O操作,磁盘文件通常是全缓冲的。下面是一个在linux中将”hello world!“写入log.txt文件的程序,演示了这一个过程。log.txt是空文件,长度为0。

 

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
 
int main(void)
{
    FILE *stream;
    if ((stream = fopen("./log.txt", "a")) == NULL) {
        printf("error: %d\n", errno);
        exit(1);
    }
     
    char buf[BUFSIZ];
    setvbuf(stream, buf, _IOFBF,  BUFSIZ);
    fputs("hello world!", stream);
 
    sleep(20);
 
    return 0;
}

打开文件可以看到

如果fputs了很多次后,长度还是小于BUFSIZ,但是需要将这些内容都实际写入文件,可以使用fflush函数。fflush函数根据指定的文件流将缓冲区的内容进行实际的操作,并清空缓冲区;如果参数为NULL,则会对所有打开的文件流操作。例如上例,只要在fputs(“hello world!”, stream);代码后面加上下面一行代码就OK了。

fflush(stream);

编译执行代码,可以发现即使程序还在睡眠,但是内容已经写入log.txt了。

2、行缓冲

当在输入或输出中遇到换行符时,才进行实际I/O操作。linux下标准输出默认是行缓冲,下面的例子使用标准输出演示这一个过程。

#include <stdio.h>
 
int main(void)
{
    fputs("hello", stdout);
    sleep(2);
    //这里一开始输出了换行符,所以前面的hello就被输出到屏幕上了。
    fputs("\nworld", stdout);
    sleep(2);
 
    return 0;
}

在C语言中,可以通过setvbuf来设定缓冲模式,其中_IOLBF表示行缓冲,就是IO line buffer的意思。

3、无缓冲

标准I/O库不进行任何字符缓冲,任何读写都是即时可见的。linux下标准错误输出默认是不缓冲,修改上面的例子:

#include <stdio.h>

 

int main(void)

{

    fputs("hello", stderr);

    sleep(2);

    fputs("world", stderr);

    sleep(2);

 

    return 0;

}

程序一执行的时候就会输出”hello“,过两秒输出”world“,再过两秒程序就结束了。在C语言中,可以通过setbuf来设定无缓冲模式,只要将第二个参数设置为NULL就可以了;也可以通过setvbuf来设定无缓冲模式,其中_IONBF表示行缓冲,就是IO not buffer的意思。

 

 

参考链接:

Linux内核笔记--深入理解文件描述符

Linux 文件描述符详解

 

posted @ 2017-12-19 12:24  noticeable  阅读(606)  评论(0编辑  收藏  举报