文件描述符以及file结构体

一、什么是文件描述符

在Linux下一切皆文件,对于内核而言,所有打开的文件都通过文件描述符引用,文件描述符是一个非负整数,当打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,使用open或creat返回文件描述符标示该文件,将其作为参数传送给read或write.

在linux中,进程是通过文件描述符(file descriptors 简称fd)来访问文件的,文件描述符实际上是一个整数。在程序刚启动的时候,默认有三个文件描述符,分别是:0(代表标准输入),1(代表标准输出),2(代表标准错误)。再打开一个新的文件的话,它的文件描述符就是3。

文件描述符的变化范围是0~OPEN_MAX-1.

二、如何创建文件描述符

进程获取文件描述符最常见的方法就是通过系统函数open或create获取,或者是从父进程继承。
从父进程继承的话,子进程就可以访问父进程所使用的文件。我们再深入想想,进程是独立运行的,互不干扰,如果父子进程要通信的话,是不是就可以通过这些都能访问的文件入手。

文件描述符对于每一个进程是唯一的,每个进程都有一张文件描述符表,用于管理文件描述符。当使用fork创建子进程的话,子进程会获得父进程所有文件描述符的副本,这些文件描述符在执行fork时打开。在由fcntl、dup和dup2子例程复制或拷贝某个进程时,会发生同样的复制过程。

三、文件描述符与打开文件之间的关系

每一个文件描述符都与一个打开的文件相对应,同时,不同的文件描述符也会指向同一个文件。相同的文件可以被不同的进程打开,也可以在同一个进程中被打开多次。系统为每一个进程维护了一个文件描述符表,该表的值从0开始,所以在不同的进程中会看到相同的文件描述符,这种情况下的相同的文件描述符有可能指向同一个文件,也有可能指向不同的文件。
内核维护了三种文件描述符表:
1、进程级别的文件描述符表。
进程级别的文件描述符表的每一条目都记录了单个文件描述符的相关信息。表里面的信息分别是:控制文件描述符操作的一组标志、对打开文件句柄的引用。这个进程级别的文件描述符表用files_struct结构表示,在PCB中有一个这个结构类型的指针变量files。
files_struct定义如下:

struct files_struct {
atomic_t count; /* 共享该表的进程数 */
rwlock_t file_lock; /* 保护以下的所有域,以免在tsk->alloc_lock中的嵌套*/
int max_fds; /*当前文件对象的最大数*/
int max_fdset; /*当前文件描述符的最大数*/
    int next_fd; /*已分配的文件描述符加1*/
struct file ** fd; /* 指向文件对象指针数组的指针 */
fd_set *close_on_exec; /*指向执行exec( )时需要关闭的文件描述符*/
fd_set *open_fds; /*指向打开文件描述符的指针*/
fd_set close_on_exec_init;/* 执行exec( )时需要关闭的文件描述符的初 值集合*/
        fd_set open_fds_init; /*文件描述符的初值集合*/
struct file * fd_array[32];/* 文件对象指针的初始化数组*/
};

2、系统级别的文件描述符表。
内核对所有打开的文件有一个系统级别的文件描述符表。有时也称为打开文件描述符表,并将表格中各条目称为打开文件句柄。一个打开文件句柄存储了这个打开文件的全部相关信息。
大致包含以下信息:
1、当前文件偏移量
2、打开文件时所使用的状态标识(open中的flags参数)
3、文件访问模式(只读,只写,读写模式)
4、与信号驱动相关的设置。
5、对该文件的i-node引用。
6、文件类型和访问权限。
7、一个指针、指向该文件所持有的锁列表。
8、文件的各种属性,包括文件大小及不同类型操作相关的时间戳。
相关定义:

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驱动程序所需 */

};

3、文件系统的i-node表。
保存了文件系统的相关信息。

