LV200-open函数简介
前面大概已经了解了文件描述符分配的相关内容。那么这个文件描述符对应哪个设备?这个的关联就是 open 函数去做的啦。
一、open 函数?
1. open 怎么使用
我们经常在一个进程中使用 open()来获取一个文件描述符 fd,然后通过该 fd 去进行一些 write()、read()操作。例如:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
int fd = open("test.txt", O_CREAT | O_RDWR);
close(fd);
return 0;
}我们操作字符设备的时候也一样,open()的原理是通过给定的文件路径/dev/dev_node,从而找到该文件路径所对应的 inode 信息,最后生成一个 struct file 结构体,该结构体在进程的打开文件列表中,返回的 fd 信息就是这个打开文件列表中的下标索引,所以说 fd 永远不会小于 0。
2. 系统调用?
open 用的哪个系统调用?以这个 demo 为例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
int fd = open("test.txt", O_RDONLY);
close(fd);
return 0;
}使用 strace 命令看一下:
gcc main.c -Wall
touch test.txt
strace -o syscall ./a.out会发现有这么一行:
openat(AT_FDCWD, "test.txt", O_RDONLY) = 3从这里可以看出,调用的是 openat 这个系统调用。
二、怎么关联到设备?
后面写驱动的时候就会接触到每个驱动都会有自己的操作函数集,我们通过 open 函数可以打开一个设备,然后将文件描述符与对应的设备关联起来,通过这个 fd 文件描述符,就可以找到设备的操作函数集,进而控制设备,大概的一个关系如下:

进程通过系统调用 open()来打开一个文件,实质上是获得一个文件描述符,以便于进程通过文件描述符来读写该文件、进程打开文件时,会为该文件创建一个 file 对象,并将一个指向该 file 对象的指针存入进程描述符表(进程描述符数组),进而确定了打开文件的文件描述符(数组下标)。
open()系统调用是在内核里通过 do_sys_open()实现的,do_sys_open()将创建文件的 dentry、inode 和 file 对象。创建 file 对象时,将 file 对象 f_op 指向了所属文件系统的操作函数集 file_operations,而该函数集又来自具体文件的 i 节点,于是虚拟文件系统就与实际文件系统的操作就衔接起来了。并在 file_struct 结构体的进程打开文件描述符表 fd_array[NR_OPEN_DEFAULT]中查找一个空闲表项(也就是此时数组中最小的未被占用的表项),然后返回该表项的下标(文件描述符)。
相关的一些结构体定义如下:
struct task_struct - sched.h - include/linux/sched.h
struct fs_struct - fs_struct.h - include/linux/fs_struct.h
struct files_struct - fdtable.h - include/linux/fdtable.h
struct file - fs.h - include/linux/fs.h
struct dentry - dcache.h - include/linux/dcache.h
三、open 函数的调用流程
open 调用做了下面的几件事(后面会再详细去学习,这里大概了解一下):
(1)分配槽位, 即 fd 对应的索引位置;
(2)分配 file 对象;
(3)给 file 中的操作函数表赋值,struct file 的 f_ops 赋值为/dev/dev_node 字符设备所对应驱动的 struct file_operations。
函数调用关系如下(与上面有些函数名有些出入,不过流程都是一样的):

结合序列图的调用序号, 来看一看 open 调用主要做的几件事情.
step 3: get_unused_fd_flags(), 获取空闲的 fd 槽位
step 6: get_empty_filp(), 分配 file 对象
step 7: link_path_walk(), 解析文件路径
step 9: lookup_fast(), 从 cache 中查找 dentry(目录项对象)
step 13: lookup(), cache 中找不到的情况下, 调用文件所在文件系统的 lookup 函数获取
step 15: do_dentry_open(), 这里把 file 对象和具体文件系统的 inode 关联起来, 并把 file 中的操作函数表指向 inode 提供的操作表
step 16: open(), 调用文件系统提供的 open 函数, 完成一些文件系统相关的初始化, 有些文件系统的 open 函数可能为空, 即不做相关操作
step 17: fd_install(), 把 fd 和 file 对象关联起来。
最后, 就完成了 open 的任务, 应用层在获取到 fd 后, 就可以做后续的操作了. 很显然, 后面的操作都是通过 file 对象中的函数表来完成的, 这样, 就完成了 vfs 层和具体的文件系统的连接.
参考资料
Linux 中 open 命令实现原理以及源码分析_linux open-CSDN 博客
linux 文件描述符分配实现详解(基于 ARM 处理器)_fdtable-CSDN 博客