基于该博客的结论进行实验验证
首先需要各位前去看链接中的博客,了解里面的结论和知识点再来看我的实验验证。
write调用能保证的是,不管它实际写入了多少数据,比如写入了n字节数据,在写入这n字节数据的时候,在所有共享文件描述符的线程或者进程之间,每一个write调用是原子的,不可打断的。举一个例子,比如线程1写入了3个字符’a’,线程2写入了3个字符’b’,结果一定是‘aaabbb’或者是‘bbbaaa’,不可能是类似‘abaabb’这类交错的情况。
也许你自然而然会问一个问题,如果两个进程没有共享文件描述符呢?比如进程A和进程B分别独立地打开了一个文件,进程A写入3个字符’a’,进程B写入了3个字符’b’,结果怎样呢?
答案是,这种情况下没有任何保证,最终的结果可能是‘aaabbb’或者是‘bbbaaa’,也可能是‘abaabb’这种交错的情况。如果你希望不交错,那么怎么办呢?答案也是有的,那就是在所有写进程打开文件的时候,采用O_APPEND方式打开即可。
我们针对博客中的这些结论进行实验验证
实验环境 :
- 验证思路
- 用fork()创建进程共享打开的文件描述符写入数据进行实验;
- 用fork()分别再父子进程中打开文件描述符写入数据进行实验。
- 该程序共享文件描述符
#include<iostream>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<string.h>
int main()
{
int fd = open("test", O_RDWR, 0666);
pid_t pid = fork();
if(pid == 0)
{
//int fd = open("test", O_RDWR | O_APPEND, 0666);
char buffer[200];
memset(buffer, 'a', 200);
write(fd, buffer, 200);
exit(0);
}
if(pid > 0)
{
//int fd = open("test", O_RDWR | O_APPEND, 0666);
if(fd > 0)
std::cout << "parent open file success\n";
char buffer[16];
memset(buffer, '=', 16);
write(fd,buffer, 16);
}
std::cout << "写入完成\n";
int ret;
wait(&ret);
}
实验结果如下:
多次实验得出结论 ,查看test文件的字节数,发现是216和我们写入的字节数相同,并且数据没有混乱。共享文件描述符保证原子序,但是不保证写入的顺序性,这个由操作系统调度进程顺序决定。
- 不共享文件描述符进行实验
#include<iostream>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<string.h>
int main()
{
//int fd = open("test", O_RDWR, 0666);
pid_t pid = fork();
if(pid == 0)
{
int fd = open("test", O_RDWR, 0666);
char buffer[200];
memset(buffer, 'a', 200);
write(fd, buffer, 200);
exit(0);
}
if(pid > 0)
{
int fd = open("test", O_RDWR, 0666);
if(fd > 0)
std::cout << "parent open file success\n";
char buffer[16];
memset(buffer, '=', 16);
write(fd,buffer, 16);
}
std::cout << "写入完成\n";
int ret;
wait(&ret);
}
实验结果如下:
多次试验结果得出结论,不共享文件描述符,写入的数据可能会被覆盖也可能会交错,查看test文件发现只有200个字节被写入文件中,所以在写进程中打开文件要加上O_APPEND,避免出现上述错误
下图实验结果为每个进程打开文件加上O_APPEND标志位,结果如图所示
可以看出结果和共享文件描述符相同,保证写入的数据正确性。
- 多线程程序实验
实验思路:
共享文件描述符: 主线程创建文件描述符通过参数传递给线程函数实现共享,线程函数向该fd中写入数据
非共享的文件描述符:线程函数中分别打开fd,向其写入数据进行实验
共享文件描述符实验代码
#include<iostream>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>
#include<stdio.h>
#include<fcntl.h>
#include<sys/unistd.h>
#include<thread>
#include<string.h>
void* func(int fd)
{
/*
int fd = open("test1", O_RDWR, 0666);
if(fd > 0)
std::cout << "func fd create success\n";
*/
char buffer[100];
memset(buffer, 'a', 20);
write(fd, buffer, 20);
}
void* func2(int fd)
{
/*
int fd = open("test1", O_RDWR, 0666);
if(fd > 0)
std::cout << "func2 fd create success\n";
*/
char buffer[100];
memset(buffer, '=', 30);
write(fd, buffer, 30);
}
int main()
{
int fd = open("test1", O_RDWR, 0666);
std::thread thread1(func, fd);
std::thread thread2(func2, fd);
std::thread thread3(func, fd);
std::thread thread4(func2, fd);
std::thread thread5(func, fd);
std::thread thread6(func2, fd);
thread1.join();
thread2.join();
thread3.join();
thread4.join();
thread5.join();
thread6.join();
getchar();
return 0;
}
实验结果如图所示:
发现写入数据没有混乱并且写入了150个字节的数据,和我们写入的数量相同。
非共享文件描述符实验代码
#include<iostream>
#include<sys/types.h>
#include<unistd.h>
#include<pthread.h>
#include<stdio.h>
#include<fcntl.h>
#include<sys/unistd.h>
#include<thread>
#include<string.h>
void* func()
{
int fd = open("test1", O_RDWR, 0666);
if(fd > 0)
std::cout << "func fd create success\n";
char buffer[100];
memset(buffer, 'a', 20);
write(fd, buffer, 20);
}
void* func2()
{
int fd = open("test1", O_RDWR, 0666);
if(fd > 0)
std::cout << "func2 fd create success\n";
char buffer[100];
memset(buffer, '=', 30);
write(fd, buffer, 30);
}
int main()
{
std::thread thread1(func);
std::thread thread2(func2);
std::thread thread3(func);
std::thread thread4(func2);
std::thread thread5(func);
std::thread thread6(func2);
thread1.join();
thread2.join();
thread3.join();
thread4.join();
thread5.join();
thread6.join();
getchar();
return 0;
}
实验结果如图所示:
实际应该写入的数据长度为150个字节,而现在只有30个字节,数据写入混乱。
非共享文件描述符加入O_APPEND参数打开,试验结果如下图所示:
发现和共享描述符的写入结果相同,文章开头的结论得以验证!
最近又查阅了书籍,发现在Linux系统编程这本书上对这个现象进行了解释。
内核维护了三个数据结构
- 进程级的文件描述符
- 系统级的打开文件表
- 文件系统的Inode表
1、 首先内核对所有的打开的文件维护有一个系统级的描述表格。有时也称之为打开文件表,并将表中各条目称为打开的文件句柄。每个打开的文件句柄存储了与一个打开文件相关的全部信息。如下所示
- 当前的文件偏移量(这个就是对我们的实验产生影响的东西)
- 打开文件时所使用的状态标志
- 文件的访问模式
- 对该文件的I-node对象的引用
上述实验中,非共享描述符为什么会写入数据混乱呢?
-
因为每个open调用都会打开一次文件,所返回的文件描述符是不同的,他们拥有不同的文件句柄,也就不共享文件偏移量,所以会导致写入数据混乱。
-
而共享的文件描述符指向相同的文件句柄,共享文件偏移量,加入了O_APPEND参数后,不会写入数据混乱。
-
APPEND模式提供了文件层面的全局写安全,而非APPEND模式则提供了针对共享file结构体的进程/线程之间的写安全。
本篇博客仅为本人实验验证后的结论,如有错误请指出,感谢!
参考文献:
谈谈Linux IO 文章来自阿里学长
write及O_APPEND参数 来自CSDN博主