是unix原子的写操作吗?

时间:2018-05-31 12:07:04

标签: c unix posix atomic

我正在阅读 APUE (UNIX环境中的高级编程),当我看到$ 3.11时会遇到这个问题:

if (lseek(fd, 0L, 2) < 0) /* position to EOF */
err_sys("lseek error");
if (write(fd, buf, 100) != 100) /* and write */
err_sys("write error")

APUE说:

  

这适用于单个进程,但如果多个进程使用此技术附加到同一文件,则会出现问题。 .......这里的问题是我们的逻辑运算''位置到文件末尾和   write''需要两个单独的函数调用(如我们所示)。 任何需要多个函数调用的操作都不能是原子操作,因为内核可能会暂时挂起两个函数调用之间的进程。

它只是说cpu会在lseekwrite之间的函数调用之间切换,我想知道它是否也会切换一半write操作?或者更确切地说,是write原子?如果threadA写"aaaaa",则threadB写"bbbbb",结果是"aabbbbbaaa"吗?

更重要的是,在APUE说preadpwrite都是原子操作之后,这是否意味着这些函数在内部使用mutexlock是原子的?

2 个答案:

答案 0 :(得分:3)

调用Posix语义“原子”可能过于简单了。 Posix要求读取和写入按某种顺序发生:

  

写入可以针对其他读取和写入进行序列化。如果在read()数据之后可以证明write()文件数据(通过任何方式),它必须反映write(),即使调用是由不同的进程完成的。类似的要求适用于对同一文件位置的多次写入操作。这需要保证将write()次呼叫的数据传播到后续read()次呼叫。 (来自Posix specification for pwrite and write Rationale 部分)

APUE中提到的原子性保证是指使用O_APPEND标志,强制写入在文件末尾执行:

  

如果设置了文件状态标志的O_APPEND标志,则应在每次写入之前将文件偏移设置为文件的末尾,并且在更改文件偏移量和文件偏移量之间不应进行中间文件修改操作。写操作。

关于preadpwrite,APUE说(正确地,当然)这些接口允许应用程序以原子方式寻找和执行I / O;换句话说,无论其他任何进程做什么,I / O操作都将发生在指定的文件位置。 (因为位置是在调用本身中指定的,并且不会影响持久文件位置。)

Posix排序保证如下(来自write()pwrite()函数的描述):

  

成功返回常规文件的write()之后:

     
      
  • 文件中由该写入修改的每个字节位置的任何成功read()都应返回该位置的write()指定的数据,直到再次修改这些字节位置为止。

  •   
  • 文件中相同字节位置的任何后续成功write()都将覆盖该文件数据。

  •   

正如基本原理中所提到的,这个措辞确保两个同时write调用(即使在不同的不相关进程中)不会交错数据,因为如果数据在写入期间交错,最终将成功,第二个保证将无法提供。如何实现这取决于实施。

必须注意的是,并非所有文件系统都符合Posix,并且模块化OS设计允许多个文件系统在单个安装中共存,这使得内核本身无法提供适用于write的{​​{1}}保证。所有可用的文件系统。网络文件系统特别容易发生数据竞争(并且本地互斥体也不会有太大帮助),正如Posix所提到的那样(在基本原理引用的段落末尾):

  

此要求对于网络文件系统尤为重要,因为某些缓存方案违反了这些语义。

第一个保证(关于后续读取)需要在文件系统中进行一些簿记,因为已成功“写入”内核缓冲区但尚未同步到磁盘的数据必须透明地可用于从该文件读取的进程。这还需要内核元数据的一些内部锁定。

由于写入常规文件通常是通过内核缓冲区完成的,并且实际上将数据同步到物理存储设备肯定是原子,提供这些保证所必需的锁不必非常持久的。但它们必须在文件系统内部完成,因为Posix措辞中没有任何内容限制了单线程进程中同时写入的保证。

在单线程进程中,Posix确实需要read()write()pread()pwrite()在常规文件(或符号链接)上运行时才是原子的。有关必须遵守此要求的接口的完整列表,请参阅Thread Interactions with Regular File Operations

答案 1 :(得分:-2)

在Linux中,存在阻塞和非阻塞系统调用。 write是阻塞系统调用的示例,这意味着执行线程将被阻塞,直到write完成。因此,一旦用户进程调用write,在系统调用完成之前,它就无法执行任何其他操作。因此,从用户线程的角度来看,它将表现得像原子一样[尽管在内核级别很多事情都可能发生,并且系统调用的内核执行可能会被多次中断]。