Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

彻底理解 Linux 中的 文件描述符(fd) #6

Open
JemmyH opened this issue Jun 7, 2021 · 1 comment
Open

彻底理解 Linux 中的 文件描述符(fd) #6

JemmyH opened this issue Jun 7, 2021 · 1 comment
Assignees
Labels
documentation Improvements or additions to documentation done

Comments

@JemmyH
Copy link
Owner

JemmyH commented Jun 7, 2021

文件描述符(file descriptor)Linux编程 里随处可见,设备读写网络通信进程通信fd 可谓是关键中的关键。
深入理解可以增加我们使用它的信心。

@JemmyH JemmyH changed the title 彻底理解 Linux 中的 **文件描述符(fd)** 彻底理解 Linux 中的 文件描述符(fd) Jun 7, 2021
@JemmyH JemmyH self-assigned this Jun 7, 2021
@JemmyH JemmyH added the documentation Improvements or additions to documentation label Jun 7, 2021
@JemmyH
Copy link
Owner Author

JemmyH commented Jun 7, 2021

1. 什么是文件描述符?

在Linux系统中一切皆可以看成是文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引(可以理解成数组的 index),是一个非负整数,用于指代被打开的文件,所有执行 I/O操作 的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。

2. 文件描述符、文件、进程间的关系

系统为了维护文件描述符,维护了三个实体表:

  • 进程级的 文件描述符表(task_struct(PCB)->files_struct)
  • 系统级的 打开文件表,内核对所有打开文件维护的一个描述表格,表格中的每一项称为打开文件句柄,可以理解为 文件指针inode 的映射;
  • inode 表,存储文件的 metadata 信息,如 文件类型,访问权限,文件属性等。

有关 inode,可以参考此篇文章 阮一峰 理解 inode

这三张表之间的关系可以这么描述:

  • 每个文件描述符会与一个打开的文件相对应
  • 不同的文件描述符也可能指向同一个文件
  • 相同的文件可以被不同的进程打开,也可以在同一个进程被多次打开

借助 Linux文件描述符到底是什么? 中的一张图说明它们三者之间的关系:

  • 在进程A中,文件描述符 120 都指向了同一个打开的文件句柄 #23,这可能是该进程多次对执行 open 操作;
  • 进程A 中的文件描述符2进程B 的文件描述符 2 都指向了同一个打开的文件句柄 #73,这种情况有几种可能: 1.进程A进程B 可能是 父子进程 关系; 2.进程A进程B 打开了同一个文件,且文件描述符相同(很少发生);3. AB 中某个进程通过 UNIX套接字 将一个打开的文件描述符传递给另一个进程。
  • 进程A 的描述符0和进程B的描述符3分别指向不同的打开文件句柄,但这些句柄均指向 inode表 的相同条目 #1936,换言之,指向同一个文件。发生这种情况是因为每个进程各自对同一个文件发起了打开请求。同一个进程两次打开同一个文件,也会发生类似情况。

换句话说,文件描述符(fd) 只是数组的下标!

3. 关于 fd_install

内核中关于 fd 最常用的代码片段如下:

int fd;
struct file *file;

// 从当前进程的文件描述符表的 fd 数组中取一个未使用的,将其状态设置为 `busy`,并返回数组下标,这个数组下标就是 fd
fd = get_unused_fd_flags(flags);
// 创建 file 结构体,并将其关联inode
file = anon_inode_getfile(...);

// 将 fd 和 file 关联
fd_install(fd, file);

我们继续看 fd_install 的代码:

// fs/file.c
/*
 * Install a file pointer in the fd array.
 *
 * The VFS is full of places where we drop the files lock between
 * setting the open_fds bitmap and installing the file in the file
 * array.  At any such point, we are vulnerable to a dup2() race
 * installing a file in the array before us.  We need to detect this and
 * fput() the struct file we are about to overwrite in this case.
 *
 * It should never happen - if we allow dup2() do it, _really_ bad things
 * will follow.
 *
 * This consumes the "file" refcount, so callers should treat it
 * as if they had called fput(file).
 */

void fd_install(unsigned int fd, struct file *file)
{
	struct files_struct *files = current->files;
	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);
		// 将 fdtable 中的指针数组 fd 的下标指向要设置的 file,这样在当前进程下,file 和 fd 就建立了对应关系
		// 当用户使用 fd 与内核发生交互时,内核可以根据 fd 在 current->fdt->fd[fd] 中得到对应的内部文件管理结构 struct file。
		rcu_assign_pointer(fdt->fd[fd], file);
		spin_unlock(&files->file_lock);
		return;
	}
	/* coupled with smp_wmb() in expand_fdtable() */
	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();
}

@JemmyH JemmyH added the done label Jun 7, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation done
Projects
None yet
Development

No branches or pull requests

1 participant