一、疑问
1、当多个进程同时打开同一个文件,同时对同一个文件写操作,文件的内容是会被覆盖只有一份还是不会覆盖会有两份内容?
二、问题验证
1、来点代码
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <sys/time.h>
int main(int argc, char *argv[])
{
int rc = 0;
int wc = 0;
int count = 0;
int fd = 0;
int len = 0;
char str[80] = {0};
struct timeval val;
printf("process is=%d\n", (int)getpid());
#ifdef OUT
fd = open("./test.txt", O_RDWR | O_CREAT);
#endif
rc = fork();
switch (rc) {
case -1:
printf("fork error.\n");
exit(1);
case 0:
#ifdef IN
fd = open("./test.txt", O_RDWR | O_CREAT);
#endif
printf("child process is=%d, fd=%d\n", (int)getpid(), fd);
while (count++ < 3) {
memset(str, 0x00, (sizeof(str[0])/sizeof(str)));
memset(&val, 0x00, sizeof(struct timeval));
gettimeofday(&val, NULL);
len = lseek(fd, 0, SEEK_CUR);
sprintf(str, "s=%ld, us=%ld, xcount=%d, seek=%d\n", val.tv_sec, val.tv_usec, count, len);
write(fd, str, strlen(str) + 1);
printf("%s", str);
sleep(1);
}
close(fd);
#ifdef IN
close(fd);
#endif
exit(EXIT_SUCCESS);
default:
#ifdef IN
fd = open("./test.txt", O_RDWR | O_CREAT);
#endif
while (count++ < 5) {
memset(str, 0x00, (sizeof(str[0])/sizeof(str)));
memset(&val, 0x00, sizeof(struct timeval));
gettimeofday(&val, NULL);
len = lseek(fd, 0, SEEK_CUR);
sprintf(str, "s=%ld, us=%ld, ycount=%d, seek=%d\n", val.tv_sec, val.tv_usec, count, len);
write(fd, str, strlen(str) + 1);
printf("%s", str);
sleep(1);
}
wc = wait(NULL);
printf("parent process is=%d, rc=%d, wc=%d, cnt=%d, fd=%d\n", (int)getpid(), rc, wc, count, fd);
#ifdef IN
close(fd);
#endif
exit(EXIT_SUCCESS);
}
#ifdef OUT
close(fd);
#endif
return 0;
}
gcc -DIN fork_fd.c -o fork_fdi
gcc -DOUT fork_fd.c -o fork_fdo
2、运行结果
第一个打印情况
第二个打印情况
三、问题分析
1、基础知识
1.1、大家都知道,open操作后最终会返回一个整型的文件描述符fd(一般都是从3开始)。进入task_struct中用来描述文件相关的结构体,继续往下找发现描述文件的结构体struct file,其中具体的struct file由文件描述符fd获取到。从而在struct file中就可以知道文件的存放路径f_path,文件的编号f_inode,文件当前的读写偏移量……
struct task_struct {
/* Open file information: */
......
struct files_struct *files;
};
struct files_struct {
......
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
......
};
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; //VFS描述文件信息的节点,还不是指向具体文件系统文件的inode
const struct file_operations *f_op; //文件的操作函数,对应着驱动的实现
/*
* Protects f_ep, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
enum rw_hint f_write_hint;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos; //文件读写位置的偏移量
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
}
2、具体原因
第一种情况,fork后的父子进程使用的是不同的fd文件,因此使用的是两份的文件表项,两个独立的位置点f_pos
第二种情况,fork后的父子进程共用fd文件,父进程和子进程打开相同的文件时,共享同一个文件表项,使用同一个位置点f_pos,当A进程写结束后,B进程写的位置点是A结束后的位置点。类似于dup系统调用,dup结束后fd会不一样,但是父子进程的fd是一样的,但是内核态共用的是同一份文件表项