【摘自《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. python3+requests库框架设计05-unittest单元测试框架

    unittest单元测试框架,主要由四部分组成:测试固件.测试用例.测试套件.测试执行器 测试固件(test fixture) 测试固件有两部分,执行测试前的准备部分setUp(),测试执行完后的清扫 ...

  2. Unity3D Shader 入门

    什么是Shader Shader(着色器)是一段能够针对3D对象进行操作.并被GPU所执行的程序,它负责将输入的Mesh(网格)以指定的方式和输入的贴图或者颜色等组合作用,然后输出.绘图单元可以依据这 ...

  3. MySQL基本语句、存储引擎

    数据库服务器中存放的是 库(文件加) 表(文件) 表里面是记录(一行数据)quit or  exit 退出客户端\s \c  \G 库(文件)    创建        create database ...

  4. webservice:com.sun.xml.internal.ws.server.ServerRtException: [failed to localize]

    发布webservice发生了错误,一直没有能够解决,错误如下: Exception in thread "main" com.sun.xml.internal.ws.server ...

  5. Find Lines

    (Uva 6955可以直接随机,湖大OJ 13348 要优化) 题意:给出 n个点的坐标, 一个 百分数p, 求是否有一条直线上有 n * p /100个点… 随机化算法,但也要优化下……(TLE, ...

  6. Laravel 5.2--如何让表单提交错误,不清空?

    控制器 public function store(Request $request) { $validator = Validator::make($request->all(), [ 'Su ...

  7. IP保留地址

    保留地址的网络只能在内部进行通信,而不能与其他网络互连.因为本网络中的保留地址同样也可能被其它网络使用,如果进行网络互连,那么寻找路由时就会因为地址的不唯一而出现问题. 但是这些使用保留地址的网络可以 ...

  8. pip的常用命令

    前言 pip作为Python的御用包管理工具有着强大的功能,但是许多命令需要我们使用的时候借助搜索引擎查找(尤其是我), 于是我想将我使用到的命令整合下来,以后不用麻烦去找了,也希望能给你带来帮助.文 ...

  9. Confluence 6 编辑一个站点装饰文件

    希望编辑一个站点的 decorator 文件: 进入  > 基本配置(General Configuration) > 布局(Layouts )(在Look and Feel 菜单下面) ...

  10. Swift DispatchQueue

    延迟2s执行 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2)