分类: 系统运维
2012-03-28 12:57:17
添加到文件
考虑一个想添加到文件末尾的单独进程。UNIX的早期版本没有为open支持O_APPEND选项,所以程序可能会以如下方式编码:
if (lseek(fd, 0L, 2) < 0)
err_sys("lseek error");
if (write(fd, buf, 100) != 100)
err_sys("write error");
这在单进程上可以工作,但如果多进程使用这种技术来添加到同一文件时会产生问题。(比如,如果一个程序的多个实例在一个日志文件上添加信息时,这种情形会发生。)
假设两个独立的进程,A和B,添加到同一个文件。每个进程打开文件但不使用O_APPEND标志。每个进程都有自己的文件表项,但它们共享一个单一的v-
node表项。假设A进程用lseek设置当前偏移量为1500字节(当前的文件末尾)。然后内核交换进程,B进程继续运行。B进程然后使用lseek设
置当前偏移量为1500字节(当前文件末尾)。然后B调用write,使得B的当前文件偏移量增加到1600。然后内核交换进程而A继续运行。当A调用
write时,数据从A的当前文件偏移量开始写,它是1500字节。这样会覆盖掉B写入文件的数据。
这里的问题是我们的逻辑操作“定位到文件末尾并写入”需要两个函数调用。解决方法是让定位到文件末尾和写入成为一个对于其它进程而言的原子操作。任何需要两个函数调用的操作都不可能是原子的,因为内核总是可能在两个函数调用之间临时挂起一个进程。
UNIS系统提供了一个原子方式来实现这种操作:当打开文件时设置O_APPEND标志。正如我们在前一节描述的一样,这会导致内核在每次write之前定位到当前文件末尾。我们不再需要在每次write前调用lseek。
pread和pwrite函数
SUS包含了允许应用程序原子化定位并执行I/O的XSI扩展。这个扩展为pread和pwrite:
#include
ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset);
返回读取的字节数,文件末尾则返回0,错误返回-1。
ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset);
成功返回写入的字节数,错误返回-1。
调用pread等价于调用lseek后再调用read,除了以下区别:
1、使用pread时不能中断这两个操作。
2、文件指针没有更新。
调用pwrite等价于调用lseek后再调用write,和上面有类似的区别。
创建一个文件
当描述open函数的O_CREAT和O_EXCL选项时,我们看过另一个原子操作的例子。当两个选项都被指定时,如果文件已经存在open会失败。我们也说过检查文件的存在和文件的创建是作为一个原子操作来执行的。如果我们没有这个原子操作,我们可能会尝试:
if ((fd = open(pathname, O_WRONLY)) <0) {
if (errno == ENOENT) {
if ((fd = creat(pathname, mode)) < 0)
err_sys("creat error");
}
}
如果在open和creat之间有另一个进程创建这个文件时会发生问题。如果在两个函数调用之间另一个进程创建了这个文件并写入了些数据,那这些数据会在creat执行时被擦除。把测试文件存在与创建合并成一个原子操作避免了这个问题。
一般来说,术语原子操作是指一个可能由多个步骤组成的操作。如果操作被原子执行,那要么多久的步骤都被执行,要么没有一个步骤被执行。不可能只有这些步骤的一个子集被执行。我们在4.15节讨论link函数时和在14.3节讨论记录锁时再回到这个原子操作的话题。