一、文件描述符
1.1 文件描述符概念
我们知道在Linux下一切皆文件,因此我们需要一个东西对这些文件进行管理,此时就需要文件描述符来管理了。
文件描述符简称fd,对于内核而言,所有打开的文件都要通过文件描述符来引用。文件描述符是一个递增的非负整数,一旦当我们打开或者创建一个新的文件的时候,内核向进程返回一个文件描述符。
文件描述符一般有以下三个性质:
- 每个进程都具有自己的一个递增的文件描述符,如果我们关闭了一个文件描述符所占用的正整数,则这个正整数有可能被其它文件描述符所占用。
- 单个进程能同时打开的文件描述符数量受到limit设置所限制,可以用ulimit -a查看最大文件描述符个数。进程最大打开文件数目默认是1024个。
- 根据规定,所有的shell启动新的程序的时候,总是将0、1、2这三个数字的文件描述符打开为标准输入、标准输出,标准错误。
1.2 文件表项
1.2.1文件表简介
由于内核在对所有打开的文件都维护一个文件表项,也叫打开文件表。在文件表项中,拥有状态标志位和文件偏移量以及vnode指针,表格中各个条目被叫做打开文件句柄,一个打开文件句柄存储了与一个打开文件相关的全部信息,它有以下信息:
- 当前文件偏移量(read()和write()会更新这个偏移量,lseek()可以直接修改文件偏移量)
- 打开文件所用的状态标志(open()的flags参数)
- 文件访问模式 (调用open()所设置的只读、只写、读写模式)
- 与信号驱动相关的设置
- 文件类型和访问权限
- 指向所有者的锁列表
- 文件的各种属性,包含了文件的大小和不同类型操作的时间戳(A\C\M)
- 文件的inode对象的应用
由于文件表有很多字段,我们重点关注其中的三个字段:
- 状态标志位
- 文件偏移量
- Vnode指针
1.2.2文件表与文件描述符
文件表是内核中的概念,但却是我们理解文件描述符所必备的概念,我们通过下面这副图来再次理解下文件描述符。
进程表项中有文件描述符表,文件描述符里面又有文件描述符标志位和文件指针,文件指针用来指向文件描述符表的,文件描述符标志位标志着是哪一位。
注意:在Linux下没有将相关的数据结构分为i结点和v结点,它是直接采用了一个与文件系统相关的i结点和一个与文件系统无关的i结点。
一个进程里面的文件描述符不但可以指向不同的文件表,同时两个不同的进程还可以指向相同的文件表项。
下图是两个进程指向同一个v结点的图示:
1.3总结
- 由于进程拥有文件描述符表,所以在不同的进程中可以出现相同的文件描述符,它们可以指向不同的文件,也可以指向相同的文件。
- 两个不同的文件描述符,若指向同一个打开文件句柄,将共享同一文件偏移量。因此,如果通过其中一个文件描述符来修改文件偏移量(由调用read()、write()或lseek()所致),那么从另一个描述符中也会观察到变化,无论这两个文件描述符是否属于不同进程,还是同一个进程,情况都是如此。
- 要获取和修改打开的文件标志(例如:
O_APPEND、O_NONBLOCK和O_ASYNC
),可执行fcntl()
的F_GETFL和F_SETFL操作
,其对作用域的约束与上一条颇为类似。 - 文件描述符标志(即,close-on-exec)为进程和文件描述符所私有。对这一标志的修改将不会影响同一进程或不同进程中的其他文件描述符
二、dup()和dup2()函数
2.1函数简介
dup()和dup2()函数是用来复制一个文件描述符,可以实现文件共享。由于返回新的文件描述符与原来的文件描述符对应同一个文件表,所以它们共享同一个当前文件的偏移量,因此在利用新的文件描述符向文件中写入数据的时候不会出现数据覆盖的问题。所以它们经常被用来重定向到进程的标准输入、标准输出、标准出错。
2.1函数原型
在Linux下这两个函数的原型如下
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
返回值:
- 调用成功返回新的文件描述符
- 调用失败返回-1
函数参数:
- oldfd:被复制的文件描述符
- newfd:在dup2中指定新的文件描述符
2.2dup函数与dup2函数区别
在dup函数中返回的是进程中未使用的数值最小的文件描述符,这个和open函数是一样的,而dup2函数会先关闭指定的文件描述符,然后返回该文件描述符作为新的文件描述符。
dup2函数相当于先执行了close函数,再执行了fcntl函数进行文件描述符的复制,这个操作是一个原子操作
dup2函数第二个参数取值返回[0-255]