假设我想在系统调用中使用文件描述符(fd 号将通过参数提供)。如果用户空间程序使用此系统调用,会发生什么?操作系统会在哪里寻找这个特定的 fd?在当前进程的文件描述符中还是其他地方?
下面,我试图说明这一点。
+--------------+ +----++--------------+
| Kernel space | | fd || User space |
| | |list|| |
| handler <---------------- syscall(fd) |
| | | || |
+--------------+ +----++--------------+
答案1
文件描述符是一个整数,用于引用给定进程打开的所有文件中的一个文件。通常,这是由内核通过将文件描述符视为表中的索引来实现的。
我的答案的其余部分适用于 Linux。
在 Linux 中,每个有效的文件描述符都与一个struct file
.该结构包含指向 inode(文件的数据和元数据)的指针、进程在文件中的当前位置、操作列表(实际上是指向文件所在的文件系统实现的函数的指针)等。
file
为了从文件描述符中获取结构,Linux 内核按如下方式进行。我这里以系统调用为例read
。
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if (f.file) {
loff_t pos = file_pos_read(f.file);
ret = vfs_read(f.file, buf, count, &pos);
if (ret >= 0)
file_pos_write(f.file, pos);
fdput_pos(f);
}
return ret;
}
第一个操作是fdget_pos
.它从用户空间中的调用者处获取文件描述符作为参数,并获取相应的file
.它返回一个struct fd
定义如下:
struct fd {
struct file *file;
unsigned int flags;
};
这基本上是一个struct file
,有几个标志来记住放回结构时需要哪些操作。
现在,如何fdget_pos
运作。它实际上以奇怪的方式复杂化,但它归结为两个基本操作(为了简单起见,我没有在此处显示更多检查):
第一个是获取进程的文件表。该表可通过调用者进程结构中的指针获得(可通过访问current
):
struct files_struct *files = current->files;
下一个操作包括验证文件描述符的有效性:
if (fd < files->fdt->max_fds) // first of all, if the file descriptor is too big, then it cannot be valid
return files->fdt->fd[fd]; // otherwise, we return the pointer stored in the table of file descriptors (may be NULL)
return NULL;
该指针可能会在函数返回之前被消除(例如,如果进程的一个线程同时对同一文件描述符执行 aread
和另一个 a )。close
内核负责处理这个问题。
如果struct file
返回的指针fdget_pos
是NULL
,则意味着传递给系统调用的文件描述符无效。在这种情况下,系统调用返回错误代码EBADF
(“错误的文件描述符”)。
总而言之,文件描述符只是每个进程的文件描述符表中的索引。但是,仅仅取消引用它们是不够的,因为文件表中的条目可能是NULL
.此外,内核必须执行额外的检查来处理文件描述符上的竞争条件。