【摘自《Linux/Unix系统编程手册》】

所有系统调用都是以原子操作方式执行的。这里是指内核保证了某系统调用中的所有步骤会作为独立操作而一次性执行,其间不会为其它进程或线程所中断。

原子性是某些操作得以圆满成功的关键所在。特别是它规避了竞争状态(race conditions)。竞争状态是这样一种情形:操作共享资源的两个进程(或线程),其结果取决于一个无法预期的顺序,即这些进程(或线程)获得CPU使用权的先后相对顺序。

以独占方式创建一个文件

当同时指定O_EXCL与O_CREAT作为open()的标志位时,如果要打开的文件已然存在,则open()将返回一个错误。

这就提供了一个机制,保证了进程是打开文件的创建者。对文件是否存在的检查和创建文件属于同一原子操作。

下面这段代码没有使用O_EXCL标志

     fd = open(argv[], O_WRONLY);       /* Open 1: check if file exists */
if (fd != -) { /* Open succeeded */
printf("[PID %ld] File \"%s\" already exists\n",
(long) getpid(), argv[]);
close(fd);
} else {
if (errno != ENOENT) { /* Failed for unexpected reason */
errExit("open");
} else {
printf("[PID %ld] File \"%s\" doesn't exist yet\n",
(long) getpid(), argv[]);
if (argc > ) { /* Delay between check and create */
sleep(); /* Suspend execution for 5 seconds */
printf("[PID %ld] Done sleeping\n", (long) getpid());
}
fd = open(argv[], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -)
errExit("open"); printf("[PID %ld] Created file \"%s\" exclusively\n",
(long) getpid(), argv[]); /* MAY NOT BE TRUE! */
}
}

可以看到,它调用了open()两次,而且还潜伏了一个bug:

假设当第一次调用open()时,希望打开的文件还不存在,而当第二次调用open()时,其它进程已经创建了该文件,则当前进程会得出错误的结论:目标文件是自己创建的。

向文件尾部追加数据

用以说明原子操作必要性的第二个例子是:多个进程同时向同一个文件(例如,全局的日志文件)尾部添加数据。为了达到这一目的,也许可以考虑在每个写进程中使用如下代码

 if (lseek(fd, , SEEK_END) == -)
errExit("lseek");
if (write(fd, buf, len) != len)
fatal("Partial/failed write");

但是这段代码存在的缺陷和前一个例子如出一辙。如果第一个进程执行到lseek()和write()之间,被执行相同代码的第二个进程所中断,那么这两个进程会在写入数据前,将文件偏移量设为相同位置,而当第一个进程再次获得调度时,会覆盖第二个进程已写入的数据。

要规避这一问题,需要将文件偏移量的移动与数据写操作纳入同一原子操作。在打开文件时加入O_APPEND标志可以保证这一点。注:有些文件系统(例如NFS)不支持O_APPEND标志。在这种情况下,内核会选择按如上代码所示的方式,施之以非原子操作的调用序列,从而可能导致上述的文件脏写入问题。

多线程中的应用

系统调用pread()和pwrite()完成与read()和write()相类似的工作,只是前两者会在offset参数所指定的位置进行文件I/O操作,而非始于文件的当前偏移量处,且它们不会改变文件的当前偏移量。

 #include <unistd.h>
ssize_t pread(int fd, void* buf, size_t count, off_t offset);
Returns number of bytes read, on EOF, or - on error
ssize_t pwrite(int fd, const void* buf, size_t count, off_t offset);
Returns number of bytes written, or - on error

pread()调用等同于将如下调用纳入同一原子操作:

off_t orig;
orig = lseek(fd, , SEEK_CUR); // Save current offset
lseek(fd, offset, SEEK_SET);
s = read(fd, buf, len);
lseek(fd, orig, SEEK_SET); // Restore original file offset

对pread()和pwrite()而言,fd所指代的文件必须是可定位的(即允许对文件描述符执行lseek()调用)。

多线程应用为这些系统调用提供了用武之地。进程下辖的所有线程将共享同一文件描述符表。这也意味着每个已打开文件的文件偏移量为所有线程所共享。当调用pread()和pwrite()时,多个线程可同时对同一个文件描述符执行I/O操作,且不会因其他线程修改文件偏移量而受到影响。如果还试图使用lseek()和read()/write()来代替pread()/pwrite(),那么将引发竞争状态。这类似于O_APPEND标志时的描述(当多个进程的文件描述符指向相同的打开文件句柄时,使用pread()和pwrite()系统调用同样能够避免进程间出现竞争状态)。

如果需要反复执行lseek(),并伴之以文件I/O,那么pread()和pwrite()系统调用在某些情况下是具有性能优势的。这是因为执行单个pread()或pwrite()系统调用的成本要低于执行lseek()和read()/write()两个系统调用。然而,较之于执行I/O实际所需的时间,系统调用的开销就有些相形见绌了(执行实际I/O的开销要远大于执行系统调用,系统调用的性能优势作用有限)。

