Linux内核有什么之块设备驱动有什么第三回 —— 邂逅的三个文件系统之一:devtmpfs

本文主要是介绍Linux内核有什么之块设备驱动有什么第三回 —— 邂逅的三个文件系统之一:devtmpfs,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

接前一篇文章:Linux内核有什么之块设备驱动有什么第二回 —— 块设备驱动初始化流程 vs 字符设备驱动初始化流程

本文内容参考:

34 | 块设备(上):如何建立代理商销售模式?-趣谈Linux操作系统-极客时间

Linux内核——块设备总结_linux do_open-CSDN博客

【Linux驱动】块设备驱动(一)—— 注册块设备_创建块设备-CSDN博客

【linux kernel】devtmpfs文件系统分析

特此致谢!

上一回借着与字符设备初始化流程的对比,讲解了块设备初始化的一般流程。本回开始对于此流程以及整个块设备驱动的细节进行深入解析。

和字符设备一样,块设备也是mknod创建设备结点、打开设备,以及读写设备流程。但与字符设备流程不同的是,块设备的流程多了文件系统及底层硬盘设备的读写。

块设备一般会被格式化为文件系统,而且还不止一个,而是一共三个,这就意味着有三套dentry和inode。因此,这是块设备的一个不好理解、容易搞晕的地方,同时也是块设备中的一个重点。也就是说,这是一个重点暨难点。

三个文件系统都是哪三个?分别是:(1)devtmpfs文件系统;(2)挂载(mount)时选择的具体文件系统(ext2/3/4、XFS、F2FS等);(3)bdev伪文件系统。由于挂载时选择的文件系统可能各种各样,因此严格来说,并不是三个文件系统,而是三类文件系统。接下来,跟随块设备的操作步骤一个一个来看。

和字符设备一样,块设备的mknod还是会在/dev/下创建设备结点。例如,笔者电脑Ubuntu虚拟机下/dev/下的各设备结点如下:

$ ls /dev/
autofs           disk      hugepages  loop14  loop-control  ptmx    sg2       tty10  tty21  tty32  tty43  tty54  tty8       ttyS17  ttyS28   uhid         vcsa   vcsu5
block            dma_heap  hwrng      loop15  mapper        pts     shm       tty11  tty22  tty33  tty44  tty55  tty9       ttyS18  ttyS29   uinput       vcsa1  vcsu6
bsg              dmmidi    initctl    loop16  mcelog        random  snapshot  tty12  tty23  tty34  tty45  tty56  ttyprintk  ttyS19  ttyS3    urandom      vcsa2  vfio
btrfs-control    dri       input      loop17  mem           rfkill  snd       tty13  tty24  tty35  tty46  tty57  ttyS0      ttyS2   ttyS30   userfaultfd  vcsa3  vga_arbiter
bus              ecryptfs  kmsg       loop2   midi          rtc     sr0       tty14  tty25  tty36  tty47  tty58  ttyS1      ttyS20  ttyS31   userio       vcsa4  vhci
cdrom            fb0       log        loop3   mqueue        rtc0    sr1       tty15  tty26  tty37  tty48  tty59  ttyS10     ttyS21  ttyS4    vcs          vcsa5  vhost-net
char             fd        loop0      loop4   net           sda     stderr    tty16  tty27  tty38  tty49  tty6   ttyS11     ttyS22  ttyS5    vcs1         vcsa6  vhost-vsock
console          fd0       loop1      loop5   null          sda1    stdin     tty17  tty28  tty39  tty5   tty60  ttyS12     ttyS23  ttyS6    vcs2         vcsu   vmci
core             full      loop10     loop6   nvram         sda2    stdout    tty18  tty29  tty4   tty50  tty61  ttyS13     ttyS24  ttyS7    vcs3         vcsu1  vsock
cpu              fuse      loop11     loop7   port          sda3    tty       tty19  tty3   tty40  tty51  tty62  ttyS14     ttyS25  ttyS8    vcs4         vcsu2  zero
cpu_dma_latency  hidraw0   loop12     loop8   ppp           sg0     tty0      tty2   tty30  tty41  tty52  tty63  ttyS15     ttyS26  ttyS9    vcs5         vcsu3  zfs
cuse             hpet      loop13     loop9   psaux         sg1     tty1      tty20  tty31  tty42  tty53  tty7   ttyS16     ttyS27  udmabuf  vcs6         vcsu4

其中,就既包括字符设备,又包括块设备。比如其中的ttyS0为字符设备(第一个字母为c,代表char),而/dev/sda1为块设备(第一个字母为b,代表block),如下所示:

