一、添写至一个文件

考虑一个进程,它要将数据添加到一个文件尾端。早期的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. What is Cross Linux From Scratch?

    /**************************************************************************** * What is Cross Linux ...

  2. ichartjs

    ichartjs-基于html5的图表组件 不适合桌面端

  3. mysql修改表、字段、库的字符集

    在一次导入数据表(MYISAM)的经历:复制过来的表打开后中文出现乱码,肯定是字符集出现了不致的问题,所以从原数据库导出.sql文件,修改其中的创建表的语句,加入字符集DEFAULT CHARSET= ...

  4. Struts2的struts.properties文件在哪儿啊?

    老师教我们Struts2的时候叫我们建了个Struts.xml文件啊?那struts.properties呢?不需要吗? 回答1: struts.properties 是可以不要的!!!因为 stru ...

  5. 新手!SDK Manager里找不到API安装的选项怎么办?

    只有Tools和EXTRAS文件夹的选项,没有API包安装,咋办呢?   回复讨论(解决方案) 网络有问题吗? 网络有问题吗? 就是不知道啊 你是在eclispe里面打开的?还是在外面直接打开的?没有 ...

  6. c#编程指南(四) 组元(Tuple)

    (1).C#语法中一个个问号(?)的运算符是指:可以为 null 的类型. MSDN上面的解释: 在处理数据库和其他包含不可赋值的元素的数据类型时,将 null 赋值给数值类型或布尔型以及日期类型的功 ...

  7. 静态Web开发 JavaScript

    三章 Javascript 1节javascript基本语法和注意事项 脚本,一条条的文字命令.执行时由系统的一个解释器,将其一条条的翻译成机器可识别的指令,然后执行.(不需要编译)常见的脚本:批处理 ...

  8. 【转】ubuntu下自动挂载硬盘分区

    1, 首先摸清我系统的硬盘分区等使用情况 sudo fdisk -l Disk /dev/sda: 128.0 GB, 128035676160 bytes 255 heads, 63 sectors ...

  9. UVA 11488-Hyper Prefix Sets(Trie)

    题意: 给一个01串的集合,一个集合的幸运值是串的个数*集合中串的最大公共前缀 ,求所有子集中最大幸运值 分析: val[N]表示经过每个节点串的个数求幸运值 求就是每个节点值*该节点的深度 搜一遍树 ...

  10. [LeetCode] Two Sum水过

    刷LeetCode的第一题,TwoSum,基本算是水过. 题目:https://leetcode.com/problems/two-sum/ Given an array of integers, f ...