摘自APUE2
3.11 原子操作
1 添加至一个文件
考虑一个进程,它要将数据添加到一个文件尾端。早期的UNIX版本并不支持open的O_APPEND选择项,所以程序被编写成下列形式:
if(lseek(fd,0L,2)<0) /*positiontoEOF*/
err_sys("lseekerror");
if(write(fd,buff,100)!=100) /*andwrite*/
err_sys("writeerror");
对单个进程而言,这段程序能正常工作,但若有多个进程时,则会产生问题。(如果此程序由多个进程同时执行,各自将消息添加到一个日记文件中,就会产生这种情况。)
假定有两个独立的进程A和B,都对同一文件进行添加操作。每个进程都已打开了该文件,自己的文件表项,但是共享一个v节点表项。假定进程A调用了
lseek,它将对于进程A的该文件的当前位移量设置为1500字节(当前文件尾端处)。然后内核切换进程使进程B运行。进程B执行lseek,也将其对
该文件的当前位移量设置为1500字节(当前文件尾端处)。然后B调用write,它将B的该文件的当前文件位移量增至1600。因为该文件的长度已经增
加了,所以内核对v节点中的当前文件长度更新为1600。然后,内核又进行进程切换使进程A恢复运行。当A调用write时,就从其当前文件位移量
(1500)处将数据写到文件中去。这样也就代换了进程B刚写到该文件中的数据。
这里的问题出在逻辑操作“定位档到文件尾端处,然后写”使用了两个分开的函数调用。
解决问题的方法是使这两个操作对于其他进程而言成为一个原子操作。任何一个要求多于1个函数调用的操作都不能成为原子操作,因为在两个函数调用之间,内核有可能会临时挂起该进程(正如我们前面所假定的)。
UNIX提供了一种方法使这种操作成为原子操作,其方法就是在打开文件时设置O_APPEND标志。正如前一节中所述,这就使内核每次对这种文件进行写之前,都将进程的当前位移量设置到该文件的尾端处,于是在每次写之前就不再需要调用lseek。
2 创建一个文件
在对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");
}
}
else
err_sys("open error");
如果在打开和创建之间,另一个进程创建了该文件,那么就会发生问题。如果在这两个函数调用之间,另一个进程创建了该文件,而且又向该文件
写进了一些数据,那么执行这段程序中的creat 时,刚写上去的数据就会被擦去。将这两者合并在一个原子操作中,此种问题也就不会产生。
一般而言,原子操作(atomic operation)指的是由多步组成的操作。如果该操作原子地执行,则或者执行完所有步,或者一步也不执行,不可能只执行所有步的一个子集。在论述link函数以及述及记录锁时,还将讨论原子操作。
阅读(2202) | 评论(0) | 转发(0) |