我正在翻阅《The Linux Programming Interface
》一书。在第4章第73页,上面写着:
fd = open("w.log", O_WRONLY | O_CREAT | O_TRUNC | O_APPEND, S_IRUSR | S_IWUSR);
我读到O_TRUC
标志用于将文件长度截断为零,从而破坏文件中的任何现有数据。
O_APPEND
标志用于将数据附加到文件的末尾。
内核记录一个file offset
,有时也称为read-write offset or pointer
。这是文件中下一个read()
或write()
将开始的位置。
我感到困惑的是,如果文件被截断,内核在文件末尾进行后续写入,为什么需要append标志来明确告知在文件末尾追加?
在没有append标志的情况下(如果文件被截断),内核会在文件的末尾为后续的write()
函数调用进行写入。
O_APPEND
标志用于将数据附加到文件末尾。
这是真的,但不够完整,可能会产生误导。我怀疑你实际上在这方面感到困惑。
内核记录一个文件偏移量,有时也称为读写偏移量或指针。这是文件中下一个
read()
或write()
将开始的位置。
这也是不完整的。至少每个可查找文件都有一个文件偏移量。这是下一个read()
将开始的位置。如果文件未在附加模式下打开,则下一个write()
将开始,但在附加模式中,每次写入都发生在文件末尾,就好像在每次写入之前用lseek(fd, 0, SEEK_END)
重新定位一样。那么,在这种情况下,当前文件偏移量可能不是下一个write()
将开始的位置。
我很困惑,如果文件被截断,内核在文件末尾进行后续写入,为什么需要append标志来明确告知在文件末尾追加?
不需要导致截断后的第一次写入(由任何进程执行)发生在文件末尾,因为文件被截断后立即没有任何其他位置。
在没有append标志的情况下(如果文件被截断),内核会在文件的末尾进行写入,以进行后续的write()函数调用。
只要文件没有重新定位或外部修改,后续写入也不需要它。否则,下一次写入的位置取决于文件是否以追加模式打开。
在实践中,不一定每个标志的组合都是有用的,但O_TRUNC
和O_APPEND
的组合与没有另一个标志的组合具有明显不同的效果,并且该组合在某些情况下是有用的。
O_APPEND
与O_TRUNC
很少有意义。我认为fopen
模式的任何组合都不会产生这种组合(在POSIX系统上,这是相关的)。
O_APPEND
确保无论写入位置如何,每次写入都会在文件末尾自动完成。特别是,这意味着如果多个进程正在向文件写入,它们不会践踏彼此的写入。
请注意,POSIX不需要
O_APPEND
的原子行为。它要求在写入之前自动查找到文件的(当前)末尾,但不要求在写入发生时该位置仍然是文件的末尾。即使在具有原子O_APPEND
的实现上,它也可能不适用于所有文件系统。open
上的Linux手册页警告说,O_APPEND
在NFS上不能以原子方式工作。
现在,如果每个进程在打开文件时都使用O_TRUNC
,那么它将破坏其他进程所写的所有内容。这与进程不应该破坏彼此写入的想法相冲突,为此指定了O_APPEND
。
O_APPEND
不需要通过被理解为唯一写入程序的单个进程来附加到文件。可以只搜索到最后,然后开始写入新数据。有时O_APPEND
在独占情况下使用,只是因为它是一种编程快捷方式。我们不必麻烦打额外的电话来定位到文件的末尾。比较:
FILE *f = fopen("file.txt", "a");
// check f and start writing
对比:
FILE *f = fopen("file.txt", "r+");
// check f
fseek(f, 0, SEEK_END); // go to the end, also check this for errors
// start writing
我们可以考虑这样的想法,即我们有一组进程使用O_APPEND
来处理一个文件,这样第一个进程也会执行O_TRUNC
来首先截断它。但编程似乎很尴尬;对于一个进程来说,判断它是否是第一个打开文件的进程并不容易。
如果在启动时需要这样的情况,例如,由于某种原因,启动前的旧文件不相关,只需在启动这些多个进程之前执行启动时操作(脚本或其他任何操作)即可删除旧文件。然后,如果需要(如果是第一个进程),每个进程都使用O_CREAT
创建文件,但不使用O_TRUNC
(如果不是第一个进程的话),并使用O_APPEND
进行原子(如果可用)附加。
两者完全独立。该文件只是用O_APPEND
打开的,因为它是一个日志文件。
作者希望并发消息连接起来,而不是相互覆盖(例如,如果程序分叉),如果管理员或日志轮换工具截断了文件,那么新消息应该在第#1行开始写入,而不是在写入最后一个日志条目的第1000000行。如果没有O_APPEND,这是不会发生的。