《UNIXLinux程序设计教程》一3.1 文件描述字的打开、创建和关闭

3.1 文件描述字的打开、创建和关闭

函数open()或create()用于打开或创建一个文件描述字。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open (const char * filename, int flags [, mode_t mode]);
int create (const char * filename, mode_t mode);

open()用于打开或创建一个文件,参数filename指明文件;flags给出文件的打开方式,它是一个位串,其值由表3-1列出的各种常数做位或运算(C中的“|”操作)而形成;mode是可选参数,给出文件的访问方式,其值由表4-6列出的各种常数做位或运算而形成,仅当创建文件时才用它指明新文件的访问方式位,其他情况下提供该参数不起作用。
open()调用成功返回为文件filename新创建的一个描述字,同时文件位置定位于文件的开始处;调用失败则不会创建或修改文件。
open()返回的描述字一定是当前最小未使用的描述字。这个特征特别有用,例如,如果一个程序关闭了它的标准输出,然后再调用open()创建或打开另一个文件,则标准输出使用的文件描述字1将成为这个新打开文件的描述字,从而使得标准输出可以重新定向至不同的文件或设备。在3.4节可看到利用这一特征的例子。

open()返回的文件描述字是唯一的,它不会由正在运行的其他进程所共享。如果两个进程同时打开同一个文件,系统会保证各自有自己的文件描述字。如果这两个进程都写这个文件,它们将按照自己的文件位置来写。写入的数据不会交错地记录在文件内,而是一个被另一个所覆盖。为了避免写入的数据被覆盖,可以利用文件锁(10.1节)来协调。
函数create()用于创建一个新文件。调用

fd = create(filename, mode);

等价于

fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,mode);

函数close()用于关闭已打开的文件描述字。

#include <unistd.h>
int close (int filedes);

关闭文件描述字意味下述一系列动作:
释放描述字filedes,此描述字可被后继调用的open()再次使用。
释放进程在此文件上占有的任何文件锁(10.1节)。
当与管道(11.1节)或FIFO(11.2节)相连的所有文件描述字均被关闭时,废弃任何还在管道或FIFO中的数据。
当进程终止时,UNIX内核会自动地关闭该进程打开的所有文件,因此许多应用利用这一特征而不明显地调用close()关闭文件。
类似于标准流,每一个进程也有预先打开的三个文件描述字:0、1和2,它们分别对应于标准输入、标准输出和标准错误输出。符号常数STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO分别表示这三个文件描述字。
内核用于管理打开和关闭文件的数据结构
UNIX内核为管理文件的打开和关闭使用了如图3-1所示的三种数据结构。
1)每一个活跃进程在系统的进程表中有一个登记项,称为进程表项。进程表项中有一张进程打开文件表,此表可以看成是一个数组,它的每一个元素对应一个文件描述字(FD)。每当进程打开一个文件时,系统便在该数组中为它指定一个元素,open()返回的文件描述字就是该数组元素的索引。此数组元素记录着文件描述字的如下信息:
文件描述字标签。目前UNIX标准仅支持一个标签值FD_CLOEXEC(3.6.2节)。
指向系统打开文件表项的指针。
2)每当open()成功打开一个文件时,便在系统打开文件表中建立一个打开文件表项。每一个表项代表着一个已打开的文件,它含有已打开文件的如下信息:
文件状态标签。这些标签是open()的第二个参数指定的,如O_RDONLY、O_APPEND、O_CREAT等。
当前文件位置。
指向该文件的v-node的指针。
3)每一个打开的文件都有一个vnode结构。vnode 是内核中表示文件的数据结构,不论文件属于什么类型的文件系统,内核都通过与文件相连的vnode来访问文件。同系统打开文件表不同,vnode不是以文件打开为基础的,而是以文件为基础的,即一个文件只有一个vnode结构。因此,同一个文件可以被若干个进程打开,但每一个进程在系统打开文件表中用自己的打开文件表项与这个vnode相连。vnode中包含着与文件有关的所有维护信息,其中包括vnode信息和文件的inode信息。inode中记录着文件的属主、文件大小、文件驻存的设备等信息,4.2节讨论文件类型时将具体涉及inode的内容。
图3-1展示了一个进程打开两个不同文件时这三个数据结构之间的关系。其中一个文件打开在标准输入(描述字0),另一个文件打开在标准输出(描述字1)。

