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线程同步/互斥过程详解

《Linux线程同步/互斥过程详解》文章讲解多线程并发访问导致竞态条件,需通过互斥锁、原子操作和条件变量实现线程安全与同步,分析死锁条件及避免方法,并介绍RAII封装技术提升资源管理效率... 目录01. 资源共享问题1.1 多线程并发访问1.2 临界区与临界资源1.3 锁的引入02. 多线程案例2.1 为

Oracle数据库定时备份脚本方式(Linux)

《Oracle数据库定时备份脚本方式(Linux)》文章介绍Oracle数据库自动备份方案,包含主机备份传输与备机解压导入流程,强调需提前全量删除原库数据避免报错,并需配置无密传输、定时任务及验证脚本... 目录说明主机脚本备机上自动导库脚本整个自动备份oracle数据库的过程(建议全程用root用户)总结

Linux如何查看文件权限的命令

《Linux如何查看文件权限的命令》Linux中使用ls-R命令递归查看指定目录及子目录下所有文件和文件夹的权限信息,以列表形式展示权限位、所有者、组等详细内容... 目录linux China编程查看文件权限命令输出结果示例这里是查看tomcat文件夹总结Linux 查看文件权限命令ls -l 文件或文件夹

idea的终端(Terminal)cmd的命令换成linux的命令详解

《idea的终端(Terminal)cmd的命令换成linux的命令详解》本文介绍IDEA配置Git的步骤:安装Git、修改终端设置并重启IDEA,强调顺序,作为个人经验分享,希望提供参考并支持脚本之... 目录一编程、设置前二、前置条件三、android设置四、设置后总结一、php设置前二、前置条件

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

Linux系统之lvcreate命令使用解读

《Linux系统之lvcreate命令使用解读》lvcreate是LVM中创建逻辑卷的核心命令,支持线性、条带化、RAID、镜像、快照、瘦池和缓存池等多种类型,实现灵活存储资源管理,需注意空间分配、R... 目录lvcreate命令详解一、命令概述二、语法格式三、核心功能四、选项详解五、使用示例1. 创建逻

Linux下在线安装启动VNC教程

《Linux下在线安装启动VNC教程》本文指导在CentOS7上在线安装VNC,包含安装、配置密码、启动/停止、清理重启步骤及注意事项,强调需安装VNC桌面以避免黑屏,并解决端口冲突和目录权限问题... 目录描述安装VNC安装 VNC 桌面可能遇到的问题总结描js述linux中的VNC就类似于Window

linux下shell脚本启动jar包实现过程

《linux下shell脚本启动jar包实现过程》确保APP_NAME和LOG_FILE位于目录内,首次启动前需手动创建log文件夹,否则报错,此为个人经验,供参考,欢迎支持脚本之家... 目录linux下shell脚本启动jar包样例1样例2总结linux下shell脚本启动jar包样例1#!/bin

Linux之platform平台设备驱动详解

《Linux之platform平台设备驱动详解》Linux设备驱动模型中,Platform总线作为虚拟总线统一管理无物理总线依赖的嵌入式设备,通过platform_driver和platform_de... 目录platform驱动注册platform设备注册设备树Platform驱动和设备的关系总结在 l

linux批量替换文件内容的实现方式

《linux批量替换文件内容的实现方式》本文总结了Linux中批量替换文件内容的几种方法,包括使用sed替换文件夹内所有文件、单个文件内容及逐行字符串,强调使用反引号和绝对路径,并分享个人经验供参考... 目录一、linux批量替换文件内容 二、替换文件内所有匹配的字符串 三、替换每一行中全部str1为st