Linux文件描述符分析
在Linxu下面对于文件的操作,都是在一个文件描述符下面进行的,例如:
//文件打开或者创建
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);
//文件读取
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
我们知道,文件描述符是一个非负的整数,默认情况下定义了标准输入输出的文件描述符,如下:
#define STDIN_FILENO 0 /* Standard input. */
#define STDOUT_FILENO 1 /* Standard output. */
#define STDERR_FILENO 2 /* Standard error output. */
那么文件描述符具体是怎么使用的呢?本文来探讨一下其中相关的技术原理。
1. file_struct结构
在linux中,一个进程使用一个task_struct
的结构进行抽象,这个结构体的中存在如下数据:
struct task_struct {
//...
/* Open file information: */
struct files_struct *files;
//...
};
这个结构体中struct files_struct *files
代表着打开的文件的信息,这个文件内容大致如下:
struct files_struct {
atomic_t count; //引用计数
bool resize_in_progress;
wait_queue_head_t resize_wait;
struct fdtable __rcu *fdt; //文件描述符表指针
struct fdtable fdtab; //默认的文件描述符表
spinlock_t file_lock ____cacheline_aligned_in_smp; //锁
unsigned int next_fd; //下一个最小的没有使用过的fd
unsigned long close_on_exec_init[1]; //默认FD_CLOEXEC标记的位图
unsigned long open_fds_init[1]; //默认已经使用过的文件描述的标记位图
unsigned long full_fds_bits_init[1]; //默认
struct file __rcu * fd_array[NR_OPEN_DEFAULT]; //默认的文件描述符对应的结构体的数组
};
struct fdtable {
unsigned int max_fds; //最大的文件描述符
struct file __rcu **fd; //文件描述符对应的文件管理的结构
unsigned long *close_on_exec; //FD_CLOEXEC标记的位图的指针
unsigned long *open_fds; //文件描述符打开的位图关系
unsigned long *full_fds_bits;
struct rcu_head rcu; //回调函数
};
从上面的结构体我们可以发现,文件描述符(fd)其实就是一个结构体数组的一个索引值,并且为了方便快速的查找这个数组中没有使用的项,使用一个位图来进行快速查找,例如:
static unsigned int find_next_fd(struct fdtable *fdt, unsigned int start)
{
unsigned int maxfd = fdt->max_fds;
unsigned int maxbit = maxfd / BITS_PER_LONG;
unsigned int bitbit = start / BITS_PER_LONG;
bitbit = find_next_zero_bit(fdt->full_fds_bits, maxbit, bitbit) * BITS_PER_LONG;
if (bitbit > maxfd)
return maxfd;
if (bitbit > start)
start = bitbit;
return find_next_zero_bit(fdt->open_fds, maxfd, start);
}
使用find_next_zero_bit(fdt->open_fds, maxfd, start)
来查找open_fds
找到下一个没有被标记的位(也就是空闲的索引)。
2. file结构
文件描述符对应的就是file
数组的索引,一个file
结构代表的是一个打开的文件,这个结构体声明大致如下:
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u; //所有打开文件形成链表
struct path f_path; //包含struct dentry *dentry是与文件相关的目录项对象
struct inode *f_inode; /* cached value */
const struct file_operations *f_op; //文件操作的各种函数
spinlock_t f_lock;
enum rw_hint f_write_hint;
atomic_long_t f_count; //引用计数
unsigned int f_flags; //打开文件时候指定的标识
fmode_t f_mode; //文件的访问模式,对应系统调用open的mod_t mode参数
struct mutex f_pos_lock;
loff_t f_pos; //目前文件的相对开头的偏移
struct fown_struct f_owner; //该结构的作用是通过信号进行I/O时间通知的数据。
const struct cred *f_cred; //认证信息,包含标识文件的所有者id,所有者所在组的id
struct file_ra_state f_ra; //文件预读状态,文件预读算法使用的主要数据结构
u64 f_version; //记录文件的版本号,每次使用后都自动递增。
void *private_data; //私有数据( 文件系统和驱动程序使用 )
};
3. fget_light函数
fget_light
这个函数就是用fd来获取对应的file的函数,该函数实现如下:
static unsigned long __fget_light(unsigned int fd, fmode_t mask)
{
struct files_struct *files = current->files;
struct file *file;
if (atomic_read(&files->count) == 1) {
file = __fcheck_files(files, fd);
if (!file || unlikely(file->f_mode & mask))
return 0;
return (unsigned long)file;
} else {
file = __fget(fd, mask, 1);
if (!file)
return 0;
return FDPUT_FPUT | (unsigned long)file;
}
}
static struct file *__fget(unsigned int fd, fmode_t mask, unsigned int refs)
{
struct files_struct *files = current->files;
struct file *file;
rcu_read_lock();
loop:
file = fcheck_files(files, fd);
if (file) {
/* File object ref couldn't be taken.
* dup2() atomicity guarantee is the reason
* we loop to catch the new file (or NULL pointer)
*/
if (file->f_mode & mask)
file = NULL;
else if (!get_file_rcu_many(file, refs))
goto loop;
}
rcu_read_unlock();
return file;
}
static inline struct file *__fcheck_files(struct files_struct *files, unsigned int fd)
{
struct fdtable *fdt = rcu_dereference_raw(files->fdt);
if (fd < fdt->max_fds) {
fd = array_index_nospec(fd, fdt->max_fds);
return rcu_dereference_raw(fdt->fd[fd]);
}
return NULL;
}
从fdt->fd[fd]
我们就可以发现通过fd获取file的过程就是通过索引查找数组成员的过程。
4. fd_install函数
我们获取到了一个文件描述符之后,就可以通过fd_install
将文件描述符和file进行安装(绑定),其实就是在file数组中设置下标对应的成员,这个函数实现如下:
void __fd_install(struct files_struct *files, unsigned int fd,
struct file *file)
{
struct fdtable *fdt;
rcu_read_lock_sched();
if (unlikely(files->resize_in_progress)) {
rcu_read_unlock_sched();
spin_lock(&files->file_lock);
fdt = files_fdtable(files);
BUG_ON(fdt->fd[fd] != NULL);
rcu_assign_pointer(fdt->fd[fd], file);
spin_unlock(&files->file_lock);
return;
}
smp_rmb();
fdt = rcu_dereference_sched(files->fdt);
BUG_ON(fdt->fd[fd] != NULL);
rcu_assign_pointer(fdt->fd[fd], file);
rcu_read_unlock_sched();
}
这里很容易发现就是通过rcu_assign_pointer(fdt->fd[fd], file);
来设置数组索引对应的指针。
5. dup2函数
dup
就是拷贝一个file到新的描述符(也就是生成新的索引),整个过程如下:
static inline struct file *get_file(struct file *f)
{
atomic_long_inc(&f->f_count);
return f;
}
static int do_dup2(struct files_struct *files,
struct file *file, unsigned fd, unsigned flags)
__releases(&files->file_lock)
{
struct file *tofree;
struct fdtable *fdt;
fdt = files_fdtable(files);
tofree = fdt->fd[fd];
if (!tofree && fd_is_open(fd, fdt))
goto Ebusy;
get_file(file);
rcu_assign_pointer(fdt->fd[fd], file);
__set_open_fd(fd, fdt);
if (flags & O_CLOEXEC)
__set_close_on_exec(fd, fdt);
else
__clear_close_on_exec(fd, fdt);
spin_unlock(&files->file_lock);
if (tofree)
filp_close(tofree, files);
return fd;
Ebusy:
spin_unlock(&files->file_lock);
return -EBUSY;
}
这里也是直接通过rcu_assign_pointer(fdt->fd[fd], file);
来将原来的file拷贝到fd对应的索引位置,并且使用get_file
增加引用计数。
6. 总结
综上所述,我们知道了文件描述符(fd)其实就是一个数组的索引,其对应关系图可以总结位如下: