Linux文件描述符
Linux文件描述符
一、文件描述符(fd)
文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。
我们都知道在Linux下一切皆文件。当然设备也不例外,如果要对某个设备进行操作,就不得不打开此设备文件,打开文件就会获得该文件的文件描述符fd( file discriptor), 它就是一个很小的整数,每个进程在PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。
要理解文件描述符,必须先理解这三个表的结构
理解具体情况,需要了解由内核维护的3个数据结构:
进程级文件描述符表(file descriptor table)
系统级打开文件表(open file table)
文件系统i-node表(i-node table)
再多看一张图,你就会对整体的结构有更清晰的了解:
struct FILE
{
char *_ptr;//文件输入的下一个位置
int _cnt;//当前缓冲区的相对位置
char *_base;//指基础位置(文件的起始位置)
int _flag;//文件标志
int _file;//文件的有效性验证
int _charbuf;//检查缓冲区状况,如果缓冲区则不读取
int _bufsiz;//文件的大小
char *_tmpfname;//临时文件名
};
文件描述符 | 缩写 | 描述 |
---|---|---|
0 | STDIN | 标准输入 |
1 | STDOUT | 标准输出 |
2 | STDERR | 标准错误输出 |
图中文件描述符即为文件描述符数组的下标
文件描述符的分配规律:从当前未使用的最小的整数开始分配;
文件描述符的缺点:
不能移植到UNIX以外的系统上去,也不直观。
举一个系统函数的例子:ssize_t write ( int fd, const void *buf, size_t count);
write: 是系统写函数 fd: 文件描述符 (一个整数) *buf: 内容写在哪里 count: 一次写多少个;
概括:
每一个进程在PCB(Process Control Block)即进程控制块中都保存着一分文件描述符表,文件描述符就是这个表的索引,文件描述符表中每个表项都有一个指向已打开文件的指针。现在我们明确一下:已打开的文件在内核中用file结构体表示,文件描述符表中的指针指向file结构体。
fd详解
fd:为打开文件的文件描述符,而每个进程都有一张文件描述符表,fd文件描述符就是这张表的索引,同样这张表中有一表项,该表项又是指向前面提到打开文件的file结构体,file结构体才是内核中用来描述文件属性的结构体。
文件打开表
在Linux中,进程是通过文件描述符(file descriptors,简称fd)而不是文件名来访问文件的,文件描述符实际上是一个整数。Linux中规定每个进程能最多能同时使用NR_OPEN个文件描述符,这个值在fs.h中定义,为1024*1024(2.0版中仅定义为256)。
每个文件都有一个32位的数字来表示下一个读写的字节位置,这个数字叫做文件位置。每次打开一个文件,除非明确要求,否则文件位置都被置为0,即文件的开始处,此后的读或写操作都将从文件的开始处执行,但你可以通过执行系统调用LSEEK(随机存储)对这个文件位置进行修改。Linux中专门用了一个数据结构file来保存打开文件的文件位置,这个结构称为打开的文件描述(open
file description)。
file结构在include\linux\fs.h中定义如下: struct file
{
struct list_head f_list; /所有打开的文件形成一个链表/
struct dentry *f_dentry; /指向相关目录项的指针/
struct vfsmount *f_vfsmnt; /指向VFS安装点的指针/
struct file_operations *f_op; /指向文件操作表的指针/
mode_t f_mode; /文件的打开模式/
loff_t f_pos; /文件的当前位置/
unsigned short f_flags; /打开文件时所指定的标志/
unsigned short f_count; /使用该结构的进程数/
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
/预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及预读的页面数/
int f_owner; /* 通过信号进行异步I/O数据的传送*/
unsigned int f_uid, f_gid; /用户的UID和GID/
int f_error; /网络写操作的错误码/
unsigned long f_version; /版本号/
void private_data; / tty驱动程序所需 */
};
f_list:所有的打开的文件形成的链表!注意一个文件系统所有的打开的文件都通过这个链接到super_block中的s_files链表中!
f_dentry:与该文件相关的dentry f_vfsmnt:该文件在这个文件系统中的安装点
f_op:文件操作,当进程打开文件的时候,这个文件的关联inode中的i_fop文件操作会初始化这个f_op字段 f_count:引用计数
f_flags:打开文件时候指定的标识 f_mode:文件的访问模式 f_pos:目前文件的相对开头的偏移 unsigned long
f_reada, f_ramax, f_raend, f_ralen,
f_rawin:预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及预读的页面数
f_owner:记录一个进程ID,以及当某些事发送的时候发送给该ID进程的信号 f_uid:用户ID f_gid:组ID
f_error:写操作错误码 f_version:版本号,当f_pos改变时候,version递增 private_data:私有数据(
文件系统和驱动程序使用 )
重点解释一些重要字段:
首先,f_flags、f_mode和f_pos代表的是这个进程当前操作这个文件的控制信息。这个非常重要,因为对于一个文件,可以被多个进程同时打开,那么对于每个进程来说,操作这个文件是异步的,所以这个三个字段就很重要了。
第二:对于引用计数f_count,当我们关闭一个进程的某一个文件描述符时候,其实并不是真正的关闭文件,仅仅是将f_count减一,当f_count=0时候,才会真的去关闭它。对于dup,fork这些操作来说,都会使得f_count增加,具体的细节,以后再说。
第三:f_op也是很重要的!是涉及到所有的文件的操作结构体。例如:用户使用read,最终都会调用file_operations中的读操作,而file_operations结构体是对于不同的文件系统不一定相同。里面一个重要的操作函数式release函数,当用户执行close时候,其实在内核中是执行release函数,这个函数仅仅将f_count减一,这也就解释了上面说的,用户close一个文件其实是将f_count减一。只有引用计数减到0才关闭文件。
内核中,对应于每个进程都有一个文件描述符表,表示这个进程打开的所有文件。文件描述表中每一项都是一个指针,指向一个用于描述打开的文件的数据块———file对象,file对象中描述了文件的打开模式,读写位置等重要信息,当进程打开一个文件时,内核就会创建一个新的file对象。需要注意的是,file对象不是专属于某个进程的,不同进程的文件描述符表中的指针可以指向相同的file对象,从而共享这个打开的文件。file对象有引用计数,记录了引用这个对象的文件描述符个数,只有当引用计数为0时,内核才销毁file对象,因此某个进程关闭文件,不影响与之共享同一个file对象的进程.
file对象中包含一个指针,指向dentry对象。dentry对象代表一个独立的文件路径,如果一个文件路径被打开多次,那么会建立多个file对象,但它们都指向同一个dentry对象。
dentry对象中又包含一个指向inode对象的指针。inode对象代表一个独立文件。因为存在硬链接与符号链接,因此不同的dentry对象可以指向相同的inode对象.inode 对象包含了最终对文件进行操作所需的所有信息,如文件系统类型、文件的操作方法、文件的权限、访问日期等。
fd只是一个整数,在open时产生。起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针filp。 打开文件后,进程得到的文件描述符实质上就是文件描述符表的下标,内核根据这个下标值去访问相应的文件对象,从而实现对文件的操作。
注意,同一个进程多次打开同一个文件时,内核会创建多个file对象。
当进程使用fork系统调用创建一个子进程后,子进程将继承父进程的文件描述符表,因此在父进程中打开的文件可以在子进程中用同一个描述符访问。
注意上面的file只是对一个文件而言,对于一个进程(用户)来说,可以同时处理多个文件,所以需要另一个结构来管理所有的files!
即:用户打开文件表—>files_struct
struct files_struct { atomic_t count; rwlock_t
file_lock; /* Protects all the below members. Nests inside
tsk->alloc_lock / int max_fds; int max_fdset;
int next_fd; struct file ** fd; / current fd array */
fd_set *close_on_exec; fd_set *open_fds; fd_set
close_on_exec_init; fd_set open_fds_init; struct
file * fd_array[NR_OPEN_DEFAULT]; };解释一些字段:
count:引用计数 file_lock:锁,保护下面的字段 max_fds:当前文件对象的最大的数量 max_fdset:文件描述符最大数
next_fd:已分配的最大的文件描述符+1
fd:指向文件对象指针数组的指针,一般就是指向最后一个字段fd_arrray,当文件数超过NR_OPEN_DEFAULT时候,就会重新分配一个数组,然后指向这个新的数组指针!
close_on_exec:执行exec()时候需要关闭的文件描述符 open_fds:指向打开的文件描述符的指针
close_on_exec_init:执行exec()时候需要关闭的文件描述符初始化值 open_fds_init:文件描述符初值集合
fd_array:文件对象指针的初始化数组
这里补充一点i-node表的知识:
i-node表
每个文件系统会为存储于其上的所有文件(包括目录)维护一个i-node表,单个i-node包含以下信息:
•文件类型(file type),可以是常规文件、目录、套接字或FIFO
•访问权限
•文件锁列表(file locks)
•文件大小
fs_struct结构体定义如下:
我看网上有两种论调,也不知道这两种说法是都对了还是只对其一,请看如下两张图:
关于FILE结构体的第一种说法是int _file是文件描述符,这里我就就不懂了,我们知道,文件描述符其实就是一个打开文件的索引,他是存放在文件描述符表中的,仅此而已,与具体文件结构有什么关系,一个文件创建出来的时候,这个索引它自己本什么怎么可能知道。而且我们必须要知道还有一个文件打开表,这是系统级的一个表,主要记录的打开的文件的文件偏移量、状态、i-node节点。
还有一种说如下:
上一篇: Linux:文件描述符
下一篇: Linux文件描述符