首先给出一段程序,这段程序的本来目的是为了验证用O_APPEND标志打开文件后,调用lseek(fd, 0, SEEK_CUR)应该返回文件长度,但是运行程序后发现错了。注意加红的两句,本来希望分别输出0和49,但实际输出都是零。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int fd;
off_t off;
printf("Not using O_APPEND:\n");
fd = open("./test.c", O_WRONLY);
off = lseek(fd, 0, SEEK_SET);
printf("The offset of SEEK_SET is %d.\n", off);
close(fd);
fd = open("./test.c", O_WRONLY);
off = lseek(fd, 0, SEEK_END);
printf("The offset of SEEK_END is %d.\n", off);
close(fd);
fd = open("./test.c", O_WRONLY);
off = lseek(fd, 0, SEEK_CUR);
printf("The offset of SEEK_CUR is %d.\n", off);
close(fd);
printf("Using O_APPEND:\n");
fd = open("./test.c", O_WRONLY | O_APPEND);
off = lseek(fd, 0, SEEK_SET);
printf("The offset of SEEK_SET is %d.\n", off);
close(fd);
fd = open("./test.c", O_WRONLY | O_APPEND);
off = lseek(fd, 0, SEEK_END);
printf("The offset of SEEK_END is %d.\n", off);
close(fd);
fd = open("./test.c", O_WRONLY | O_APPEND);
off = lseek(fd, 0, SEEK_CUR);
printf("The offset of SEEK_CUR is %d.\n", off);
close(fd);
exit(0);
}
输出为:
Not using O_APPEND:
The offset of SEEK_SET is 0.
The offset of SEEK_END is 49.
The offset of SEEK_CUR is 0.
Using O_APPEND:
The offset of SEEK_SET is 0.
The offset of SEEK_END is 49.
The offset of SEEK_CUR is 0.
原本以为O_APPEND把文件指针移到了文件尾,以方便从文件尾添加数据,但这么看来不对,文件指针并没有移动。问题出在哪里呢?
从APUE中得到答案:
每个进程有一个打开文件描述符表,而该表的表项中有一个文件状态标志,当前文件偏移量和一个v节点指针,v节点指针指向一个v节点表,而该表中的i节点信息中有一个当前文件长度。
对于lseek来说,它直接修改文件描述符表项中的当前文件偏移量,并返回当前的文件偏移量,而对于O_APPEND标志,则只是将其设置到了文件表项的文件状态标志中,此时当前文件偏移量并未修改,仍然为0,只有当用户企图写入文件时,才将文件当前偏移量置为文件长度。这从另一个角度说明,如果文件以O_APPEND标志打开,则lseek对该文件的写将不起作用,因为无论lseek怎样调整当前文件偏移量,在写入时仍然会被设为文件长度而将内容添加在文件尾。例如如下程序:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int fd;
off_t off;
fd = open("./test.c", O_WRONLY | O_APPEND);
off = lseek(fd, 3, SEEK_SET);
write(fd, "a", 1);
printf("The offset of SEEK_SET is %d.\n", off);
close(fd);
exit(0);
}
输出为:
The offset of SEEK_SET is 3.
可见,lseek修改了当前文件偏移量,但是,查看test.c文件,a却添加在了文件尾,验证了上面的内容。但是,对于读来说,O_APPEND就不起作用了,如下面的程序:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int fd;
char buf[3];
off_t off;
fd = open("./test.c", O_RDONLY | O_APPEND);
off = lseek(fd, 3, SEEK_SET);
read(fd, buf, 2);
buf[2] = '\0';
printf("%s\n", buf);
printf("The offset of SEEK_SET is %d.\n", off);
close(fd);
exit(0);
}
输出为:
cl
The offset of SEEK_SET is 3.
程序读到了文件偏移量为3处的两个字符。
可见,OAPPEND标志的主要作用就是保证在多进程写入时,保证每个进程写入的内容都的确添加在了文件尾,因为不管有几个进程,在他们写同一个文件时,都会首先将当前文件偏移量修改为此时此刻的文件长度,这就保证了文件写入的原子性。