如果两个独立的进程打开同一个文件,这三个数据结构之间的关系如图3-2所示。这里我们假定第一个进程在描述字3上打开一个文件,而后第二个进程在描述字4上打开同一个文件。每个进程有自己的系统打开文件表项,但它们的描述字都指向同一个vnode。每个进程有各自的系统打开文件表项的原因之一是各个进程需要有自己的当前文件位置。

了解了这三个数据结构后,我们特别需要指出以下几点:
1)文件描述字总是从最小可用描述字开始分配。例如,如果一个进程已打开5个文件,随后关闭了与描述字0相连的文件,则下一个open()成功返回的描述字是0而不是5。
2)子进程总是继承父进程的所有描述字。例如,如果一个进程打开了5个描述字,则它用fork()派生的子进程也继承它的这5个描述字,且这5个描述字指向与父进程相同的文件。这就是为什么每一个进程总是预先有三个打开的描述字0、1和2,因为所有用户进程都是shell进程的子进程,它们继承了由shell打开的这三个描述字。标准输入输出重定向便利用了这个特点和特点1。
3)每一次write()完成之后,系统打开文件表项中当前文件位置将增加所写的字节数。如果这导致当前文件位置超出了当前文件大小,则用当前文件位置更新inode中的当前文件大小,此时称文件被扩展。
4)如果文件是用O_APPEND标志打开的,该标志将被设置在系统文件打开表项的文件状态标签中。每当对此文件执行write()时,系统打开文件表项中当前文件位置将首先移到由inode给出的当前文件大小所指出的位置,从而强制write()只能在当前文件尾添加数据。
5)如果一个文件位置被lseek()(3.3节)定位于当前文件尾,所发生的只是用inode中的当前文件大小设置系统打开文件表中的当前文件位置。
6)一个进程或不同进程的多个文件描述字可以指向同一个系统打开文件表项,这对应于单次打开文件但复制了多个描述字的情形,如通过dup()(3.4节)或通过fork()(6.2节)继承。多个系统打开文件表项也可以指向同一个vnode结构,这对应于一个进程多次打开或者多个进程同时打开同一文件的情形。
本章后继内容和第10章关于高级I/O的讨论中,我们将进一步看到这些特点的应用。

时间: 2024-10-31 05:48:16

《UNIXLinux程序设计教程》一3.1 文件描述字的打开、创建和关闭的相关文章

《UNIXLinux程序设计教程》一导读

前言 十年前,我们出版了<UNIX程序设计教程>(清华大学出版社).十年来,影响UNIX编程接口的规范和标准发生了较大变化,当时写书参照的"Single UNIX Specification 2"现在已发展到了"Single UNIX Specification 4",而若干分离独立的规范和标准,包括Single UNIX Specification,现在都已经统一在POSIX.1-2008标准之下.同时,随着Linux系统的成熟和发展,UNIX系统已不

《UNIXLinux程序设计教程》一3.10 思考与练习

3.10 思考与练习 打开文件时,如果希望总是创建一个新文件,应当使用什么标志?如果希望每次写出的数据都实际写到物理存储设备,应当使用什么标志? 程序3-1中,存放读写数据的缓冲区大小为1024字节.请在你的机器上指定不同大小的缓冲区来运行这个程序,仔细体会缓冲区大小对程序效率的影响. open()调用成功总是返回当前可用的编号 的描述字.对同一个文件用不同open()打开的文件描述字具有 的文件位置,由dup()重复的文件描述字具有 的文件位置. 编写一个程序打印出指定文件的文件状态标签. 用

《UNIXLinux程序设计教程》一2.10 思考与练习