$ ls /dev/ttyS0 -l
crw-rw---- 1 root dialout 4, 64  1月 26 21:34 /dev/ttyS0$ ls /dev/sda1 -l
brw-rw---- 1 root disk 8, 1  1月 26 21:34 /dev/sda1

这一点经常玩Linux尤其是驱动的想必不陌生。

/dev路径下是/devtmpfs文件系统,如下所示:

$ mount | grep devtmpfs
udev on /dev type devtmpfs (rw,nosuid,relatime,size=2972608k,nr_inodes=743152,mode=755,inode64)

这就是块设备邂逅的第一个文件系统。

这里先来了解一下devtmpfs的相关知识。

devtmpfs简介

devtmpfs文件系统与传统的tmpfs文件系统类似,都是基于内存的文件系统。它将设备结点以文件的形式表示,并提供了对设备结点的访问和管理通过devtmpfs文件系统,内核可以自动创建和删除设备节点,而无需依赖外部工具

devtmpfs文件系统的主要作用是为了方便设备的管理和访问。在Linux系统中,每个设备都对应一个设备结点,通过设备结点可以与设备进行通信和操作。devtmpfs文件系统可以自动创建和管理这些设备结点,使得设备的管理更加方便和高效。

devtmpfs的作用是在Linux内核启动早期建立一个初步的/dev,令一般启动程序不用等待udev(udev 是Linux Kernel 2.6系列的设备管理器。它主要的功能是管理/dev目录底下的设备结点),从而缩短GNU/Linux的开机时间。

devtmpfs允许内核在初始化时,即在驱动程序核心设备注册之前创建tmpfs。每个主/次设备都将在这个tmpfs实例中创建它的一个设备结点。当rootfs被内核挂载后,被填充的tmpfs被挂载在/dev路径下。在initramfs中,执行/sbin/init之前,可以将它移动到指定的根文件系统中。

devtmpfs初始化

在Linux内核中,对devtmpfs文件系统的初始化由devtmpfs_init函数完成,该函数会创建devtmpfs文件系统实例,然后各个驱动模块核心会将设备结点添加到该文件系统中。devtmpfs_init函数在drivers/base/devtmpfs.c中,代码如下:

/** Create devtmpfs instance, driver-core devices will add their device* nodes here.*/
int __init devtmpfs_init(void)
{char opts[] = "mode=0755";int err;mnt = vfs_kern_mount(&internal_fs_type, 0, "devtmpfs", opts);if (IS_ERR(mnt)) {pr_err("unable to create devtmpfs %ld\n", PTR_ERR(mnt));return PTR_ERR(mnt);}err = register_filesystem(&dev_fs_type);if (err) {pr_err("unable to register devtmpfs type %d\n", err);return err;}thread = kthread_run(devtmpfsd, &err, "kdevtmpfs");if (!IS_ERR(thread)) {wait_for_completion(&setup_done);} else {err = PTR_ERR(thread);thread = NULL;}if (err) {pr_err("unable to create devtmpfs %d\n", err);unregister_filesystem(&dev_fs_type);thread = NULL;return err;}pr_info("initialized\n");return 0;
}

从devtmpfs_init函数代码可见,与几乎所有的文件系统注册一样,在函数中都会调用register_filesystem函数向Linux内核注册文件系统。devtmpfs文件系统类型描述符dev_fs_type定义如下(同文件中):

static struct file_system_type dev_fs_type = {.name = "devtmpfs",.mount = public_dev_mount,
};

对于devtmpfs文件系统的知识补强就到这里。回到块设备驱动中来。

上边提到,mknod会为每一个块设备文件创建一个设备结点,也就意味着为其分配一个特殊的inode(索引节点),这一点也和字符设备一样。这一步的具体实现函数为init_special_inode,在fs/inode.c中,代码如下:

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{inode->i_mode = mode;if (S_ISCHR(mode)) {inode->i_fop = &def_chr_fops;inode->i_rdev = rdev;} else if (S_ISBLK(mode)) {if (IS_ENABLED(CONFIG_BLOCK))inode->i_fop = &def_blk_fops;inode->i_rdev = rdev;} else if (S_ISFIFO(mode))inode->i_fop = &pipefifo_fops;else if (S_ISSOCK(mode));	/* leave it no_open_fops */elseprintk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"" inode %s:%lu\n", mode, inode->i_sb->s_id,inode->i_ino);
}
EXPORT_SYMBOL(init_special_inode);

init_special_inode函数用于初始化一个特殊类型的inode结构。inode(索引结点)是Linux文件系统中的一个重要概念,用于表示文件或目录的元数据信息。init_special_inode函数通常在文件系统实现中被调用,用于创建特殊类型的文件或设备结点。例如,在ext4文件系统中,可以通过调用init_special_inode函数来创建字符设备或块设备结点。

