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之systemV共享内存方式

《Linux之systemV共享内存方式》:本文主要介绍Linux之systemV共享内存方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、工作原理二、系统调用接口1、申请共享内存(一)key的获取(二)共享内存的申请2、将共享内存段连接到进程地址空间3、将

快速修复一个Panic的Linux内核的技巧

《快速修复一个Panic的Linux内核的技巧》Linux系统中运行了不当的mkinitcpio操作导致内核文件不能正常工作,重启的时候,内核启动中止于Panic状态,该怎么解决这个问题呢?下面我们就... 感谢China编程(www.chinasem.cn)网友 鸢一雨音 的投稿写这篇文章是有原因的。为了配置完

usb接口驱动异常问题常用解决方案

《usb接口驱动异常问题常用解决方案》当遇到USB接口驱动异常时,可以通过多种方法来解决,其中主要就包括重装USB控制器、禁用USB选择性暂停设置、更新或安装新的主板驱动等... usb接口驱动异常怎么办,USB接口驱动异常是常见问题,通常由驱动损坏、系统更新冲突、硬件故障或电源管理设置导致。以下是常用解决

Linux命令之firewalld的用法

《Linux命令之firewalld的用法》:本文主要介绍Linux命令之firewalld的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux命令之firewalld1、程序包2、启动firewalld3、配置文件4、firewalld规则定义的九大

Linux之计划任务和调度命令at/cron详解

《Linux之计划任务和调度命令at/cron详解》:本文主要介绍Linux之计划任务和调度命令at/cron的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux计划任务和调度命令at/cron一、计划任务二、命令{at}介绍三、命令语法及功能 :at

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

Linux内核参数配置与验证详细指南

《Linux内核参数配置与验证详细指南》在Linux系统运维和性能优化中,内核参数(sysctl)的配置至关重要,本文主要来聊聊如何配置与验证这些Linux内核参数,希望对大家有一定的帮助... 目录1. 引言2. 内核参数的作用3. 如何设置内核参数3.1 临时设置(重启失效)3.2 永久设置(重启仍生效

kali linux 无法登录root的问题及解决方法

《kalilinux无法登录root的问题及解决方法》:本文主要介绍kalilinux无法登录root的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录kali linux 无法登录root1、问题描述1.1、本地登录root1.2、ssh远程登录root2、

Linux ls命令操作详解

《Linuxls命令操作详解》通过ls命令,我们可以查看指定目录下的文件和子目录,并结合不同的选项获取详细的文件信息,如权限、大小、修改时间等,:本文主要介绍Linuxls命令详解,需要的朋友可... 目录1. 命令简介2. 命令的基本语法和用法2.1 语法格式2.2 使用示例2.2.1 列出当前目录下的文

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro