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标志则在我们每次写至文件时更新文件内容。
对一个文件描述符关闭一个或多个文件状态标志
#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标志
#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标志功能??