Chinaunix首页 | 论坛 | 博客
  • 博客访问: 40773
  • 博文数量: 29
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 285
  • 用 户 组: 普通用户
  • 注册时间: 2014-12-08 13:03
个人简介

海纳百川有容乃大,壁立千仞无欲则刚。

文章分类
文章存档

2015年(17)

2014年(12)

我的朋友

分类: LINUX

2015-03-05 18:01:39

APUE中说明 O_APPEND是原子性的。也就是,lseek和write是不可分割的。但是write本身是否是原子性的呢?书上并没有说,但是以下几点是可以确定的:
1. 如果未使用 O_NONBLOCK 打开描述符,那么write是阻塞的。
2. write写磁盘文件虽然会短时间阻塞,但是不会被信号中断

至于,一个进程在执行write的过程中是否会被其他进程的write打断,而发生“混合”输出的情况,书上没有说明。我自己使用以下代码,对一个大小为10M的全是“1”的temp1文件和对一个大小为10M的全是“2”的temp2文件进行多进程同时写的测试。测试结果并没有出现一个进程的write被另一个进程write打断的现象。所以,也不能说明什么问题。

  1. #include <stdio.h>
  2. #include <fcntl.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #define SIZE 1024000
  6. int main(void)
  7. {
  8.     int n;
  9.     char buf[SIZE];
  10.     pid_t pid;
  11.     int fd,fd1,fd2;
  12.     if ( (fd = open("huen",O_RDWR|O_CREAT|O_EXCL|O_APPEND)) == -1){
  13.         perror("open error");
  14.         return 0;
  15.     }    
  16.     fd1 = open("temp1",O_RDWR);
  17.     fd2 = open("temp2",O_RDWR);
  18.     if((pid = fork()) < 0){
  19.         perror("fork error");
  20.     }else if(pid ==0){
  21.         while((n = read(fd1,buf,SIZE)) > 0){
  22.             int i = 0;
  23.             for(;i<50;i++)
  24.                 write(fd,buf,n);
  25.         }
  26.     }else{
  27.         while((n = read(fd2,buf,SIZE)) > 0){
  28.             int i = 0;
  29.             for(;i<50;i++)
  30.                 write(fd,buf,n);
  31.         }
  32.     }
  33. }
后来,在网上搜索来了一些信息,结论大致都是,只要写的数据不超过一定大小(4096),可以认为write是原子性的,但是如果写的数据过大,那么就依赖write的具体实现了。以下是资料的链接和内容:

1.

Linux命令之write调用的原子性

linuxman手册页中关于write调用的说明很不详细,并未说明写操作是否是原子的,所以我们有必要查找 SingleUNIXSpecification(SUS)对write调用的说明,在SUS中对此调用的说明还是比较详细的。在继续讨论之前我们需要清 楚内核在写文件之前会对该文件加锁,不管是否成功完成写操作,在返回之前都会解锁。

linux命令是对Linux系统进行管理的命令。本文介绍的关于linux命令中write调用的原子性的详细描述,具体内容如下所述。

UNIX环境高级编程中关于原子操作的介绍,其中有一种情形是在文件尾端添加数据。

文中说,如果多个进程都需要将数据添加到某一文件,那么为了保证定位和写数据这两步是一个原子操作,需要在打开文件时设置O_APPEND标志,看 到这里我们就会想,虽然保证了定位和写数据是一个原子操作,但是是否能够保证多个进程或线程写入的数据不会交错呢,比如A进程调用 write(filedes1,"AAA",3),B进程调用write(filedes2,"BBBB",4)(其中filedes1和 filedes2指向同一个文件),但是最后文件中的数据是否有可能是AABBBAB,如果这个文件是一个管道或socket呢。

linuxman手册页中关于write调用的说明很不详细,并未说明写操作是否是原子的,所以我们有必要查找 SingleUNIXSpecification(SUS)对write调用的说明,在SUS中对此调用的说明还是比较详细的。在继续讨论之前我们需要清 楚内核在写文件之前会对该文件加锁,不管是否成功完成写操作,在返回之前都会解锁。

下面我们就以三种常见的文件根据SUS标准来讨论上面提出的这个问题:

