《UNIXLinux程序设计教程》一3.6 文件控制函数fcntl()

3.6 文件控制函数fcntl()

函数fcntl()提供了进一步管理低级文件描述字的各种手段,用它可以对已打开的描述字执行各种控制操作,例如,重复一个文件描述字,查询或设置文件描述字标签,查询或设置文件描述字标签或文件状态标签,操纵文件锁(10.1节)等。

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

int fcntl (int filedes, int cmd, ...);

fcntl()对打开的文件描述字filedes执行各种控制操作,具体是哪一种操作由它的第二个参数cmd给出,表3-2列出了该参数的所有允许值。根据参数cmd的值,有一些操作还要求提供第三个参数。除了文件锁操作之外,其他操作均要求第三个参数是int类型。对于文件锁操作,第三个参数是一个指向结构struct flock的指针(10.1节)。fcntl()的正常返回值也取决于cmd参数。
《UNIXLinux程序设计教程》一3.6 文件控制函数fcntl()

fcntl()可执行表3-2所列10种不同的操作,前5种分别完成三类功能:重复文件描述字(F_DUPFD),设置/获取描述字标签(F_GETFD/F_SETFD),设置/获取文件状态标签(F_GETFL/F_SETFL)。本节中只描述这5种操作,后5种操作推迟至第10章讲述。

3.6.1 重复文件描述字

调用

fcntl(filedes, F_DUPFD, arg);

的作用是重复文件描述字filedes:它返回一个其值等于或大于arg的新文件描述字。该文件描述字与filedes共享同一个系统打开文件表,但有自己的文件描述字标签,并且它的FD_CLOEXEC标签被清除。
dup()是fcntl()这种操作的特例,它等价于

fcntl(filedes, F_DUPFD, 0);

而dup2()在filedes与arg不同且filedes是合法的文件描述字的情况下,等价于

close(arg);
fcntl(filedes, F_DUPFD, arg);

不过,dup2()是原子操作,即关闭arg以及复制filedes这两个动作的完成保证是完整的,在它们之间不会出现arg被关闭但filedes还未复制便被中断的情形。

3.6.2 文件描述字标签

回顾图3-2,我们看到与每个打开的文件描述字相连有一个文件描述字标签。这个标签只与特定的文件描述字有关,所以,如果从单次打开的文件创建了多个重复的文件描述字,则每个描述字有它自己的文件描述字标签。
当前,文件描述字标签中只有一个标志:FD_CLOEXEC,此标志指明当exec()(6.3节)执行时将关闭文件描述字。当用open()或dup()打开文件描述字时,此标志位的初始状态是清除的,这意味着在exec()之后描述字仍将在新程序中保留。
如下调用获取与参数filedes相连的文件描述字标签:

fcntl(filedes,F_GETFD);

它的返回值是一个非负整数,该整数解释为标签中各个标志位的按位或,其中包括FD_CLOEXEC(在当前只有一个标签在使用的情况下,则只有一位)。
如下调用:

fcntl(filedes,F_SETFD,flags);

设置与文件filedes相连的文件描述字标签,它需要类型为int的第三个参数给出新标签值。
例3-4 当修改文件描述字标签时,首先应当用F_GETFD获得当前标志,然后修改其值。不要假定本书所列的标志是实现提供的全部标志。尽管当前只支持一个标志,但我们写的程序可能从现在起还需运行多年且今后可能存在更多标志。应当如程序3-4所示来设置或清除标志FD_CLOEXEC,这样便不会对其他标志造成影响。
《UNIXLinux程序设计教程》一3.6 文件控制函数fcntl()

3.6.3 文件状态标签

文件状态标签指明文件的打开属性,它们由open()的flags参数指明(见表3-1)。与描述字标签不同,文件状态标签由与同一次打开文件相连的所有重复文件描述字所共享(参见图3-3)。文件状态标签中的标志可分为三类:访问方式、打开时标志和I/O操作方式。

  1. 访问方式
    访问方式指明允许文件描述字用于读、写或两者兼之,包括O_RDONLY、O_WRONLY和O_RDWR。这些访问方式在文件被打开时选定,之后便不能再改变。

