linux get fd trail


我们知道在我们使用open() 系统调用打开一个文件以后, 会返回一个具体的fd, 我们知道0, 1, 2是默认的标准输入, 标准输出以及错误输出的fd, 那么这个fd 号是如何获得, 以及关闭以后如何使用的呢?

数据结构

首先进程打开一个文件相关的操作都保存在 files_struct 这个结构体里, 变量名是files, 每一个进程有自己的 files_struct, files_struct 中包含fdtable 结构体

/*
 * Open file table structure
 */
struct files_struct {
  /*
   * read mostly part
   */
	atomic_t count;
	struct fdtable *fdt;
	struct fdtable fdtab;
  /*
   * written part on a separate cache line in SMP
   * 操作 fdt 的时候一般都会锁住 file_lock
   * 比如在 open 需要往fdt 增加fd 的过程, 以及在close 需要往fdt 中减东西的过程
   * 这个就是保证了有多个线程在一个进程中都在申请open, socket() 等等操作的时候fd 
   * 不会冲突的保证
   */
	spinlock_t file_lock ____cacheline_aligned_in_smp;  
	int next_fd;
	struct embedded_fd_set close_on_exec_init;
	struct embedded_fd_set open_fds_init;
	struct file * fd_array[NR_OPEN_DEFAULT];
};

struct fdtable {
	unsigned int max_fds;
  /*
   * 这里这个fd 保存的就是fd 与 vfs 中的file 的对应关系, 
   * 所以知道一个进程打开的fd 以后, 在这里根据fd 的号码对应的数组
   * 的位置就可以获得这个file 结构了
   * 比如: 这里就是fdtable 根据fd 或者对应file* 的操作
   * filp = fdt->fd[fd];
   * 将一个fd 关联到file* 的方法是
	 * fd_install(fd, f);
   */
	struct file ** fd;      /* current fd array */
	fd_set *close_on_exec;
	fd_set *open_fds;
	struct rcu_head rcu;
	struct fdtable *next;
};

// 其中fd_set 的定义是这样
#define __NFDBITS	(8 * sizeof(unsigned long))
#define __FD_SETSIZE	1024
#define __FDSET_LONGS	(__FD_SETSIZE/__NFDBITS)
typedef struct {
	unsigned long fds_bits [__FDSET_LONGS];
} __kernel_fd_set;
typedef __kernel_fd_set		fd_set;


所以进程打开的fd 是保存在每一个fd_set *open_fds 中, 每一个打开的进程是一个bit 位

方法

申请fd 的过程是

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
{
	ret = do_sys_open(AT_FDCWD, filename, flags, mode);

 =>
long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{

    /*
     * 获得当前这个进程里面空闲的fd
     * 注意下面的调用里面会用到current 这个变量, 这个变量指的是当前的进程
     */
		fd = get_unused_fd_flags(flags);
	...
      /*
       * 这里是打开file * 指针的操作
       */
	struct file *f = do_filp_open(dfd, tmp, flags, mode, 0);

	...
		fsnotify_open(f->f_path.dentry);
        /*
         * 将fd 和 file* 关联起来
         * 主要做的事情就是设置这个fdtable 中的fd[fd] = f;
         * 那么下一次就可以根据fd 号获得 file* 这个指针
         */
		fd_install(fd, f);

=>
#define get_unused_fd_flags(flags) alloc_fd(0, (flags))

int alloc_fd(unsigned start, unsigned flags)
{
...
  // 在操作fdt 之前都会加 file_lock 这个锁
	spin_lock(&files->file_lock);
  // 找出在open_fds->fds_bits 中空闲的bit, 如果不够了, 同时扩展这个fds_bits
	if (fd < fdt->max_fds)
		fd = find_next_zero_bit(fdt->open_fds->fds_bits,
					   fdt->max_fds, fd);
  /*
   * 在expand_files 里面, 会进行检查是否达到这个进程的resource limit
   * 以及ulimit -a 中的打开文件句柄数限制
   * 如果fdtable 的空间不够, 就进行翻倍扩展
   */
	error = expand_files(files, fd);


tips

从代码的实现里面可以看出, 所有对于fdtable 的操作都会添加file_lock锁, 所以肯定不会出现一个fd 对应多个打开的文件的情况, 之前线上出现一个fd 对应多个打开文件, 我们还怀疑过, 估计当时的现象应该是打开了了一个文件以后, 已经被关闭, 这个时候打开了一个新的文件使用的还是这个fd 号, 因为从下面close 的策略可以看出, 是一旦有fd 释放, 马上就会去使用这个fd 的

close的过程

SYSCALL_DEFINE1(close, unsigned int, fd)
{
	spin_lock(&files->file_lock);
  /*
   * 保存在fdtable 中, 通过fd 就可以直接找到对应的file* 指针
   */
	filp = fdt->fd[fd];

	__put_unused_fd(files, fd);
	spin_unlock(&files->file_lock);

...

static void __put_unused_fd(struct files_struct *files, unsigned int fd)
{
	struct fdtable *fdt = files_fdtable(files);
	__FD_CLR(fd, fdt->open_fds);
  /*
   * 从这个设置next_fd 的操作可以看出
   * 对于fd 的操作是close 一个fd 以后, 下一次的open 操作申请的
   * 就是这个释放的fd
   * 所以这里是循环利用这个fd的
   */
	if (fd < files->next_fd)
		files->next_fd = fd;
}