文件I/O(不带缓冲)之原子操作
一、添写至一个文件
考虑一个进程,它要将数据添加到一个文件尾端。早期的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(不带缓冲)之原子操作的更多相关文章
- 文件I/O(不带缓冲)概述
一.引言 UNIX系统中大多数文件I/O只需用到5个函数:open.read.write.lseek以及close.这些函数经常被称为不带缓冲的I/O(unbuffered I/O).术语不带缓冲指的 ...
- UNIX环境编程学习笔记(2)——文件I/O之不带缓冲的 I/O
lienhua342014-08-25 1 文件描述符 对于内核而言,所有打开的文件都通过文件描述符引用.文件描述符是一个非负整数.当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符. ...
- 带缓冲I/O和不带缓冲I/O的区别与联系
转自:http://blog.csdn.net/lmh12506/article/details/6803847 首先要明白不带缓冲的概念:所谓不带缓冲,并不是指内核不提供缓冲,而是只单纯的系统调用, ...
- Unix文件 I/O(不带缓冲区的)上
简介 Unix系统大多数文件i/o只需要:open.read.write.lseek.close这几个函数.但是某些时候我们也需要fcntl.ioctl.sync等函数配合使用.这些函数都是不带缓冲区 ...
- 带缓冲I/O 和不带缓冲I/O的区别与联系
首先要明白不带缓冲的概念:所谓不带缓冲,并不是指内核不提供缓冲,而是只单纯的系统调用,不是函数库的调用.系统内核对磁盘的读写都会提供一个块缓冲(在有些地方也被称为内核高速缓存),当用write函数对其 ...
- 带缓冲的IO和不带缓冲的IO
文件描述符: 文件描述符是一个小的非负整数,是内核用来标识特定进程正在访问的文件 标准输入/输出/出错: shell为每个程序打开了三个文件描述符,STDIN_FILEON,STDOUT_FILEON ...
- 第十三篇:带缓冲的IO( 标准IO库 )
前言 在之前,学习了 read write 这样的不带缓冲IO函数. 而本文将讲解标准IO库中,带缓冲的IO函数. 为什么要有带缓冲IO函数 标准库提供的带缓冲IO函数是为了减少 read 和 wri ...
- Java 带缓冲的字节流和字符流
输入流就是文件从硬盘到内存的中间媒介,那么输出流就是文件从内存到硬盘的中间媒介.首先来看看FileOutputStream的继承了哪些类, java.lang.Object java.io.Outpu ...
- 带缓冲的IO( 标准IO库 )
前言 在之前,学习了 read write 这样的不带缓冲IO函数.而本文将讲解标准IO库中,带缓冲的IO函数. 为什么要有带缓冲IO函数 标准库提供的带缓冲IO函数是为了减少 read 和 writ ...
随机推荐
- linux tmp75 /dev/i2c-* 获取数据 demo
/********************************************************************** * linux tmp75 /dev/i2c-* 获取数 ...
- 【转】AngularJS路由和模板
1. AngularJS路由介绍 AngularJS路由功能是一个纯前端的解决方案,与我们熟悉的后台路由不太一样.后台路由,通过不同的URL会路由到不同的控制器上(controller),再渲染(re ...
- Asp.net mvc中整合autofac
创建Asp.net MVC并引入Autofac 首先,创建一个MVC站点,为方便起见,选初始带HomeController和AccountController的那种.然后通过NuGet或到Autofa ...
- Ext入门学习系列(二)弹出窗体
第二章 弹出窗体 上节学习了Ext的环境搭建和最基本的一个操作——弹出对话框,作为一个引子,本节讲述如何弹出一个新窗体,从实例讲解Ext的基本运行原理. 一.Ext的窗体长什么样? 先来看看几个效果, ...
- 实体框架Entity Framework 4.1快速入门
介 绍 在旧的Entity 框架中,开发者可以从已存在的数据库中产生业务实体的模型,这种开发方法被称为数据库驱动的开发方法.而在4.1的Entity Framework中,支开发者先创建实体业务类,然 ...
- CCR
不用任何与创建线程.资源互斥有关系的API写多线程程序 这次的例子,是一个很简单的控制台,她将面对瞬间提交的百万的数据,而面不改色(CPU.内存非常平稳),队列中始终只保存最新的数据,每次只处理cpu ...
- Android Activity四种加载方式
Android之四种加载方式 (http://marshal.easymorse.com/archives/2950 图片) 在多Activity开发中,有可能是自己应用之间的Activity跳转,或 ...
- 设计模式_Composite_合成模式
形象例子: Mary今天过生日.“我过生日,你要送我一件礼物.”“嗯,好吧,去 商店,你自己挑.”“这件T恤挺漂亮,买,这条裙子好看,买,这个包也不错,买.”“喂,买了三件了呀,我只答应送一件礼物的哦 ...
- 转载c#泛型 类型参数的约束(c#编程指南)
在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制.如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误.这些限制称为约束.约束是使用 where 上 ...
- cocos2d-x 3.0 开发(一) Hello_New_World
1.Previous On 2dx 2dx 的3.0版本是个与以往不同的版本.变化比2dx从1.x 到2.x的变化还要大不少.具体的新功能可以参见:CocoaChina大会见闻——cocos2 ...