意外发现,pwrite() 函数是一个原子操作,现测试验证如下:
1 /*
2 pwrite相当于先调用lseek接着调用write。但又不完全是这样:
3 (1)pwrite是原子操作,定位和写操作在一个原子操作中完成,期间不可中断。但分开的lseek和write中间可能被其他程序中断。
4 (2)pwrite不更改当前文件的指针,也就是说不改变当前文件偏移量。
5 (3)pwrite中的offset是一个绝对量,相对于文件开始处的绝对量,与当前文件指针位置无关。
6
7 如下实例:
8
9 */
10 #include <fcntl.h>
11 #include <unistd.h>
12 #include <stdio.h>
13 #include <errno.h>
14
15 int main(void)
16 {
17 char pathName[] = {"/home/zll/201907-12/test"};
18 int fd = -1;
19 unsigned int offsetLen = 0;
20 unsigned int writeLen;
21 char bufData[] = {"0123456789asdfgh"};
22 int ret;
23
24 fd = open(pathName, O_RDWR | O_CREAT);
25 if (fd < 0) {
26 printf("open error: %s.\n", strerror(errno));
27 return -1;
28 }
29
30 offsetLen = lseek(fd, 0, SEEK_SET);
31 if (offsetLen < 0) {
32 //printf("lseek error: %d.\n", perror(errno));
33 perror("lseek error.\n");
34 return -1;
35 }
36
37 writeLen = 10;
38 ret = write(fd, bufData, writeLen);
39 if (ret != writeLen) {
40 printf("write error: %s.\n", strerror(errno));
41 return -1;
42 }
43 // pwrite() 操作写文件时,只会按照偏移来写,不会影响当前的文件描述符位置;
44 // (从文件描述符开头偏移两个字节处,写入从(&bufData[10])开始的4个字符)
45 // 两个write() 之间增加pwrite(),丝毫未影响write操作的文件描述符位置
46 writeLen = 4;
47 ret = pwrite(fd, bufData + 10, writeLen, 2);
48 if (ret != writeLen) {
49 printf("pwrite error: %s.\n", strerror(errno));
50 return -1;
51 }
52
53 writeLen = 6;
54 ret = write(fd, bufData + 10, writeLen);
55 if (ret != writeLen) {
56 printf("write error: %s.\n", strerror(errno));
57 return -1;
58 }
59
60 close(fd);
61 return 0;
62 }
63
a. 第一次write() 写入10个字符:0——9
b. 第二次pwrite() 从文件头的第2个字符开始写入从&bufData[10] 开始的4个字母,即:asdf
c. 第三次write() 写入bufData的最后6个字符,即:asdfgh
如上代码执行结果:
从上结果可知:同一个进程中,write() 写操作时,其系统内部会自动统计文件描述符的当前位置,pwrite() 操作是不会影响write() 操作的文件描述符指针的;
pwrite 操作,其写入位置是一个相对于文件头的偏移量,是一个相对的偏移地址。
————————————————————————————————————————————————————————
如果带有O_APPEND标志的情况,在文件真正写入之前会调用generic_write_checks进行一些检查,在检查的时候如果发现带有O_APPEND标志就将offset设置为文件的大小。而这整个过程都是在加锁的情况下完成的,所以带有O_APPEND标志的情况下,文件的写入是原子的,多线程写文件是不会导致数据错乱的。
另外一种情况就是pwrite系统调用,pwrite系统调用通过让用户指定写入的offset,值得整个写入的过程天然的变成原子的了,在上文说到,整个写入的过程是因为获取offset和文件写入是两个独立的步骤,并没有加锁,通过pwrite省去了获取offset这一步,最终整个文件写入只有一步加锁的文件写入过程了。
最后一个问题是多个进程写同一个文件是否会造成文件写错乱,直观来说是多进程写文件不是原子的,这是很显而易见的,因为每个进程都拥有一个struct file对象,是独立的,并且都拥有独立的文件offset,所以很显然这会导致上文中说到的数据覆盖的情况,但是否会导致数据错乱呢?,答案是不会,虽然struct file对象是独立的,但是struct inode是共享的(相同的文件无论打开多少次都只有一个struct inode对象),文件的最后写入其实是先要写入到页缓存中,而页缓存和struct inode是一一对应的关系,在实际文件写入之前会加锁,而这个锁就是属于struct inode对象(见上文中的mutex_lock(&inode->i_mutex))的,所有无论有多少个进程或者线程,只要是对同一个文件写数据,拿到的都是同一把锁,是线程安全的,所以也不会出现数据写错乱的情况。
---------------------
作者:zhangyifei216
来源:CSDN
原文:https://blog.csdn.net/zhangyifei216/article/details/76653746
版权声明:本文为博主原创文章,转载请附上博文链接!