fork-父子进程读写文件的偏移量(linux4.0.4)

一、背景

本文阐述的问题是:fork进程后父子进程操作文件的偏移量是否相同?

该问题可以用以下代码来展示,如您能知道代码执行后forkfile文件的内容是什么,那么请略过此文。

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

int main()

{

         pid_t pid = 0;

         int fd = -1;

         char buf[4] = { 'a', 'b', 'c', 'd'};

         char buf2[4] = { '1', '2', '3', '4'};

        

         unlink("./forkfile");

         fd = open("./forkfile", O_CREAT|O_RDWR|O_TRUNC);

         if ( -1 == fd) {

                   printf("open ./forkfile failed\n");

                   return -1;

         }

         write(fd, &buf[0], sizeof(buf));

         pid = fork();

         if (pid > 0) {

                   printf("this is parent process, fd=%d\n", fd);

         } else if (0 == pid){

                   printf("this is child process, fd=%d\n", fd);

                   write(fd, &buf2[0], sizeof(buf));

         } else {

                   printf("fork failed\n");

         }

         close(fd);

         return 0;

}

代码执行后,forkfile文件内为:abcd1234,可以看出fork后,父子进程的文件偏移量是相同的。

二、代码分析

1)进程管理文件的数据结构struct files_struct

文件系统模块涉及到struct file、struct dentry、struct inode,三者的关系为

进程用struct file结构来记录打开文件的信息,进程每次打开一个文件,就会用一个struct file结构来记录信息,同一个文件打开多次(没有close)也会得到多个struct file结构。struct file->f_path.dentry、struct file->f_inode、struct dentry->d_inode这些字段是vfs的基本组件,构成了vfs的基本框架,不过不属于本文的描述范围,本文关注struct file->f_pos字段,该字段用于记录被打开的文件的偏移量,read、write操作就是从该偏移量处开始操作文件的。

一个进程会有很多struct file结构,所有的struct file结构构成一个数组,由struct files_struct管理。用户态是看不到这些struct file结构的,只能看到fd,fd就是该数组的索引,内核通过fd在stuct file数组中找到对应的struct file结构,就能知道文件的偏移量信息。struct files_struct定义如下:

struct files_struct {

  /*

   * read mostly part

   */

         atomic_t count;

         struct fdtable __rcu *fdt;

         struct fdtable fdtab;

  /*

   * written part on a separate cache line in SMP

   */

         spinlock_t file_lock ____cacheline_aligned_in_smp;

         int next_fd;

         unsigned long close_on_exec_init[1];

         unsigned long open_fds_init[1];

         struct file __rcu * fd_array[NR_OPEN_DEFAULT];

};

struct files_struct结构分为三部分,第一部分是files_struct->fdt,第二部分是files_struct->fdtab,剩下的字段是第三部分。内核以files_struct->fdt中的信息为准,可以查询到所有fd对应的struct file,files_struct->fdtab及后面的字段是一些辅助字段,用于初始化files_struct->fdt。默认情况下files_struct->fdt = &files_struct->fdtab,files_struct->fdtab中的一些字段用struct files_struct中的第三部分字段初始化。所以核心在于struct fdtable结构,定义如下:

struct fdtable {

         unsigned int max_fds;

         struct file __rcu **fd;      /* current fd array */

         unsigned long *close_on_exec;

         unsigned long *open_fds;

         struct rcu_head rcu;

};

fork进程时,需要把父进程的struct files_struct中的信息复制到子进程,所以fork先分配一块内存作为子进程的struct files_struct,内存示意图如下(只列出了关键字段):

然后通过do_fork-->copy_process-->copy_files-->dup_fd对files_struct->fdtab初始化,初始化后各字段值如下:

此时,子进程申请的struct files_struct与父进程没有任何关系,struct files_struct->fd_array[]是一个struct file数组,里面的指针都是空的。fork通过dup_fd的如下代码将父进程中的struct file指针拷贝到子进程中:

struct file **old_fds, **new_fds;

struct fdtable *old_fdt, *new_fdt;

 

old_fdt = files_fdtable(oldf);

 

old_fds = old_fdt->fd;

new_fds = new_fdt->fd;

 

for (i = open_files; i != 0; i--) {

                   struct file *f = *old_fds++;

                   if (f) {

                            get_file(f);

                   } else {

                            /*

                             * The fd may be claimed in the fd bitmap but not yet

                             * instantiated in the files array if a sibling thread

                             * is partway through open().  So make sure that this

                             * fd is available to the new process.

                             */

                            __clear_open_fd(open_files - i, new_fdt);

                   }

                   rcu_assign_pointer(*new_fds++, f);

         }

代码检查父进程struct file **old_fds中的元素是否为空,如果不空,则把元素值(struct file*类型)赋付给子进程struct file **new_fds中对应的位置。从这个赋值过程可以看出,父子进程的struct file都是一样的(父子进程都指向同一个struct file),所以文件偏移量struct file->f_pos也一样。

2)父子进程修改struct file结构的字段时,对方能看到吗?

根据常识父子进程是独立的,修改struct file结构的字段值对方是看不到的,但是根据前面分析(父子进程的struct file都是一样的(父子进程都指向同一个struct file),所以文件偏移量struct file->f_pos也一样),父子进程操作的是同一个struct file,一个人修改,另一个人应该能看到啊-----这是错误的。

这里涉及到写时复制机制,父子进程都指向同一个struct file,这只是指父子进程struct file数据结构的虚拟地址是相同的,但是物理地址是不同的。fork进程时,父进程的页表项被标记为只读,当父子进程中任意一个写值时,会产生缺页异常,建立虚拟地址与物理地址的映射关系,然后在新的物理地址上进行写,从而与另一个进程独立,不会相互影响。

3)init进程的struct files_struct

fork进程会将父进程struct files_struct中的信息复制给子进程,那么最原始的struct files_struct是哪里来的?这是在file.c中静态定义的:

struct files_struct init_files = {

         .count                = ATOMIC_INIT(1),

         .fdt            = &init_files.fdtab,

         .fdtab                 = {

                   .max_fds = NR_OPEN_DEFAULT,

                   .fd              = &init_files.fd_array[0],

                   .close_on_exec        = init_files.close_on_exec_init,

                   .open_fds         = init_files.open_fds_init,

         },

         .file_lock  = __SPIN_LOCK_UNLOCKED(init_files.file_lock),

};

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值