Linux文件描述符分析

本文详细解析了Linux中的文件描述符机制,包括file_struct结构、fdtable结构、fget_light、fd_install和dup2函数的工作原理,展示了文件描述符作为索引关联文件的机制和操作流程。
摘要由CSDN通过智能技术生成

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)其实就是一个数组的索引,其对应关系图可以总结位如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值