linux 文件打开(open)原理,非父子进程,父子进程打开文件关系

Table of Contents

 

目录

1、文件打开原理总体概述

2、文件描述符fd和文件描述符表介绍

2.1. 概述

2.2、文件描述限制

3、文件描述符和打开文件之间的关系

4、进程打开文件各种情况分析

4.1、 一个进程open两次同一个文件

 4.2、(父子进程) fork之前open

 4.3、fork之后open

4.4、两个进程同时open一个文件

5、结论


1、文件打开原理总体概述


1、一个Linux进程启动后,会在内核空间创建一个PCB进程控制块,PCB是一个进程的私有财产。

2、这个PCB中有一个已打开文件描述符表,记录着所有该进程打开的文件描述符以及对应的file结构体地址。

3、默认情况下,启动一个Linux进程后,会打开三个文件,分别是标准输入、标准输出、标准错误分别使用了0、1 、2号文件描述符。

4、当该进程使用函数open打开一个新的文件时,一般会在内核空间申请一个file结构体,并且把3号文件描述符对应的file指针指向file结构体,file结构体包含指向v-node节点的指针。再去打开一个文件(可以是再次打开同一个文件),文件描述符fd就会递增,并且file指针会指向新创建的file结构体。

 

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
    int fd = open("./log.txt", O_RDWR);
    printf("new fd = %d\n", fd);
}


 

process table entry就是进程的文件描述符表file table entry用来记录文件的读写打开模式,当前文件偏移量,以及v-node指针

v-node table entry是虚拟文件系统对应的文件节点,i-node是磁盘文件系统对应的文件节点。通过这两个节点就能找到最终的磁盘文件。

每一个进程只有一个process table entry,一般情况下默认使用 fd 0、fd1、fd2,新打开的文件log.txt将使用fd 3。


 


2、文件描述符fd和文件描述符表介绍



2.1. 概述


在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引(fd),其是一个非负整数(通常是小整数),文件描述符fd和文件file指针是文件描述符表的重要内容,用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。每个进程刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3,再去打开一个文件(可以是再次打开同一个文件,文件描述符fd就会递增。POSIX标准要求每次打开文件时(含socket)必须使用当前进程中最小可用的文件描述符号码,因此,在网络通信过程中稍不注意就有可能造成串话。标准文件描述符和描述符表如下:


 


2.2、文件描述限制



    在编写文件操作的或者网络通信的软件时,初学者一般可能会遇到“Too many open files”的问题。这主要是因为文件描述符是系统的一个重要资源,虽然说系统内存有多少就可以打开多少的文件描述符,但是在实际实现过程中内核是会做相应的处理的,一般最大打开文件数会是系统内存的10%(以KB来计算)(称之为系统级限制,查看系统级别的最大打开文件数可以使用sysctl -a | grep fs.file-max命令查看。与此同时,内核为了不让某一个进程消耗掉所有的文件资源,其也会对单个进程最大打开文件数做默认值处理(称之为用户级限制),默认值一般是1024使用ulimit -n命令可以查看。在Web服务器中,通过更改系统默认值文件描述符的最大值来优化服务器是最常见的方式之一,具体优化方式请查看http://blog.csdn.net/kumu_linux/article/details/7877770。

 


3、文件描述符和打开文件之间的关系


参考:文件描述符与打开文件的关系?

 

内核中,对应于每个进程都有一个文件描述符表,表示这个进程打开的所有文件,它每个进程的文件描述符表是独立的。文件描述表中包含了一个file指针,指向一个用 于描述打开的文件的数据块———file对象,file对象中描述了文件的打开模式,读写位置等重要信息,当进程打开一个文件时,内核就会创建一个新的file对象。需要注意的是,file对象不是专属于某个进程的,不同进程的文件描述符表中的指针可以指向相同的file对象,从而共享这个打开的文件,并且由于每个进程文件描述符表相互独立,因此不同进程同时打开一个文件的文件描述符fd可能相同也可能不同file对象有引用计数,记录了引用这个file对象的文件描述符个数,只有当引用计数为0时,内核才销毁file对象,因此某个进程关闭文件,不影响与之共享同一个file对象的进程。

 

内核维护的3个数据结构 :

    1. 进程级的文件描述符表

    2. 系统级的打开文件描述符表(file 结构体,文件句柄)

    3. 文件系统的i-node表

内核对所有打开的文件的文件维护有一个系统级的描述符表格(open file description table)。有时,也称之为打开文件表(open file table),并将表格中各条目称为打开文件句柄(open file handle)。一个打开文件句柄存储了与一个打开文件相关的全部信息,如下所示:
    1. 当前文件偏移量(调用read()和write()时更新,或使用lseek()直接修改)
    2. 打开文件时所使用的状态标识(即,open()的flags参数)
    3. 文件访问模式(如调用open()时所设置的只读模式、只写模式或读写模式)
    4. 与信号驱动相关的设置
    5. 对该文件i-node对象的引用
    6. 文件类型(例如:常规文件、套接字或FIFO)和访问权限
    7. 一个指针,指向该文件所持有的锁列表
    8. 文件的各种属性,包括文件大小以及与不同类型操作相关的时间戳

 

三者关系图如下:

 


4、进程打开文件各种情况分析



4.1、 一个进程open两次同一个文件



一个进程open两次同一个文件,其实跟两个进程open一次的原理相同,都是调用了两次open,反正只要记住,调用一次open函数,就会创建一个file table entry。

原理图如下:

 

由于只有一个进程,所以只有一个process table entry,open了两次,所以是两个file table entry 分别分配了fd 3与fd 4指向这两个结构体


 4.2、(父子进程) fork之前open


fork之前调用open函数,也就是只调用了一次,产生了一个fd以及file table,fork之后子进程的process table entry会从父亲进程中复制过来,文件描述表也复制过来了,那么子进程的fd指向的是同一个file table(file共享),因为创建子进程是直接复制父进程的文件描述符表

原理图如下:

代码如下:testopenfork.c

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
    int fd = open("./log.txt", O_RDWR);
    int pid = fork();
    printf("pid %d %ld\n", pid, lseek(fd, 0, SEEK_CUR));
    write(fd, "123", 3);
    sleep(5);
    printf("pid %d %ld\n", pid, lseek(fd, 0, SEEK_CUR));
    close(fd);
}


运行结果:

$ ./a.out 
pid 6388 lseek 0
pid 0 lseek 3
pid 6388 lseek 6
pid 0 lseek 6

 

父子进程都各自写入3字节,如果是两个file table,那么最终都应该打印的是3,而不是6,请与第5节进行对比。

需要注意的是:如果想要释放这个file table,也必须父子进程都close一次fd才会释放,如果不close,进程退出的时候会自动close掉所有的文件描述符。


 4.3、fork之后open


如果在调用fork之后调用一次open函数,由于fork之后会返回两次,一次父进程返回,一次子进程返回,那么这个时候其实是相当与两个进程分别调用了一次open函数打开同一个文件,与第二节中的原理相同。

 

代码如下:testforkopen.c

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
    int pid = fork();
    int fd = open("./log.txt", O_RDWR);
    printf("pid %d %ld\n", pid, lseek(fd, 0, SEEK_CUR));
    write(fd, "123", 3);
    sleep(5);
    printf("pid %d %ld\n", pid, lseek(fd, 0, SEEK_CUR));
    close(fd);
}

