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 rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

Linux搭建ftp服务器的步骤

《Linux搭建ftp服务器的步骤》本文给大家分享Linux搭建ftp服务器的步骤,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录ftp搭建1:下载vsftpd工具2:下载客户端工具3:进入配置文件目录vsftpd.conf配置文件4:

Java+AI驱动实现PDF文件数据提取与解析

《Java+AI驱动实现PDF文件数据提取与解析》本文将和大家分享一套基于AI的体检报告智能评估方案,详细介绍从PDF上传、内容提取到AI分析、数据存储的全流程自动化实现方法,感兴趣的可以了解下... 目录一、核心流程:从上传到评估的完整链路二、第一步:解析 PDF,提取体检报告内容1. 引入依赖2. 封装

Linux实现查看某一端口是否开放

《Linux实现查看某一端口是否开放》文章介绍了三种检查端口6379是否开放的方法:通过lsof查看进程占用,用netstat区分TCP/UDP监听状态,以及用telnet测试远程连接可达性... 目录1、使用lsof 命令来查看端口是否开放2、使用netstat 命令来查看端口是否开放3、使用telnet

Linux系统管理与进程任务管理方式

《Linux系统管理与进程任务管理方式》本文系统讲解Linux管理核心技能,涵盖引导流程、服务控制(Systemd与GRUB2)、进程管理(前台/后台运行、工具使用)、计划任务(at/cron)及常用... 目录引言一、linux系统引导过程与服务控制1.1 系统引导的五个关键阶段1.2 GRUB2的进化优

Linux查询服务器 IP 地址的命令详解

《Linux查询服务器IP地址的命令详解》在服务器管理和网络运维中,快速准确地获取服务器的IP地址是一项基本但至关重要的技能,下面我们来看看Linux中查询服务器IP的相关命令使用吧... 目录一、hostname 命令:简单高效的 IP 查询工具命令详解实际应用技巧注意事项二、ip 命令:新一代网络配置全

linux安装、更新、卸载anaconda实践

《linux安装、更新、卸载anaconda实践》Anaconda是基于conda的科学计算环境,集成1400+包及依赖,安装需下载脚本、接受协议、设置路径、配置环境变量,更新与卸载通过conda命令... 目录随意找一个目录下载安装脚本检查许可证协议,ENTER就可以安装完毕之后激活anaconda安装更