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

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

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

以独占方式创建一个文件

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

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

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

  1. fd = open(argv[], O_WRONLY); /* Open 1: check if file exists */
  2. if (fd != -) { /* Open succeeded */
  3. printf("[PID %ld] File \"%s\" already exists\n",
  4. (long) getpid(), argv[]);
  5. close(fd);
  6. } else {
  7. if (errno != ENOENT) { /* Failed for unexpected reason */
  8. errExit("open");
  9. } else {
  10. printf("[PID %ld] File \"%s\" doesn't exist yet\n",
  11. (long) getpid(), argv[]);
  12. if (argc > ) { /* Delay between check and create */
  13. sleep(); /* Suspend execution for 5 seconds */
  14. printf("[PID %ld] Done sleeping\n", (long) getpid());
  15. }
  16. fd = open(argv[], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
  17. if (fd == -)
  18. errExit("open");
  19.  
  20. printf("[PID %ld] Created file \"%s\" exclusively\n",
  21. (long) getpid(), argv[]); /* MAY NOT BE TRUE! */
  22. }
  23. }

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

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

向文件尾部追加数据

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

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

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

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

多线程中的应用

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

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

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

  1. off_t orig;
  2. orig = lseek(fd, , SEEK_CUR); // Save current offset
  3. lseek(fd, offset, SEEK_SET);
  4. s = read(fd, buf, len);
  5. 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. nodejs+express+mongodb简单的例子

    简单的介绍下node+express+mongodb这三个东西.node:是运行在服务器端的程序语言,表面上看过去就是javascript一样的东西,但是呢,确实就是服务器语言,个人觉得在一定层次上比 ...

  2. boost.asio包装类st_asio_wrapper开发教程(2013.12.8更新)(二)

    如果你是偶然浏览到这里,请先看 源代码及例程下载地址:命令行:svn checkout http://st-asio-wrapper.googlecode.com/svn/trunk/ st-asio ...

  3. Unity3D之IOS&Android收集Log文件

    开发项目的时候尤其在处理与服务器交互这块,如果服务端程序看不到客户端请求的Log信息,那么无法修改BUG.在Windows上Unity会自动讲Log文件写入本地,但是在IOS和Android上确没有这 ...

  4. ansible笔记(4):常用模块之文件操作

    前文中,我们已经介绍了怎样使用模块,而且我们知道,ansible有很多模块,每个模块都有自己的功能,"模块"涉及到的方向比较多,所以对于个人来说,并没有必要了解所有的模块,我们只需 ...

  5. Linux mem/swap/buffers/cached 区别

    Free free 命令相对于top 提供了更简洁的查看系统内存使用情况: $ free total used free shared buffers cached Mem: 255268 23833 ...

  6. Ubuntu选择软件源

    1. 系统桌面右上角,系统菜单中选择Software Up to Date 2. 点击Settings-按钮 3. Ubuntu Software标签页,Download from选择中国的软件服务器 ...

  7. Jmeter之响应结果乱码解决

    场景: 在测试过程中,我们可能需要查看结果树,但是发现里面的响应数据在“Document”以外的其他表现形式下都有乱码,如下图就是设置了以Text的形式展示,响应数据包含乱码: 分析:原因是Jmete ...

  8. CentOS 7安装Python3.5

    CentOS 7下安装Python3.5 •安装python3.5可能使用的依赖 yum install openssl-devel bzip2-devel expat-devel gdbm-deve ...

  9. Servet

    一.Servlet 是单例吗 不是. 1.你可以用多个 URL 映射同一个 Servlet.这样就会出现多个实例. 2.看看 Servlet 定义: 引用 For a servlet not host ...

  10. Confluence 6 从你的 JDBC 连接中直接启用校验查询

    确定 Confluence 在数据库连接池中校验数据库连接: 停止 Confluence. 编辑 <home-directory>confluence.cfg.xml 文件. 针对你特定的 ...