我正在阅读这本书the linux programming interface by Michael Kerrisk。
提供以下源代码作为练习5-3的解决方案。评论描述了行使的目的和程序及其用法。
/*************************************************************************\
* Copyright (C) Michael Kerrisk, 2017. *
* *
* This program is free software. You may use, modify, and redistribute it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation, either version 3 or (at your option) any *
* later version. This program is distributed without any warranty. See *
* the file COPYING.gpl-v3 for details. *
/* Solution for Exercise 5-3 */
/* atomic_append.c
Demonstrate the difference between using nonatomic lseek()+write()
and O_APPEND when writing to a file.
Usage: file num-bytes [x]
The program write 'num-bytes' bytes to 'file' a byte at a time. If
no additional command-line argument is supplied, the program opens the
file with the O_APPEND flag. If a command-line argument is supplied, the
O_APPEND is omitted when calling open(), and the program calls lseek()
to seek to the end of the file before calling write(). This latter
technique is vulnerable to a race condition, where data is lost because
the lseek() + write() steps are not atomic. This can be demonstrated
by looking at the size of the files produced by these two commands:
atomic_append f1 1000000 & atomic_append f1 1000000
atomic_append f2 1000000 x & atomic_append f2 1000000 x
*/
#include <sys/stat.h>
#include <fcntl.h>
#include "lib/tlpi.h"
int
main(int argc, char *argv[])
{
int numBytes, j, flags, fd;
Boolean useLseek;
if (argc < 3 || strcmp(argv[1], "--help") == 0)
usageErr("%s file num-bytes [x]\n"
" 'x' means use lseek() instead of O_APPEND\n",
argv[0]);
useLseek = argc > 3;
flags = useLseek ? 0 : O_APPEND;
numBytes = getInt(argv[2], 0, "num-bytes");
fd = open(argv[1], O_RDWR | O_CREAT | flags, S_IRUSR | S_IWUSR);
if (fd == -1)
errExit("open");
for (j = 0; j < numBytes; j++) {
if (useLseek)
if (lseek(fd, 0, SEEK_END) == -1)
errExit("lseek");
if (write(fd, "x", 1) != 1)
fatal("write() failed");
}
printf("%ld done\n", (long) getpid());
exit(EXIT_SUCCESS);
}
在运行命令atomic_append f1 1000000 & atomic_append f1 1000000
和atomic_append f2 1000000 x & atomic_append f2 1000000 x
之后,作者写道ls -l f1 f2
应该类似于
-rw------- 1 mtk users 2000000 Jan 9 11:14 f1
-rw------- 1 mtk users 1999962 Jan 9 11:14 f2
也就是说,在不使用lseek(2)
的情况下,所有字节都被写入而没有任何数据丢失。没有数据丢失,因为write(2)
是原子的,并且由于在O_APPEND
文件时使用open(2)
标志,因此两个进程都不会覆盖彼此的输出。
然而,在运行macOS High Sierra 10.13.1的机器上运行这个确切的源代码后,两个命令都会导致某些字节被覆盖,这表明write(2)
不是原子的。在我的机器上,ls -l f1 f2
输出
-rw------- 1 user staff 1983995 Jun 7 21:38 f1
-rw------- 1 user staff 1964984 Jun 7 21:37 f2
这让我非常困惑。为什么文件write(2)
上的open(2)
加上O_APPEND
标志看似不是原子的?
我已经使用上面的程序在干净的文件上仔细执行了上述命令。结果总是重现。
如果需要,我可以提供更多关于练习或我的设置的背景信息。
这被标记为this question的副本。那里的讨论提供了一些见解,即: