16 读书笔记:第3章 文件I/O (6)

3.14 fcntl函数

        fcntl函数可以改变已打开的文件的性质。

       #include <fcntl.h>
       int fcntl(int fildes, int cmd, ...);

        fcntl函数有5种功能:

                (1) 复制一个现有的描述符(cmd = F_DUPFD)。

                (2) 获得/设置文件描述符标记(cmd = F_GETFD或F_SETFD)。

                (3) 获得/设置文件状态标记(cmd = F_GETFL或F_SETFL)。

                (4) 获得/设置异步I/O所有权(cmd = F_GETOWN或F_SETOWN)。

                (5) 获得/设置记录锁(cmd = F_GETLK、F_SETLK或F_SETLKW)。

        文件状态的三个访问方式标志(O_RDONLY、O_WRONLY以及O_RDWR)并不各占一位(分别是0、1、2,这三种值互斥,一个文件只能有这三种值之一)。因此首先必须用屏蔽字O_ACCMODE取得访问模式位,然后将结果与这三种值的任一种作比较。

        《UNIX环境高级编程》P64:程序清单3-4  对于指定的描述符打印文件标志(有改动)

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int     val;

    if (argc != 2) {
        fprintf(stderr, "usage: a.out <descriptor#>");
        exit(-1);
    }   

    // 获得文件状态标志
    if ((val = fcntl(atoi(argv[1]), F_GETFL, 0)) < 0) {
        fprintf(stderr, "fcntl error for fd %d\n", atoi(argv[1]));
        exit(-1);
    }   

    switch(val & O_ACCMODE) {   // 用屏蔽字O_ACCMODE取得访问模式位
        case O_RDONLY:          // 只读
            printf("read only");
            break;
        case O_WRONLY:          // 只写
            printf("write only");
            break;
        case O_RDWR:            // 读写
            printf("read write");
            break;
        default:
            fprintf(stderr, "unknown access mode");
    }   

    if (val & O_APPEND)         // 追加
        printf(", append");
    if (val & O_NONBLOCK)       // 非阻塞
        printf(", nonblocking");
#if defined(O_SYNC)
    if (val & O_SYNC)           // 每次write都等待物理I/O操作完成
        printf(", synchronous writes");
#endif

#if !defined(_POSIX_C_SOURCE) && defined(O_FSYNC)
    if (val & O_FSYNC)          // 与O_SYNC作用相同
        printf(", synchronous writes");
#endif
    putchar('\n');

    exit(0);
}

        使用了功能测试宏_POSIX_C_SOURCE,并编译了POSIX.1中没有定义的文件访问标志。

        尝试运行

$ ./08 0 < /dev/tty
read only
$ cat temp.foo
write only
$ ./08 2 2>>temp.foo 
write only, append
$ ./08 5 5<>temp.foo
read write

        子句5<>tempfoo表示在文件描述符5上打开文件temp.foo以供读写。

        在修改文件描述符标志或文件状态标志时必须谨慎,先要取得现有的标志值,然后根据需要修改它,最后设置新标志值。不能只是执行F_SETFD或F_SETFL命令,这样会关闭以前设置的标志位。

        《UNIX环境高级编程》P65:程序清单3-5  对一个文件描述符打开一个或多个文件状态标志(有改动)

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

void set_fl(int fd, int flags)
{
    int val;

    if ((val = fcntl(fd, F_GETFL, 0)) < 0) {         // 取得现有标志值
        fprintf(stderr, "fcntl F_GETFL error\n");
        return;
    }   
    val |= flags;                                   // 修改标志,当前文件状态标志val与flags进行逻辑“或”运算

    if (fcntl(fd, F_SETFL, val) < 0) {              // 设置新的标志值
        fprintf(stderr, "fcntl F_SETFL error\n");
        return;
    }   
}

        将fsync和fdatasync函数与O_SYNC标志比较,fsync和fdatasync在我们需要时更新文件内容,O_SYNC标志则在我们每次写至文件时更新文件内容。

PS:        

        对一个文件描述符关闭一个或多个文件状态标志

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