关于文件I/o的原子操作的更多相关文章

  1. [03]APUE:文件 I/O

    [a] open #include <fcntl.h> int open(const char *path, int oflag, ... ,mode_t mode) 成功返回文件描术符, ...

  2. c++ 原子操作

    转载自: http://blog.csdn.net/yockie/article/details/8838686 所谓的原子操作,取的就是“原子是最小的.不可分割的最小个体”的意义,它表示在多个线程访 ...

  3. Linux系统学习笔记:文件I/O

    Linux支持C语言中的标准I/O函数,同时它还提供了一套SUS标准的I/O库函数.和标准I/O不同,UNIX的I/O函数是不带缓冲的,即每个读写都调用内核中的一个系统调用.本篇总结UNIX的I/O并 ...

  4. UNIX高级环境编程(2)FIle I/O - 原子操作、共享文件描述符和I/O控制函数

    引言: 本篇通过对open函数的讨论,引入原子操作,多进程通信(共享文件描述符)和内核相关的数据结构. 还会讨论集中常见的文件IO控制函数,包括: dup和dup2 sync,fsync和fdatas ...

  5. C++ ------ 互斥锁、原子操作的性能测试

    atomic原子操作:是在新标准C++11,引入了原子操作的概念,并通过这个新的头文件提供了多种原子操作数据类型,例如,atomic_bool,atomic_int等等 测试程序 #include & ...

  6. linux_api之文件操作

    本篇索引: 1.引言 2.文件描述符 3.open函数 4.close函数 5.read函数 6.write函数 7.lseek函数 8.i/o效率问题 9.内核用以维护打开文件的相关数据结构 10. ...

  7. C++11中的原子操作(atomic operation)

    所谓的原子操作,取的就是“原子是最小的.不可分割的最小个体”的意义,它表示在多个线程访问同一个全局资源的时候,能够确保所有其他的线程都不在同一时间内访问相同的资源.也就是他确保了在同一时刻只有唯一的线 ...

  8. C++11中的原子操作(atomic operation)(转)

    所谓的原子操作,取的就是“原子是最小的.不可分割的最小个体”的意义,它表示在多个线程访问同一个全局资源的时候,能够确保所有其他的线程都不在同一时间内访问相同的资源.也就是他确保了在同一时刻只有唯一的线 ...

  9. 《Linux内核设计与实现》读书笔记(十)- 内核同步方法【转】

    转自:http://www.cnblogs.com/wang_yb/archive/2013/05/01/3052865.html 内核中提供了多种方法来防止竞争条件,理解了这些方法的使用场景有助于我 ...

随机推荐

  1. Windows PowerShell 入門(6)-関数編1

    この連載では.Microsoftが提供している新しいシェル.Windows Power Shellの使い方を解説します.今回は.関数の作成基礎と引数.戻り値.Switchパラメータについて説明します. ...

  2. 设计模式C++学习笔记之八(Adapter适配器模式)

      适配器模式,使用之处比较特殊,不属于常规设计模式,主要用于不同系统之间的处理.是将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工 ...

  3. $Django cbv源码分析 djangorestframework框架之APIView源码分析

    1 CBV的源码分析 #视图 class login (View): pass #路由 url(r'^books/$', views.login.as_view()) #阅读源码: #左侧工程栏--- ...

  4. 使用Vagrant搭建本地python开发环境

    使用Vagrant搭建本地python开发环境 关于vagrant:Vagrant是一个基于Ruby的工具,用于创建和部署虚拟化开发环境,它使用Oracle的开源VirtualBox虚拟化系统也可以使 ...

  5. MySQL按字段排序后取序号

    1 前言 项目中排行榜刚好需要查数据库表然后给出编号,方案一,可以按条件查找出来,然后再按数组序号给编号,但是如果要查表出来直接看,就不太够用了:方案二,就是用代码帮忙编号.参考了网上一些代码,然后发 ...

  6. git 创建分支

  7. 在XIB 或者 SB 上面 实现 半透明的背景上添加UILabel是文字不透明

    设透明背景的时候直接设置RGB值  然后设置Opacity即可

  8. Centos 7.3下 Linux For SQL Server安装及配置介绍

    Centos 7.3下 Linux For SQL Server安装及配置介绍 高文龙关注13人评论2828人阅读2017-03-05 21:46:21 Centos 7.3下Linux For SQ ...

  9. Confluence 6 管理协同编辑 - 代理和 SSL 的考虑

    对于你如何连接  Synchrony 是与你的环境有关的.我们知道绝大部分的 Confluence 站点是运行在反向代理后面的,同时还使用了 SSL.这里是帮助你在你环境中识别正确的配置的一些信息和一 ...

  10. Confluence 6 手动运行和修改

    手动运行一个任务 希望手动运行一个计划任务,进入计划任务的列表中,找到你希望手动运行的计划任务,在这个计划任务的边上选择 运行(Run).这个计划任务将会马上执行. 不是所有的计划任务都可以手动运行的 ...