一、添写至一个文件

考虑一个进程,它要将数据添加到一个文件尾端。早期的UNIX系统并不支持open的O_APPEND选项,所以程序被编写成下列形式:

if( lseek( fd, 0L,  ) <  )        /* position to EOF */
err_sys( "lseek error" );
if( write( fd, buf, ) != ) /* and write */
err_sys( "write error" );

对单个进程而言,这段程序能正常工作,但若有多个进程同时使用这种方法将数据添加到同一文件,则会产生问题。

假定有两个独立的进程A和B都对同一文件进行添加操作。每个进程都已打开了该文件,但未使用O_APPEND标志。此时,各数据结构之间的关系如图3-2所示(参考【文件I/O(不带缓冲)之文件共享】)。每个进程都有它自己的文件表项,但是共享一个v节点表项。假定进程A调用了lseek,它将进程A的该文件当前偏移量设置为1500字节(当前文件尾端处)。然后内核切换进程使进程B运行。进程B执行lseek,也将其对该文件的当前偏移量设置为1500字节(当前文件尾端处)。然后B调用write,它将B的该文件当前文件偏移量增至1600.因为该文件的长度已经增加了,所以内核对v节点中的当前文件长度更新为1600。然后,内核又进行进程切换使进程A恢复运行。当A调用write时,就从其当前文件偏移量(1500字节)处将数据写到文件中去。这样也就代换了进程B刚写到该文件中的数据。

问题出在逻辑操作“定位到文件尾端处,然后写”上,它使用了两个分开的函数调用。解决问题的方法是使这两个操作对于其他进程而言成为一个原子操作。任何一个需要多个函数调用的操作都不可能是原子操作,因为在两个函数调用之间,内核有可能会临时挂起该进程。

UNIX提供了一种方法使这种操作成为原子操作,该方法是在打开文件时设置O_APPEND标志。这就使内核每次对这种文件进行写之前,都将进程的当前偏移量设置到该文件的尾端处,于是在每次写之前就不再需要调用lseek。

二、pread和pwrite函数

Single UNIX Specification包括了XSI扩展,该扩展允许原子性地定位搜索(seek)和执行I/O。pread和pwrite就是这种扩展。

#include <unistd.h>

ssize_t pread( int filedes, void *buf, size_t nbytes, off_t offset );
返回值:读到的字节数,若已到文件结尾则返回0,若出错则返回- ssize_t pwrite( int filedes, const void *buf, size_t nbytes, off_t offset );
返回值:若成功则返回已写的字节数,若出错则返回-

调用pread相当于顺序调用lseek和read,但是pread又与这种顺序调用有下列重要的区别:

  • 调用pread时,无法中断其定位和读操作。
  • 不更新文件指针。

调用pwrite相当于顺序调用lseek和write,但也与他们有类似的区别。

SYNOPSIS
       #define _XOPEN_SOURCE 500

   #include <unistd.h>

   ssize_t pread(int fd, void *buf, size_t count, off_t offset);

   ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

DESCRIPTION

       pread()  reads  up to count bytes from file descriptor fd at offset offset

       (from the start of the file) into the buffer starting at  buf.   The  file

       offset is not changed.

   pwrite()  writes  up to count bytes from the buffer starting at buf to the

       file descriptor fd at offset offset. The file offset is not changed.

   The file referenced by fd must be capable of seeking.

RETURN VALUE

       On success, the number of bytes read or written is  returned  (zero  indi-

cates  that  nothing was written, in the case of pwrite(), or end of file,

   in the case of pread), or -1 on error, in which case errno is set to indi-

      cate the error.

三、创建一个文件

对open函数同时指定O_CREAT和O_EXCL选项,如果该文件已经存在时,open将失败。检查该文件是否存在以及创建该文件这两个操作是作为一个原子操作执行的。如果没有这样一个原子操作,那么可能会编写下列程序段:

if( ( fd = open( pathname, O_WRONLY )) <  )
{
if( errno == ENOENT )
{
if(( fd = creat( pathname, mode )) < )
err_sys( "creat error" );
}
else
{
err_sys( "open error" );
}
}

如果在open和creat之间,另一个进程创建了该文件,那么就会引起问题。例如,若在这两个函数调用之间,另一个进程创建了该文件,并且写进了一些数据,然后,原先的进程执行这段程序中的creat,这时,刚由另一个进程写上去的数据就会被擦去。如若将这两者合并在一个原子操作中,这种问题也就不会产生。

一般而言,原子操作(atomic operation)指的是由多步组成的操作。如果该操作原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。

本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/

最新文章

  1. Analyzer报表结果行
  2. Winform下WebBrowser 编辑模式 监听键盘按键事件
  3. DevExpress.XtraGrid的使用(部分)
  4. HDU 4614-Vases and Flowers(线段树区间更新)
  5. jQuery 表单验证插件——Validation(基础)
  6. 关于Meta标签中format-detection属性及含义
  7. C语言通过函数参数不能带出动态内存的例子。
  8. #034Python选修课第二届Turtle绘图大赛
  9. CSS常见的中属性级,选择符级的Hack
  10. 【ssh】端口转发
  11. jvm理论-运行时数据区
  12. Redis setnx命令 分布式缓存
  13. react native中使用 react-native-easy-toast 和react-native-htmlview
  14. jQuery懒加载插件jquery.lazyload.js使用说明实例
  15. IIS隐藏版本号教程(Windows Server 2003)
  16. 在Delphi中处理word文档与数据库的互联
  17. HDU6188
  18. springmvc转换JSON数据
  19. Oracle用户权限及死锁
  20. 利用SynchronizationContext.Current在线程间同步上下文

热门文章

  1. Centos 下Nginx 自启动脚本
  2. apache开源项目--Sirona
  3. ASP.NET MVC 开篇
  4. UVA 10585 Center of symmetry
  5. 交易的成功 = 60%的资金管理 + 40%出入场信号 zt
  6. 430flash的操作
  7. error: Setup script exited with error: Unable to find vcvarsall.bat
  8. 【原】理解Storm拓扑的并行
  9. Linux Vi 删除全部内容,删除某行到结尾,删除某段内容 的方法
  10. 嵌入式开发应该掌握的一些Linux命令