void clr_fl(int fd, int flags)
{
    int val;

    if ((val = fcntl(fd, F_GETFL, 0)) < 0) {         // 取得现有标志值
        fprintf(stderr, "fcntl F_GETFL error\n");
        return;
    }   

    val &= ~flags;                                  // 修改标志,当前文件状态标志val与flags取反后的值进行逻辑“与”运算

    if (fcntl(fd, F_SETFL, val) < 0) {              // 设置新的标志值
        fprintf(stderr, "fcntl F_SETFL error\n");
        return;
    }   
}

        在UNIX系统中,通常write只是将数据排入队列,而实际的写磁盘操作可能在以后的某个时刻进行。设置O_SYNC(每次write都等物理I/O操作完成,包括文件属性更新)标志会增加时钟时间。

测试设置O_SYNC标志对速度的影响测试

        未设置O_SYNC标志

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#define BUFFSIZE    4096

// 用户读、写;组读、写;其他读
#define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH)

int main(int argc, char *argv[])
{
    int     n, fdin, fdout;
    char    buf[BUFFSIZE];

    if (argc != 3) {
        fprintf(stderr, "a.out [输入文件命] [输出文件名]\n");
        exit(-1);
    }   

    // 打开待复制的文件
    if ((fdin = open(argv[1], O_RDONLY, 0)) == -1) {
        fprintf(stderr, "fdin open error:%s\n", strerror(errno));
        exit(-1);
    }   

    // 打开待写入的文件
    if ((fdout = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, FILE_MODE)) == -1) {
        fprintf(stderr, "fout open error:%s\n", strerror(errno));
        exit(-1);
    }   

    while ((n = read(fdin, buf, BUFFSIZE)) > 0) {       // 读数据
        if (write(fdout, buf, n) != n) {                // 写数据
            fprintf(stderr, "write error:%s\n", strerror(errno));
        }
    }   

    if (n < 0) {
        fprintf(stderr, "read error:%s\n", strerror(errno));
    }   
    exit(0);
}

        设置O_SYNC标志

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

// 用户读、写;组读、写;其他读
#define FILE_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH)
#define BUFFSIZE	4096

void set_fl(int fd, int flags)
{
// 代码在上方已有,复制到该位置即可
}

int main(int argc, char *argv[])
{
	int		n, fdin, fdout;
	char	buf[BUFFSIZE];

	if (argc != 3) {
		fprintf(stderr, "a.out [输入文件命] [输出文件名]\n");
		exit(-1);
	}

	// 打开待复制的文件
	if ((fdin = open(argv[1], O_RDONLY, 0)) == -1) {
		fprintf(stderr, "fdin open error:%s\n", strerror(errno));
		exit(-1);
	}

	// 打开待写入的文件
	if ((fdout = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, FILE_MODE)) == -1) {
		fprintf(stderr, "fout open error:%s\n", strerror(errno));
		exit(-1);
	}

	set_fl(fdout, O_SYNC);		// 设置标志位O_SYNC(每次write都等待物理I/O操作完成,包括属性的更新)
	while ((n = read(fdin, buf, BUFFSIZE)) > 0) {		// 读数据
		if (write(fdout, buf, n) != n) {				// 写数据
			fprintf(stderr, "write error:%s\n", strerror(errno));
		}
	}

	if (n < 0) {
		fprintf(stderr, "read error:%s\n", strerror(errno));
	}
	exit(0);
}

        进行测试,其中程序11没有设置O_SYNC而程序12设置了O_SYNC。

        程序会将第一个参数(file0x.in)指定的文件复制到第二个参数(file0x.out)所指定的位置

$ dd if=/dev/zero of=file02.in bs=103316352 count=1
记录了1+0 的读入
记录了1+0 的写出
103316352字节(103 MB)已复制,1.16714 秒,88.5 MB/秒
$ time ./11 file01.in file01.out

real	0m1.342s
user	0m0.020s
sys	0m0.380s
$ time ./12 file02.in file02.out

real	0m1.254s
user	0m0.020s
sys	0m0.398s

        如果进行多次测试,结果可能相差很大。应该是受系统预读和系统缓存的影响。

        当支持同步写时,系统时间和时钟时间应当会显著增加,但在本次测试中(Linux 3.13.0-29-generic #53-Ubuntu SMP Wed Jun 4 21:00:20 UTC 2014 x86_64)同步写所用时间与延迟写所用时间几乎相同。可能依旧和作者所测试的一样:Linux Ext4文件系统并为真正实现O_SYNC标志功能??

        

你可能感兴趣的:(读书笔记,《UNIX环境高级编程》)