一、添写至一个文件

考虑一个进程,它要将数据添加到一个文件尾端。早期的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/

文件I/O(不带缓冲)之原子操作的更多相关文章

  1. 文件I/O(不带缓冲)概述

    一.引言 UNIX系统中大多数文件I/O只需用到5个函数:open.read.write.lseek以及close.这些函数经常被称为不带缓冲的I/O(unbuffered I/O).术语不带缓冲指的 ...

  2. UNIX环境编程学习笔记(2)——文件I/O之不带缓冲的 I/O

    lienhua342014-08-25 1 文件描述符 对于内核而言,所有打开的文件都通过文件描述符引用.文件描述符是一个非负整数.当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符. ...

  3. 带缓冲I/O和不带缓冲I/O的区别与联系

    转自:http://blog.csdn.net/lmh12506/article/details/6803847 首先要明白不带缓冲的概念:所谓不带缓冲,并不是指内核不提供缓冲,而是只单纯的系统调用, ...

  4. Unix文件 I/O(不带缓冲区的)上

    简介 Unix系统大多数文件i/o只需要:open.read.write.lseek.close这几个函数.但是某些时候我们也需要fcntl.ioctl.sync等函数配合使用.这些函数都是不带缓冲区 ...

  5. 带缓冲I/O 和不带缓冲I/O的区别与联系

    首先要明白不带缓冲的概念:所谓不带缓冲,并不是指内核不提供缓冲,而是只单纯的系统调用,不是函数库的调用.系统内核对磁盘的读写都会提供一个块缓冲(在有些地方也被称为内核高速缓存),当用write函数对其 ...

  6. 带缓冲的IO和不带缓冲的IO

    文件描述符: 文件描述符是一个小的非负整数,是内核用来标识特定进程正在访问的文件 标准输入/输出/出错: shell为每个程序打开了三个文件描述符,STDIN_FILEON,STDOUT_FILEON ...

  7. 第十三篇:带缓冲的IO( 标准IO库 )

    前言 在之前,学习了 read write 这样的不带缓冲IO函数. 而本文将讲解标准IO库中,带缓冲的IO函数. 为什么要有带缓冲IO函数 标准库提供的带缓冲IO函数是为了减少 read 和 wri ...

  8. Java 带缓冲的字节流和字符流

    输入流就是文件从硬盘到内存的中间媒介,那么输出流就是文件从内存到硬盘的中间媒介.首先来看看FileOutputStream的继承了哪些类, java.lang.Object java.io.Outpu ...

  9. 带缓冲的IO( 标准IO库 )

    前言 在之前,学习了 read write 这样的不带缓冲IO函数.而本文将讲解标准IO库中,带缓冲的IO函数. 为什么要有带缓冲IO函数 标准库提供的带缓冲IO函数是为了减少 read 和 writ ...

随机推荐

  1. js jquery版本的 金额千分位转换函数(非正则,效率极高)

    没想到js里面没有 金额千分位格式化的处理函数(例:1,234.01 这样的格式),网上搜了一圈,都是使用正则的方式处理的.正则的效率不敢恭维啊,又耗费资源速度又慢(虽然处理起来会直观一些). 因此专 ...

  2. CentOS 7 安装 tomcat7.0

    安装tomcat: [root@admin local]# cd /usr/local[root@admin local]# tar -zxv -f apache-tomcat-7.0.29.tar. ...

  3. 【转】Android动态改变对 onCreateDialog话框值 -- 不错不错!!!

    原文网址:http://www.111cn.net/sj/android/46484.htm 使用方法是这样的,Activity.showDialog()激发Activity.onCreateDial ...

  4. TortoiseGit连接github不用每次输入用户名和密码的方法

    每次git clone 和push 都要输入用户名和密码.虽然安全,但在本机上每次都输有些麻烦,如何记住用户名和密码呢? 当你配置好git后,在C:\Documents and Settings\Ad ...

  5. Key/Value之王Memcached初探:二、Memcached在.Net中的基本操作 - Edison Chou

    一.Memcached ClientLib For .Net 首先,不得不说,许多语言都实现了连接Memcached的客户端,其中以Perl.PHP为主. 仅仅memcached网站上列出的语言就有: ...

  6. 排序算法(C#)

    1.插入排序 1.1直接插入排序 算法介绍: 直接插入排序(straight insertion sort)的做法是:   每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序. ...

  7. 【JMeter】ant+jmeter生成html报告

    源博文来自于  http://my.oschina.net/hellotest/blog/517518 主要应用于接口的回归或者性能的简单查看功能.操作为先在jmeter中写好测试计划,保存为jmx文 ...

  8. jquery选择器返回值

    jquery选择器$('selector')返回的不是数组,而是封装好的jquery对象.但这个对象有一个特别的地方,就是查询到的节点被以下标为属性,添加到了jquery对象上,所以它看起来像数组,因 ...

  9. 深入了解JavaScript中的关键字

    this是Javascript语言的一个关键字它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用,下面分四种情况,详细讨论this的用法,感兴趣的朋友可以了解下. this是Javascri ...

  10. IAR 编译错解决Error[e16]: Segment NEAR_Z (size: 0x16d align: 0) is too long for segment definition. At least 0x83 more bytes needed.

    Error[e16]: Segment NEAR_Z (size: 0x16d align: 0) is too long for segment definition. At least 0x83 ...