为了确定文件访问方式,必须从fcntl()取回的文件状态标签中抽取访问方式。因为读和写访问方式不一定是独立的标志位,因此抽取文件访问方式的可移植方法是用宏常数O_ACCMODE位串与文件状态标签值作按位与操作(C中的'&'运算),由此生成表示文件访问方式的值,即O_RDONLY、O_WRONLY或O_RDWR(参见程序3-5)。

  1. 打开时标志
    打开时标志指明打开文件时影响open()行为的一些选项。这些选项一旦文件打开就不保留,但有一个例外是O_NONBLOCK,因为O_NONBLOCK同时也是一个I/O操作方式,故此标志被保留(3.7节)。

O_CREAT:若设置,当该文件不存在时创建此文件。
O_EXCL:若O_CREAT和O_EXCL同时设置,则当指定的文件已经存在时,open()失败。这保证不会破坏已存在的文件。
O_NONBLOCK:防止open()为打开文件而阻塞很长时间。这通常仅对诸如串行端口的设备文件才有意义。O_NONBLOCK标志同时也作为I/O操作方式标志,这意味着在open()中指明O_NONBLOCK就同时设置了非阻塞I/O方式(3.7节)。为了非阻塞地打开一个文件且不影响正常的阻塞I/O,必须先设置O_NONBLOCK来调用open(),然后调用fcntl()关闭此位。
O_NOCTTY:若命名的文件是终端设备,不让它成为该进程的控制终端(6.9节)。
O_TRUNC:截断文件为零长度,这一选项只对普通文件(4.2.1节)有用,对诸如目录或FIFO之类的特殊文件(4.2.5节)无用。由open()来做截断而不直接调用ftruncate()函数并没有什么太好的理由,只是因为O_TRUNC标志在ftruncate()引入之前就已存在于UNIX中,保留它只是为了向下兼容。

  1. I/O操作方式
    I/O操作方式影响使用文件描述字进行输入输出操作的工作方式。这些标志由open()设置,之后可以用fcntl()获取和改变。

O_APPEND:文件的附加方式位。若此位设置,所有write()操作写数据至文件尾而不管文件位置在何处。这是附加数据至文件尾唯一可靠的方法。用附加方式可以保证无论是否有其他进程正在写同一个文件,write()操作总是将数据写在当前文件尾。相反,在未设置此位的情况下,如果通过简单地移动文件位置到文件尾,然后再写数据,则在设置文件位置之后开始写之前,可能有其他进程扩展此文件(对应于两个不同的进程打开同一个文件的情形,它们共享同一个vnode,但各自有自己的系统打开文件表,因而有自己的文件位置,参见图3-2),从而导致所写的数据出现在实际文件尾之前的某个地方。
O_NONBLOCK:此标志同时也作为I/O操作方式标志。如果这一位设置,对文件的read()请求,当无立即可用的输入时能以EAGAIN错误状态立即返回,而不是阻塞。类似地,write()请求也能在输出不能写出时以EAGAIN错误状态立即返回。
O_ASYNC:此标志用于信号驱动的I/O(10.2节)。如果这一位设置,当文件描述字中有输入数据时会生成SIGIO信号。
O_SYNC:如果这一位设置,文件按同步I/O方式打开,并将导致任何写该文件的操作都阻塞调用进程直至数据(包括内核I/O缓冲区中的数据)以及与此次写有关的文件属性(4.1.1节)已全部写至物理存储介质(3.9节)。
O_DSYNC:如果这一位设置,文件按同步I/O方式打开,并将导致任何写该文件的操作都阻塞调用进程直至数据(包括内核I/O缓冲区中的数据)已全部已写至物理存储介质(3.9节)。但如果所写的数据不影响读刚写入的数据,则不等待文件属性更新。
O_RSYNC:如果这一位设置,文件按同步I/O方式打开,并将导致任何读该文件的操作都将等待所有写入同一区域的写操作按O_DSYNC和O_SYNC完成后再进行。如果同时设置了O_SYNC 和 O_RSYNC标志,则读操作将阻塞直到文件的访问时间属性已写至物理存储介质。如果同时设置了O_DSYNC 和 O_RSYNC标志,则读操作将阻塞直到所有与保持文件完整性有关的数据都已写至物理存储介质。
简单地说,O_SYNC、O_DSYNC和O_RSYNC这几个标志的主要作用是使数据直接写到磁盘或直接从磁盘读入。

  1. 获取和改变文件状态标签
    文件状态标签可以用fcntl()函数获取或改变。如下调用:
