c 文件系统

UNIX 输入输出基本概念

在任何一种操作系统中,程序开始读写一个文件的内容之前,必须首先在程序文件之间建立连接或通信通道,这一过程称为打开文件。打开一个文件的目的可能是要读其中的数据,也可能是要往其中写入数据,还可能是既要读又要写数据。

UNIX系统有两种机制用于描述程序与文件的这种连接:一种称为文件描述字,另一种称为。因此,系统中关于I/O的函数也分为两大类:一类针对文件描述字操作,另一类针对流操作。

当用流或描述字I/O函数打开一个文件时,它们分别返回一个流或文件描述字,然后便可以将这个流或文件描述字作为参数传递给相应读写函数来完成实际的读写操作。

当已完成对文件的读写之后,可以通过关闭文件而终止程序与文件的这种连接。一旦关闭了一个文件描述字或者一个流,就不能再对它进行输入输出。

1.文件描述字与流

UNIX系统中,文件描述字表示为int类型的对象,而表示为指向类型为FILE结构的指针。文件描述字函数多数是系统调用,它们提供底层基本的输入输出操作接口。当需要对特定设备进行控制操作时,往往必须使用文件描述字,流函数不能够进行这类操作。另外,如果程序需要按特殊方式进行输入输出(如非阻塞输入),也必须使用文件描述字。

流函数建立在文件描述字之上,通过文件描述字函数而实现,它给程序提供了更高一级的输入输出接口。流函数比对应的文件描述字函数更丰富,功能更强大,也更利于程序的移植。任何运行ANSI C的系统均支持流,但并不是所有系统都支持文件描述字,有的系统根本不支持文件描述字或仅仅实现了文件描述字函数集合的一个子集。因此,一般情况下,应当坚持使用流而不是文件描述字,除非是想做某种特殊操作,而此操作只能用文件描述字才能完成。

2.文件名与路径名

UNIX系统中几乎每一种对象都表示为文件,不仅是通常的数据集合,系统中的每一个设备也表示为文件。文件被安排在目录中,目录本身又含有子目录,由此形成了文件系统的层次结构。

目录本身也是一种文件,不过它的内容是一组连接实际文件的文件名及相关信息,这些连接称为链或目录登记项。我们前面虽然说“文件被安排在目录中”,但是实际上目录只包含指向文件的指针而不是文件本身。为了理解文件名的语法,首先需要理解UNIX文件系统的目录层次结构。

系统中,每一个用户均有一个主目录,其文件通常存储在这个目录以及该目录的子目录中。例如,用户kjzhao,他的主目录是/home/kjzhao,在其主目录中有系统帮助建立的几个标准目录,如.bash_profile等;也有由他自己创建的子目录,如用于日常工作的目录work,用于应用程序的目录program等。另外一些用户的主目录也可能位于/home目录中,而/home则是根目录“/”的子目录。在根目录中通常还包括用于系统程序的子目录/bin,用于系统配置文件的子目录/etc,用于系统库文件的子目录/lib,以及代表各种物理设备的子目录/dev等。图2-1是这种目录层次的一个示例图。

同其他操作系统一样,UNIX中每一个文件都有一个名字,此名字为一字符串,即文件名。文件名用于命名一个文件,它由1至NAME_MAX个字符组成,这些字符可以是字符集中除斜线字符(/)和空字符(NUL)之外的任意字符。系统宏NAME_MAX是POSIX定义的文件名的最大字符个数(不是字符串的长度,该计数不包括结束的空字符)。文件名也称为路径名分量。

3.文件位置

对于已打开的文件,它的属性之一是文件位置。文件位置给出文件中当前可读写字符的位置,在所有POSIX兼容的系统中,它是一个表示距文件开始多少字节数的整数。

当文件刚打开时,文件位置位于文件的开始处,之后每当读出或者写入一个字符,文件的位置便增加一字节。换言之,对文件的访问是顺序的。

但是,对于以“添加”(append)打开的文件,其写出的处理有点特殊。对这种文件的写出总是顺序地附加在该文件的末尾,而不管文件的位置如何。其文件位置只用于控制从文件读出数据。

普通文件允许读写文件的任意位置。这种允许读写任意位置的文件也称为随机文件。可以用函数fseek()或lseek()改变随机文件的位置。如果企图改变一个不支持随机访问的文件的位置,则会得到ESPIPE错误。磁盘文件一般均是随机文件,终端则不是随机文件。

UNIX环境中,多个进程可同时读一个文件。为了使得每个进程都能够按自己的步调读文件,每个进程必须有自己的文件位置指针,这样才不会受到其他进程的影响。事实上,进程每次打开一个文件都会创建一个独立的文件位置。因此,即使在同一个程序中打开一个文件两次,也会得到两个具有独立文件位置的流或描述字。但是,如果打开一个文件描述字,然后复制它得到另一个文件描述字,则这两个文件描述字会共享同一文件位置:改变一个文件描述字的文件位置将影响另一个描述字的文件位置。

