【摘自《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. FHQ Treap摘要

    原理 以随机数维护平衡,使树高期望为logn级别 不依靠旋转,只有两个核心操作merge(合并)和split(拆分) 因此可持久化 先介绍变量 ; int n; struct Node { int v ...

  2. 029_shell编写工作常用工具类总结

    一.检查命令的执行结果 function check_result() { result=$? flag=$1 if [[ "$result"x == "0"x ...

  3. 003_crlf注入漏洞

    一. (1) 线上收到一个crlf 注入的漏洞. 同时启用80和443才会暴露,配置如下: server { listen 80; listen 443 ssl; server_name www.jy ...

  4. MySQL--表操作(innodb表字段数据类型、约束条件)、sql_mode操作

    一.创建表的完整语法 #[]内的可有可无,即创建表时字段名和类型是必须填写的,宽度与约束条件是可选择填写的.create table 表名(字段名1 类型[(宽度) 约束条件],字段名2 类型[(宽度 ...

  5. SpringCloud的版本

    Spring Cloud 项目目前仍然是快速迭代期,版本变化很快.这里整理一下版本相关的东西,备忘一下. 大版本 版本号规则 Spring Cloud并没有熟悉的数字版本号,而是对应一个开发代号. C ...

  6. nginx负载均衡后端tomcat无法加载js资源

    JS或css无法完全加载 nginx的代理缓存区,默认较小导致部分文件出现加载不全的问题,比较典型的如jQuery框架,可以通过配置调整nginx的缓存区即可.主要参考proxy参数 最终完整配置如下 ...

  7. AMD-requireJS

    require.js是AMD的一种实现形式. 加载: <script src="require.js" data-main="main"></ ...

  8. js的页面传值cookie.session

    jquery的cookie: 读取所有的cookie: document.cookie: 读取单个cookie:$.cookie('cookiename'); 新增cookie: $.cookie(' ...

  9. sleep()和wait()的区别及wait方法的一点注意事项

    一.查看API sleep是Thread类的方法,导致此线程暂停执行指定时间,给其他线程执行机会,但是依然保持着监控状态,过了指定时间会自动恢复,调用sleep方法不会释放锁对象. 当调用sleep方 ...

  10. Confluence 6 禁用或者重新启用一个任务

    在默认的情况下,所有的 Confluence 计划任务都是默认启用的. 使用 启用(Disable )/ 禁用(Enable )连接操作来启用和禁用每一个计划任务. 不是所有的加护任务都可以被禁用的. ...