fcntl (filedes, F_GETFL, new_flags);

获取描述字 filedes的文件状态标签。它的正常返回值是一个非负整数,此数解释为各个独立标志的按位或。因为文件访问方式不是用独立的位表示的,所以,为比较它们需要用O_ACCMODE屏蔽返回值中的其他位。
例3-5 程序3-5是读取文件状态标签的例子,它打印出指定的文件描述字中已设置了的文件状态标签。为了读取文件访问方式标志,我们先用fcntl()返回的标签值val和O_ACCMODE进行“与”操作,然后再比较其结果。而对于其他标志,则直接从val中抽取。

程序3-5 打印文件状态标签函数
#include "cho3.h"
int display_file_status_flages(int fd)
{
    int accmode, val;
    if ((val = fcntl(fd, F_GETFL, 0) ) < 0)
       err_exit("fcntl error");
    /* 截取文件访问方式标志 */
    accmode = val & O_ACCMODE;
    /* 打印出所有设置了的标志 */
    if (accmode == O_RDONLY) 
        printf("read only");
    else if (accmode == O_WRONLY) 
        printf( "write only");
    else if (accmode == O_RDWR) 
        printf("read write");
    else {
        printf("unknown access mode\n");
        exit(1);
    }
    if (val & O_APPEND) 
        printf(" , append");
    if (val & O_NONBLOCK) 
        printf(" , nonblocking");
#if !defined(POSIX_SOURCE) && defined(O_SYNC)
     if (val&O_SYNC) 
        printf(", synchronous writes" );
#endif
     putchar('\n' );
     exit(0);
}

为了设置描述字filedes的文件状态标签,用命令F_SETFL调用fcntl(),该命令要求int类型的第三个参数以指明新标签:

fcntl (filedes, F_SETFL, new_flags);

它的正常返回值是一个非–1的不确定值。–1指出出错。
文件状态标签中,文件的访问方式是在打开文件时设定的,一旦设定便不能改变;打开时标志只在打开时使用,之后便不再保留。因此,应用唯一可以设置的是文件的I/O操作方式,即O_APPEND、O_NONBLOCK。
同设置文件描述字标签类似,如果想修改文件状态标签,同样应当先用F_GETFL调用fcntl()获得当前标签,然后再修改其值。
例3-6 程序3-6给出的函数保证在设置或清除标志O_NONBLOCK 时不会改变其他标签。
程序3-6 设置文件状态标签为非阻塞方式

#include "ch03.h"
/* 如果value非0,设置desc的O_NONBLOCK标志,否则清除该标志。成功返回0,失败返回-1 */
int set_nonblock_flag (int desc, int value)
{
   int oldflags = fcntl(desc, F_GETFL, 0);
   if(oldflags == -1)    /* 若获取标签失败,返回错误指示 */
       return -1;
    /* 形成并设置新标签 */
   if(value != 0)
       oldflags |= O_NONBLOCK;
   else
       oldflags &= ~O_NONBLOCK;
   return fcntl(desc, F_SETFL, oldflags); 
}
上一篇:【C语言】使用回调函数通过冒泡排序模拟实现qsort函数


下一篇:金蝶ERP实现产品入库冲减生产现场虚仓毛坯数