Linux内核中的文件描述符
Kernel version:2.6.14
CPU architecture:ARM920T
作为文件的使用者,进程理所当然的要将所使用的文件记录于自己的控制块中,也就是task_struct。另外,由于进程所对应的程序也是一个文件,因此进程控制块还必须记录这个文件的相关信息。由于OS要对所有进程提供服务,因此OS还要维护一个记录所有进程打开的文件的总表。
1、文件对象
当进程通过open系统调用打开一个文件时,该系统调用找到这个文件后,会把文件封装到一个file结构的实例中提供给进程,这个实例称为file对象。file结构的定义如下:
struct file {
struct list_head f_list; //所有打开文件的链表
struct dentry *f_dentry; //文件的dentry
struct vfsmount *f_vfsmnt; //文件目录的VFS安装点指针
struct file_operations *f_op; //指向文件操作函数集的指针
atomic_t f_count; //记录访问本文件的进程数目的计数器
unsigned int f_flags; //访问类型
mode_t f_mode; //访问模式
loff_t f_pos; //文件当前的读写位置
struct fown_struct f_owner;
unsigned int f_uid, f_gid; //文件所有者ID和用户组ID
struct file_ra_state f_ra;
unsigned long f_version;
void *f_security;
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
spinlock_t f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
struct rcu_head f_rcuhead;
};
结构中的域f_uid为文件所有者的ID,f_gid为文件所有者所在组的ID。这样就使得一个文件可能面临三种用户的访问:
● 文件所有者;
● 同组用户;
● 其他用户。
内核在处理一个进程或用户访问一个文件的请求时,要根据进程的f_uid和f_gid以及访问模式来确定该进程是否具有访问这个文件的权限。对于一个用户来说,可以有读、写和执行三种文件权限,这三种权限和三种用户就共有9中组合,即文件的访问权限可以用9个bit来表示,并将其保存在文件的dentry中。
结构中的域f_pos记录了进程对文件读写位置的当前值,可以通过调用函数llseek进程移动。
结构中的f_op执向结构file_operations,该结构封装了对文件进行操作的函数,定义如下:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*dir_notify)(struct file *filp, unsigned long arg);
int (*flock) (struct file *, int, struct file_lock *);
};
从上面的代码可以看到,结构中是一系列函数的指针,这里有我们比较熟悉的read、open、write和close等函数的指针。进程就是通过这些函数访问一个文件的,file_operations是linux虚拟文件系统VFS和进程之间的接口。
2、文件描述符
下面进一步介绍进程对自己所访问的file对象的管理方法。linux中使用一个数组来管理进程打开的文件的file对象,数组中的每个元素都存放一个纸箱进程所打开的文件的file对象。既然用一个数组来存放file对象,那么用数组的下标来访问文件就是一件顺理成章的方法,于是,linux就把数组元素的下标叫做该数组元素所对应的文件的文件描述符,该描述符就是系统对文件的标识,这个数组也叫文件描述符数组,如下图所示:
内核通过系统调用dup、dup2和fctl可以使数组中的多个元素指向同一个文件的file对象,也就是说,在linux中,同一个文件可以有多个文件描述符。
3、进程打开文件表
进程描述符数组中存放了一个进程所访问的所有文件,把这个文件描述符数组和这个数组在系统中的一些动态信息组合到一起,就形成了一个新的数据结构——进程打开文件表,即file_struct,其定义如下:
/*
* Open file table structure
*/
struct files_struct {
atomic_t count; //引用计数
spinlock_t file_lock; /* Protects all the below members. Nests inside tsk->alloc_lock */
struct fdtable *fdt; //管理文件描述符
struct fdtable fdtab; //管理文件描述符
fd_set close_on_exec_init; //位图
fd_set open_fds_init; //位图
struct file * fd_array[NR_OPEN_DEFAULT]; //文件描述符数组
};
显然,这个结构应该属于进程的私有数据,所以进程控制块task_struct用指针files指向它。
struct task_struct {
...
/* open file information */
struct files_struct *files;
...
};
进程与其打开文件之间的关系如下图所示。
4、文件描述符的管理
file_struct中的fdt和fdtab用于管理文件文件描述符,一个是fdtable类型,另一个是其指针类型。fdtable的定义如下:
struct fdtable {
unsigned int max_fds; //可以代开的最大文件数
int max_fdset; //位图的最大长度
int next_fd; //下一个可用的fd
struct file ** fd; /* current fd array 指向files_struct的fd_array */
fd_set *close_on_exec;
fd_set *open_fds; //打开的文件标记,比如第2位为0,则打开了2号文件
struct rcu_head rcu;
struct files_struct *free_files;
struct fdtable *next;
};
下图可以很直观的说明文件描述符fd的管理。