浅析文件描述符 文件表项 v节点表项符
文件描述符在Linux编程里随处可见,设备读写,网络通信,进程通信. 可是文件描述符到底是什么? 文件描述符是一个简单的整数,
用以标明每一个
被
进程所打开的文件和socket. 第一个打开的文件时0,第二个是1,以此类推. linux操作系统通常给每个进程能打开
的文件数量强加一个限制.
文件描述符的限制可能会极大的影响性能,当用户用户完了所有的文件描述符之后,它不能接收新的连接.
也就是说,用完文件描述符导致拒绝服务.
所以呢,我们可以尝试修改文件描述符的个数 使用 ulimit -HSn xxxx 来修改文件描述符个数.
在Unix系统支持在不同进程间共享打开文件. 在介绍dup之前. 我们先介绍内核表示打开的文件使用的三种数据结构.
内核使用三种数据结构表示打开的文件,他们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响.
1.每一个进程在进程表中都有一个记录表项,记录项中包含有一张打开的文件描述符,可将其视为一个矢量,每个描述项占用一项.
与每个文件描述符相关联的是:
1)文件描述符标志(close-on-exec)
2)指向一个文件表项的指针.
注:其中close-on-exec标志,如果某个文件符设置了该标志,fcntl(fd,F_SETFD,1),则在该进程调用exec函数之前为exec族函数释放
对应的文件描述符
2.内核为所有打开文件维持一张文件表,每个文件表项包括:
1).文件状态标志:读,写,添写,同步,非阻塞等.
2).当前文件偏移量.
3).指向该文件v节点表项的指针.
3.每个打开文件都有个v节点结构,v节点包含了文件类型和对此文件进行各种操作的函数指针. 对于大多数文件,v节点还包含了该文件
的i节点. 这些
信息是在打开文件时从磁盘上读入内存的,所以所有关于文件的信息都是随时快速可供使用的.(
linux下没有使用v节点
,而是使用了通用i节点结
构. 虽然两种实现有所不同,但是在概念上,v节点与i节点是一样的. 两者都指向文件系统特有的i节点结构
)
现在我们来大致看看这个结构吧:
创建v节点结构的目的是对在一个计算机系统上的多文件系统类型提供支持. 这一工作是Peter Weinberger和bill joy(sun公司)分
别独立完成的. sun
把这种文件
系统成为虚拟文件系统,把与文件系统无关的i节点部分成为v节点,当各个制造商的实现增加了对
sun的网络文件系统的支持时,他们都广
泛采用了v节
点结构. linux没有讲相关数据结构分为i节点和v节点,而是采用了一个与文
件系统相关的i节点和一个与文件系统无关的i节点.
如果两个独立进程各自打开了同一个文件,则就会出现下图的这种情况:
我们假定第一个进程在文件描述符3上打开该文件,而另一个进程在文件描述符4上打开该文件. 打开该文件的每个进程都获得各
自的一个文件表项,但
对一个给定的文件只有一个v节点表项.之所以每个进程都获得自己的文件表项,是因为这可以使每一个进
程都有它自己的对该文件的当前偏移量.
给出了这些数据之后,现在对前面所述的操作进一步说明.
1.在完成每个write后,在文件表项中的当前文件偏移量既增加所写入的字节数. 如果这导致当前文件偏移量超出了自己当前文
件长度. 则将i节点表项
中的当前文件长度设置为当前文件偏移量(文件加长).
2.如果用O_APPEND标志打开一个文件,则相应标志也被设置到文件表项的文件状态标志中. 每次对这种具有追加写标志的文件执
行写操作时,文件表项
中的当前文件偏移量首先会被设置为i节点表项中的文件长度,这就使得每次写入的数据都追加到文件的当
前尾端处.
3.若一个文件用lseek定位到文件当前的尾端,则文件表项中的当前文件偏移量被设置为i节点表项中的当前文件长度.
4.lessk函数只修改文件表项中的当前文件偏移量,不进行任何I/O操作.
可能有多个文件描述符项指向同一文件表项. 比如下面的dup函数,或者fork后也会出现同样的情况,此时的父进程,子进程各自
的每一个打开文件描
述符共享同一个文件表项.
注意,文件描述符标志和文件状态标志在作用范围方面的区别,前者只用于一
个进程的一个描述符,而后者则应用于指向该
给定文件表项的任何进程中
的所有描述符. 接下来我们来认识一下函数dup 和 dup2.
下面两个函数都可以用来复制一个现有的文件描述符.
#include<unistd.h>
int dup(int fd);
int dup2(int fd,int fd2);
//两函数的返回值:若成功,返回新的文件描述符. 如出错返回-1.
由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值. 对于dup2,可以用fds参数指定新描述符的值. 如果fd2已经打
开,则现将其关闭.
如若fd等于fd2,则dup2返回fd2,而不关闭它. 否则,fd2的FD_CLOEXEC文件描述符标志就被清除了,这样fd2在
进
程调用exec时是打开状态.这些函数
返回的新的文件描述符与参数fd共享同一个文件表项. 效果如下图.
这个dup最常用的地方就是我们的文件重定向,fcntl与它具有同样功能的还有函数,该函数可以改变已经打开文件的属性.
#include<fcntl.h>
int fcntl(int fd,int cmd,.../*int arg*/):
返回值:若成功,则依赖于cmd; 若出错返回-1
现在我们继续思考,fork()出来的子进程和父进程的文件描述符关系是什么样子呢?? 我们首先分析一下: 同一个文件描述符的文件
表项是父子进
程共用
的资源,所以
父子进程共用同一份文件表项,共用一份V节点表,共用同一个i节点表. 那么我们来看看到底是不是
这样:
fcntl函数是非常重要的文件控制函数,它的功能和选项实在太多了. 所以我这里这里只是提一下,接下来就需要我们继续努力啦!!!
fcntl有如下5种功能:
1.复制一个已有的描述符(cmd = F_DUPFD或F_DUPFD_COLOEXEC).
2.获取/设置文件描述符标志(cmd = F_GETFD或F_SETFD)
3.获取/设置文件状态标志(cmd=F_GETFL或F_SETEL)
4.获取/设置异步I/O所有权(cmd = F_GETOWN或F_SETOWN)
5.获取/设置记录锁(cmd = F_GETLK,F_SETLK或F_SETLKW)
对于这些函数的具体使用还需要下去多用用多看文档即可.
当然如果你想对i节点有更深的了解,你那你必须了解Linux下的文件系统. 博客传送门->文件系统