struct inode──字符设备驱动相关的重要结构介绍
内核中用inode结构表示具体的文件,而用file结构表示打开的文件描述符。Linux2.6.27内核中,inode结构体具体定义如下:
struct inode {
struct hlist_node    i_hash;
struct list_head    i_list;
struct list_head    i_sb_list;
struct list_head    i_dentry;
unsigned long        i_ino;
atomic_t        i_count;
unsigned int        i_nlink;
uid_t            i_uid;
gid_t            i_gid;
 dev_t            i_rdev;   //该成员表示设备文件的inode结构,它包含了真正的设备编号。
u64            i_version;
loff_t            i_size;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t        i_size_seqcount;
}

它们之间的关系(节选自网络):
文件描述符以及file结构体_第1张图片

四、文件描述符的分配

文件描述符的分配:当用户调用相关函数比如open, creat等函数时,如果成功,会返回一个文件描述符,返回的文件描述符一定是当前进程最小没有用到的描述符。但是要特别注意文件描述符的最大值,可以通过命令$ulimit –n查看文件描述符的最大值,一般linux发行版都是1024,当然这个值可以修改的,具体可以参考关于文件描述符。

五、基于文件描述符的输入输出函数

接下来讲讲基于文件描述符的输入输出函数:

open:打开一个文件,并指定访问该文件的方式,调用成功后返回一个文件描述符。

creat:打开一个文件,如果该文件不存在,则创建它,调用成功后返回一个文件描述符。

close:关闭文件,进程对文件所加的锁全都被释放。

read:从文件描述符对应的文件中读取数据,调用成功后返回读出的字节数。

write:向文件描述符对应的文件中写入数据,调用成功后返回写入的字节数。

ftruncate:把文件描述符对应的文件缩短到指定的长度,调用成功后返回0。

lseek:在文件描述符对应的文件里把文件指针设定到指定的位置,调用成功后返回新指针的位置。

fsync:将所有已写入文件中的数据真正写到磁盘或其他下层设备上,调用成功后返回0。

fstat:返回文件描述符对应的文件的相关信息,把结果保存在struct stat中,调用成功后返回0。

fchown:改变与打开文件相关联的所有者和所有组,调用成功后返回0。

fchmod:把文件描述符对应的文件的权限位改为指定的八进制模式,调用成功后返回0。

flock:用于向文件描述符对应的文件施加建议性锁,调用成功后返回0。

fcntl:既能施加建议性锁也能施加强制性锁,能建立记录锁、读取锁和写入锁,调用成功后返回0。

dup:复制文件描述符,返回没使用的文件描述符中最小的编号。

dup2:由用户指定返回的文件描述符的值,用来重新打开或重定向一个文件描述符。

select:同时从多个文件描述符读取数据或向多个文件描述符写入数据。

*1. dup:

函数原型:int dup(int filefd)

函数功能:复制一份现存的文件描述符filefd

*2. dup2:

函数原型:int dup2(int Oldfilefd, int Newfield)

函数功能:复制一份现存的文件描述Oldfilefd,若Newfield等于Oldfilefd时,返回Newfield;若两者不相等时,若Newfield已经打开,先关闭Newfield,然后返回Newfield。

  1. fcntl:

函数原型:int fcntl (int filefd, int cmd, …)

函数功能:fcntl有5种功能,通过第二个参数cmd来决定。这里只讲一种:复制一份现存的文件描述符,cmd为F_DUPFD。

由上面三个函数的功能都是复制文件描述符,记住是文件描述符,不是复制文件表,更不是复制v节点表。

六、文件描述符与文件指针的区别

文件描述符:在linux系统中打开文件就会获得文件描述符,它是个很小的正整数。每个进程在PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。
文件指针:C语言中使用文件指针做为I/O的句柄。文件指针指向进程用户区中的一个被称为FILE结构的数据结构。FILE结构包括一个缓冲区和一个文件描述符。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在Windows系统上,文件描述符被称作文件句柄)。

文章整理自:
http://blog.csdn.net/cywosp/article/details/38965239
http://blog.csdn.net/lf_2016/article/details/54605651
http://blog.sina.com.cn/s/blog_7943319e01018m3w.html
http://www.cnblogs.com/hnrainll/archive/2011/11/06/2237872.html
https://my.oschina.net/iuranus/blog/330397

你可能感兴趣的:(Linux)