在Linux内核源码下搜索“init_special_inode”关键字,会发现各个文件系统中都调用了它。

init_special_inode函数中为字符设备分配特殊的inode,走的是S_ISCHR分支;而为块设备分配特殊inode,则走的是S_ISBLK分支。为了方便,再次贴一下init_special_inode函数的代码:

void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{inode->i_mode = mode;if (S_ISCHR(mode)) {inode->i_fop = &def_chr_fops;inode->i_rdev = rdev;} else if (S_ISBLK(mode)) {if (IS_ENABLED(CONFIG_BLOCK))inode->i_fop = &def_blk_fops;inode->i_rdev = rdev;} else if (S_ISFIFO(mode))inode->i_fop = &pipefifo_fops;else if (S_ISSOCK(mode));	/* leave it no_open_fops */elseprintk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"" inode %s:%lu\n", mode, inode->i_sb->s_id,inode->i_ino);
}
EXPORT_SYMBOL(init_special_inode);

这里要注意,inode中的i_rdev即inode->i_rdev被设置成了块设备的设备号rdev,其类型为dev_t,这一点后文书还会用到,这里埋一个伏笔。dev_t的定义在include/linux/types.h中:

typedef __kernel_dev_t		dev_t;

__kernel_dev_t的定义也在同文件中,如下:

typedef u32 __kernel_dev_t;

对于块设备来说,特殊inode的默认file_operations即inode->i_fop是def_blk_fops(字符设备是def_chr_fops)。

def_blk_fops的定义和初始化在block/fops.c中,如下:

const struct file_operations def_blk_fops = {.open		= blkdev_open,.release	= blkdev_release,.llseek		= blkdev_llseek,.read_iter	= blkdev_read_iter,.write_iter	= blkdev_write_iter,.iopoll		= iocb_bio_iopoll,.mmap		= blkdev_mmap,.fsync		= blkdev_fsync,.unlocked_ioctl	= blkdev_ioctl,
#ifdef CONFIG_COMPAT.compat_ioctl	= compat_blkdev_ioctl,
#endif.splice_read	= filemap_splice_read,.splice_write	= iter_file_splice_write,.fallocate	= blkdev_fallocate,
};

与字符设备一样,块设备也有打开、关闭、读写函数(指针)。不过与字符设备不同,虽然块设备有这些函数,但常规操作不会这样“raw”操作,而是会将(这个)块设备文件mount到一个文件夹下面。所谓“raw”操作,就是指直接打开/dev/下的块设备结点进行操作,而不是通过其挂载点进行相关操作。笔者的职业生涯中,这样干的情况见过,但确实不多,一般都是文件系统解决不了、无法进行块设备的相关操作需求时才这么干。

简单看一下open函数指针所指向的clkdev_open函数,其也在block/fops.c中,代码如下:

static int blkdev_open(struct inode *inode, struct file *filp)
{struct bdev_handle *handle;blk_mode_t mode;/** Preserve backwards compatibility and allow large file access* even if userspace doesn't ask for it explicitly. Some mkfs* binary needs it. We might want to drop this workaround* during an unstable branch.*/filp->f_flags |= O_LARGEFILE;filp->f_mode |= FMODE_BUF_RASYNC | FMODE_CAN_ODIRECT;mode = file_to_blk_mode(filp);handle = bdev_open_by_dev(inode->i_rdev, mode,mode & BLK_OPEN_EXCL ? filp : NULL, NULL);if (IS_ERR(handle))return PTR_ERR(handle);if (bdev_nowait(handle->bdev))filp->f_mode |= FMODE_NOWAIT;filp->f_mapping = handle->bdev->bd_inode->i_mapping;filp->f_wb_err = filemap_sample_wb_err(filp->f_mapping);filp->private_data = handle;return 0;
}

至此,块设备驱动邂逅的第一个文件系统devtmpfs就讲解到这里了,下回讲其邂逅的第二个文件系统。

这篇关于Linux内核有什么之块设备驱动有什么第三回 —— 邂逅的三个文件系统之一:devtmpfs的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/820583

相关文章

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级

【Linux】应用层http协议

一、HTTP协议 1.1 简要介绍一下HTTP        我们在网络的应用层中可以自己定义协议,但是,已经有大佬定义了一些现成的,非常好用的应用层协议,供我们直接使用,HTTP(超文本传输协议)就是其中之一。        在互联网世界中,HTTP(超文本传输协议)是一个至关重要的协议,他定义了客户端(如浏览器)与服务器之间如何进行通信,以交换或者传输超文本(比如HTML文档)。