1.普通文件

SUS中也没有说明在写普通文件时是否会保证是原子操作,但是它说明了write调用可能并不能完全把我们需要写入的数据写到文件中去,那么什么情况下可能少写数据呢?

SUS说明了两种情况:磁盘已满或则要写入的文件的大小超过了当前进程的文件大小限制。其实至少还有一种情况,那就是内核中的高速缓存不够用的时 候,比如linux内核在发现高速缓存不够用的时候就只写入实际能够容下的数据然后返回。正是由于存在上述最后一种情况,所以说按照APUE那种方法在 linux下面写文件并不能保证我们的数据不会交错(不过我们可以根据write的返回值得知是否有发生交错的可能)。

其它的unix内核可能会在实现上不同于linux内核,他们可能在写之前就判断一下缓冲区是否足够容纳所有数据,如果是这种情况,写操作应该就是 原子的;也可能写了一部分数据后才发现缓冲区不够用并让当前进程进入睡眠状态,此时内核如果解锁,那么在当前进程睡眠期间其它进程可能写了数据,如果不解锁,那么就是原子操作,其他进程不可能在这个时候写入数据。由上面的分析可知,正是由于SUS标准不太完整的标准,我们不能确定一定可以按APUE的方法 来同时向同一个普通文件写数据。

如果我们非要在同一个文件中记录多个进程产生的数据,我们最好采用unix日志系统采用的方法,用一个专用进程处理文件IO,其它进程把需要写的数据发送给这个专用进程,这样应该比多个进程同时写一个文件可靠和高效。

2.管道

SUS对管道的写操作说得更多也更明确,我们只需遵照其标准就可以了。对于write(pipefd,buf,nbyte),其要点如下:

如果nbyte<=PIPE_BUF,不管O_NONBLOCK是否设置,其写操作都是原子的,就是说多个进程都在此条件下同时写同一个管道不会引起数据交错。

如果nbyte>PIPE_BUF,是不能保证写操作是原子的,写入的数据可能与其他进程写入的数据交错。

3.socket

SUS中对于写socket并没有说很多,我们无法从标准中得知write是否保证写操作的原子性。我看了一下linux2.6.14内核关于 tcp数据的写操作,发现它不是原子的,也从网上查到了这部分代码的作者(们)对这个问题的看法,他(们)认为对一个可能永久阻塞的操作保证原子性是错误 的。我们也只能姑且这么认为了。

补充:

对于用UNIX日志系统服务器的方法,连接端必须每个线程connect一次logsvr,这样才能保证发过来的日志数据不互相错乱,保证原子性; 此时logsvr只要用reactor方法来处理每个线程的连接就好,把这些fd放到队列里轮流处理,写文件,也保证了写文件的原子性。

实际上日志服务器一般都是用UDP来完成的。

总结:

希望本文介绍的Linux命令中write调用的原子性的内容能够对读者有所帮助,更多还有待于读者去探索和学习。


--------------------------------------------------------------------------------------------------------------------------------

2.

Q:
In general, what can we take for granted when we append to a file in UNIX from multiple processes? Is it possible to lose data (one process overwriting the other's changes)? Is it possible for data to get mangled? (For example, each process is appending one line per append to a log file, is it possible that two lines get mangled?) If the append is not atomic in the above sense, then what's the best way of ensuring mutual exclusion?

A:

A write that's under the size of 'PIPE_BUF' is supposed to be atomic. That should be at least 512 bytes, though it could easily be larger (linux seems to have it set to 4096).

This assume that you're talking all fully POSIX-compliant components. For instance, this isn't true on NFS.

But assuming you write to a log file you opened in 'O_APPEND' mode and keep your lines (including newline) under 'PIPE_BUF' bytes long, you should be able to have multiple writers to a log file without any corruption issues. Any interrupts will arrive before or after the write, not in the middle. If you want file integrity to survive a reboot you'll also need to call fsync(2) after every write, but that's terrible for performance.

Clarification: read the comments and . I'm not sure that O_APPEND is supposed to have that PIPE_BUF size atomicity. It's entirely possible that it's just how Linux implemented write(), or it may be due to the underlying filesystem's block sizes.


阅读(2458) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~