LV05-03-Kernel-05-03-01-open函数简介

本文主要是kernel——open函数简介的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
https://elixir.bootlin.com/u-boot/latest/source uboot源码

前面大概已经了解了文件描述符分配的相关内容。那么这个文件描述符对应哪个设备?这个的关联就是open函数去做的啦。

一、open函数?

1. open怎么使用

我们经常在一个进程中使用open()来获取一个文件描述符fd,然后通过该fd去进行一些write()、read()操作。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
#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为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
#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命令看一下:

1
2
3
gcc main.c -Wall
touch test.txt
strace -o syscall ./a.out

会发现有这么一行:

1
openat(AT_FDCWD, "test.txt", O_RDONLY)  = 3

从这里可以看出,调用的是openat这个系统调用。

二、怎么关联到设备?

后面写驱动的时候就会接触到每个驱动都会有自己的操作函数集,我们通过open函数可以打开一个设备,然后将文件描述符与对应的设备关联起来,通过这个fd文件描述符,就可以找到设备的操作函数集,进而控制设备,大概的一个关系如下:

image-20241214120732685

进程通过系统调用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 file_operation- fs.h - include/linux/fs.h - Linux source code v4.19.71 - Bootlin Elixir Cross Referencer

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。

函数调用关系如下(与上面有些函数名有些出入,不过流程都是一样的):

proxy

结合序列图的调用序号, 来看一看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博客

文件描述符、文件描述符表、目录项、索引节点之间的总结联系_文件描述符表与文件目录的关系-CSDN博客

探索文件描述符(fd)与FILE结构体之间的关系_linux 内核 fd和file结构-CSDN博客