运行结果:

$ ./a.out 
pid 6112 lseek 0  #父进程
pid 0 lseek 0     #子进程
pid 6112 lseek 3  #父进程
pid 0 lseek 3     #子进程
可以看到父子进程的读写位置都是3,并不受影响。


4.4、两个进程同时open一个文件


两个进程同时open一个文件,这个时候的原理图如下:

 

因为现在是两个进程,所以process table entry进程控制块也是两个,每个进程控制块中各自维护一个张文件描述符表,同时打开一个文件的时候,都各自申请了一个file table entry。

 

 由于打开的是同一一个文件,所以file table entry都指向了同一个v-node。

两个file table entry,怎么去证明呢?

   

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
    int fd = open("./log.txt", O_RDWR);
    printf("new fd = %d\n", fd);
    printf("%ld\n", lseek(fd, 0, SEEK_CUR));
    write(fd, "123", 3);
    sleep(5);
    printf("%ld\n", lseek(fd, 0, SEEK_CUR));
    close(fd);
}

file table entry中都保存了一个文件读写偏移量,如果是两个file table entry,那么两个进程读写位置是独立的,不受影响的。                     

上面的代码运行结果是:

#先启动进程0
$ ./a.out 
new fd = 3
0
3
​
#在5秒时间内,启动进程1
$ ./a.out 
new fd = 3
0
3

        两个进程都分配了fd 3 给新打开个文件,并且读写位置不受其他进程的影响 。如果受影响了话,进程1的读写位置要变成3和6.
  

 

5、结论

 

1、只要有一次open就创建一次file对象,并且在文件描述符表添加一项(fd和file指针),file指针指向新创建的file对象

2、父进程fork前open新增的文件描述符项里面的file指针父子是一样的,共用file结构体,这个时候需要注意文件偏移量共享问题

3.两个进程除了上面那种情况可以分别读取文件的不同部分而不会相互影响

4、不同进程(除了父子进程)出现相同fd打开同一文件的情况,这个相同是巧合的,正好碰上系统分配的fd一样,有时候也会不同的fd打开相同的文件,因为进程之间的文件描述符表是相互独立的,互不影响

 

 

 

 

 

  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值