【摘自《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. 新版本微信导致的ios表单bug

    解决方法如下: $(document).delegate('input, textarea, select', 'blur', function(){ setTimeout(function(){ $ ...

  2. Efuse--芯片存储

    1.Efuse是什么 Efuse类似于EEPROM,是一次性可编程存储器,在芯片出场之前会被写入信息,在一个芯片中,efuse的容量通常很小,一些芯片efuse只有128bit. 2.efuse的作用 ...

  3. Mysql支持哪几种索引

    从数据结构角度 1.B+树索引(O(log(n))):关于B+树索引,可以参考 MySQL索引背后的数据结构及算法原理 2.hash索引:a 仅仅能满足"=","IN&q ...

  4. ansible笔记(12):handlers的用法

    ansible笔记():handlers的用法 这篇文章会介绍playbook中handlers的用法. 在开始介绍之前,我们先来描述一个工作场景: 当我们修改了某些程序的配置文件以后,有可能需要重启 ...

  5. 开启gtid导入报错

    导入报错 [root@redis02 data]# mysql -u root -p < ht.sqlEnter password: ERROR 1840 (HY000) at line 24: ...

  6. SSH localhost免密不成功 + 集群状态显示Configured Capacity: 0 (0 KB)

    前一天运行hadoop一切安好,今天重新运行出现BUG.下面对遇到的bug.产生原因以及解决方法进行一下简单总结记录. [bug1]用ssh localhost免密登录时提示要输入密码. 原因分析:之 ...

  7. 4)django-视图view

    视图是django功能函数,结合url使用 1.视图方式 视图方式经常用的有两种 用户GET获取数据      用户POST提交数据            用户第一次访问页面是GET       用户 ...

  8. Url解码和编码 escape()、encodeURI()、encodeURIComponent()区别详解

    Server.UrlDecode;解码 Server.UrlEncode;编码 url编码是一种浏览器用来打包表单输入的格式.浏览器从表单中获取所有的name和其中的值 ,将它们以name/value ...

  9. 金九银十中,看看这31道Android面试题

    阅读目录 1.如何对 Android 应用进行性能分析 2.什么情况下会导致内存泄露 3.如何避免 OOM 异常 4.Android 中如何捕获未捕获的异常 5.ANR 是什么?怎样避免和解决 ANR ...

  10. Django Admin的相关知识

    一.面向对象复习 1.类的继承 class Base(object): def __init__(self,val): self.val = val def func(self): self.test ...