2.10 思考与练习 打开文件的实质是什么? 从应用的角度看,UNIX系统中程序与文件建立连接有几种机制?流与文件描述字有什么区别? 什么是文件位置?它起什么作用? 系统为每一个进程自动打开的输入输出流有哪些?它们对应的名字是什么? 写"r"方式打开的文件会发生什么情况?读"w"方式打开的文件呢?建议你编写一个这样的程序试试. 按读写数据的粒度分,有几类流输入输出函数? 为什么说gets()是危险的函数? 程序2-3说明了fgets()和gets()的不同.运行该程

《UNIXLinux程序设计教程》一3.7 非阻塞I/O

3.7 非阻塞I/O 前面几节已介绍了完成各种I/O的系统调用,如read().write().open()等,这些系统调用在默认情形下均是阻塞的,也就是说,调用必须等待操作完成,即读写到数据,才能返回.但在有些应用中往往还有需要非阻塞I/O的情形.本节我们讨论使得这些调用成为非阻塞的方法. UNIX系统调用根据阻塞还是非阻塞分为两类:一类是所谓的"慢"系统调用,其他的则归为另一类.慢系统调用是以下有可能被永久阻塞的调用: 调用read()读管道.终端设备或网络设备文件时,如果数据不出

《UNIXLinux程序设计教程》一第3章-3.0 低级输入输出

第3章-3.0 低级输入输出 标准I/O函数提供了丰富.便捷的输入输出方法,但有时程序员并不需要标准I/O函数提供的数据转换和缓冲处理,而希望使用自己的方法.例如,当需要用很大的缓冲来读二进制文件,或需要对特定设备(如终端)进行控制操作,或需要传递文件描述字给子进程(子进程可以用继承的文件描述字创建自己的流,但不能直接继承流)时,便需要使用UNIX的输入输出系统调用.这些系统调用习惯上称为低级I/O函数. 低级I/O函数对文件描述字进行操作,其中有一些是实现标准I/O函数的初等函数:另外一些则执

《UNIXLinux程序设计教程》一2.1 UNIX 输入输出基本概念

2.1 UNIX 输入输出基本概念 在任何一种操作系统中,程序开始读写一个文件的内容之前,必须首先在程序与文件之间建立连接或通信通道,这一过程称为打开文件.打开一个文件的目的可能是要读其中的数据,也可能是要往其中写入数据,还可能是既要读又要写数据. UNIX系统有两种机制用于描述程序与文件的这种连接:一种称为文件描述字,另一种称为流.因此,系统中关于I/O的函数也分为两大类:一类针对文件描述字操作,另一类针对流操作. 当用流或描述字I/O函数打开一个文件时,它们分别返回一个流或文件描述字,然后便

《UNIXLinux程序设计教程》一3.5 fdopen()和fileno()函数

3.5 fdopen()和fileno()函数 文件描述字函数是流函数的初等函数,每一个流都与一个描述字相连.给定一个打开的文件描述字,可以用fdopen()函数为它创建一个流.反过来,已知一个流,也可以用fileno()函数得到它的文件描述字. #include <stdio.h> FILE fdopen (int filedes, const char opentype); int fileno (file * stream); fdopen()使描述字filedes与一个流相连.它的返回

《UNIXLinux程序设计教程》一3.2 read()和write()函数

3.2 read()和write()函数 对文件描述字进行基本输入输出操作的函数是read()和write(). #include <unistd.h> ssize_t read (int filedes, void * buffer, size_t nbytes); ssize_t write (int filedes, const void * buffer, size_t nbytes); read()从已打开的.与文件描述字filedes相连的文件中读至多nbytes个字节的数据放到b

《UNIXLinux程序设计教程》一3.8 readv()和writev()函数

3.8 readv()和writev()函数 read()和write()系统调用每次在文件和进程的地址空间之间传送一块连续的数据.但是,应用有时也需要将分散在内存多处地方的数据连续写到文件中,或者反之.在这种情况下,如果要从文件中读一片连续的数据至进程的不同区域,使用read()则要么一次将它们读至一个较大的缓冲区中,然后将它们分成若干部分复制到不同的区域,要么调用read()若干次分批将它们读至不同区域.同样,如果想将程序中不同区域的数据块连续地写至文件,也必须进行类似的处理.UNIX提供了