流和FILE对象

表示流的数据类型是FILE类型。FILE是系统定义的数据结构,它含有标准I/O库管理流所需要的与文件有关的所有内部状态信息,例如,进行实际I/O的文件描述字、文件位置指针、I/O缓冲区大小和指针、缓冲中当前存放的字符个数、错误和文件结束状态指示器等。

FILE对象由标准I/O库函数内部分配和管理,用户无须自己创建FILE类型的对象,也不需要查看FILE对象的内容。当我们用fopen()打开或创建一个流时,它会返回一个指向FILE结构的指针,此时称在程序和该文件之间建立了一个流。为了引用一个流,我们将它的FILE指针作为参数传递给标准I/O函数,因此,程序中涉及的都只是指向FILE对象的指针,即“FILE *”。所以,有时也使用术语“文件指针”表示流。

标准流

UINX系统中每个进程都有三个预先定义并自动打开的流,它们是:stdin、stdout和stderr。这三个标准流在<stdio.h>中说明,分别代表标准输入、标准输出以及错误输出。

打开和关闭流

以下函数用于打开和关闭一个流。

#include <stdio.h>
FILE * fopen(const char* pathname, const char *opentype);
int fclose(FILE *stream);
FILE * freopen(const char* pathname, const char *opentype, FILE *stream);

fopen()打开由pathname指定的文件并创建一个与之相连的流。如果该文件不存在,则创建一个新文件。如果打开文件成功,它返回指向此流的指针,否则返回空指针NULL。

opentype参数是一字符串,它控制文件打开的方式,其值只能是下述字符串之一:
“r”:为读而打开一个已存在的文件,文件位置定位于文件开始。
“w”:为写而打开一个文件。如果文件存在,则将它的长度截为0,也即文件将被重新写过;如果文件不存在,则创建一个新文件。
“a”:为在文件尾添加内容而打开文件。若文件存在,原来的内容不变且输出添加在文件的末尾;否则,创建一个新文件。
“r+”:为更新(既读又写)而打开一个已存在的文件,文件原有内容不变,文件位置定位于文件开始。
“w+”:为更新(既读又写)而打开一个文件,若文件已存在,其长度被截至0;否则,创建一个新文件。
“a+”:为更新(既读又写)而打开一个文件,若文件已存在,原内容不变;否则,创建一个新文件。用于读的初始文件位置定位于文件开始,但输出总是添加在文件的末尾。
其中,字母r、w和a分别代表read、write和append。字符“+”指明为更新而打开一个文件,当以这种方式打开一个文件时,对它既可写也可读。

以添加方式("a"或"a+"方式)打开的文件,不管文件的当前位置如何,所写出的数据总是顺序地附加在文件的末尾,因此不可能覆盖文件原来的内容。其文件位置只用于控制从文件中读数据。

打开的流通过调用fclose()来关闭。fclose()关闭参数stream指定的流,并中断与对应文件的连接。在流被关闭之前,所有缓冲的输出将被写出。关闭一个流后就不能再对它进行任何操作。

fclose()调用成功返回0,否则返回EOF并置errno指明错误。

当调用fclose()关闭一输出流时,对错误情形进行检查很重要,因为此时可能检测到真实的错误。例如,当fclose()将缓冲区中剩余的数据写出时,它可能得到磁盘空间已满的错误,如果不检测的话,就有可能丢失文件的内容。

进程从main()退出或调用exit()正常终止时,所有打开的流都将自动被关闭。但为了确保输出数据的完整性,编写程序时仍然应当在进程结束前明显地调用fclose()。因为当进程以其他非正常方式终止时,例如调用abort()流产程序或由于致命信号,文件没有正常关闭,输出缓冲区中的数据将不会被写出,从而可能导致文件不完整。2.7节会更详细地讲述流缓冲的问题。

freopen()重新打开pathname指定的文件,它的作用类似于fclose()和fopen()的合并。如果stream指定的流是打开的,它首先关闭该流,忽略任何错误,然后按opentype所给方式如同fopen()一样打开参数pathname指定的文件,并使之与stream给定的同一个流相连。freopen()调用成功返回指向流的指针,否则返回NULL并置errno指明错误。

例2-1 程序2-1是个简单的示例程序,它打开一个文件写入若干数据之后再重新打开此文件读出所写的数据。

int main() {
	FILE *stream;
	char buf[80] = "";
	printf("open and creat tfile \n");
	if (stream = fopen("tfile", "w") == NULL) {
		perrer("open() failed");
		exit(1);
	}
	printf("write string to tfile \n");
	fputs("Hello world", stream);
	printf("read string from tfile \n");
	if (s
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值