Linux MTD子系统(二)——mtdblock驱动分析

news2024/12/27 17:48:10

在之前的文章Linux MTD子系统(一)中有提到过mtd块设备,mtd块设备是在MTD设备之上模拟的块设备。
它的作用实际上只有一个——便于我们使用mount(umount)挂载(卸载)MTD设备中的文件系统,例如yaffs2,JFFS2等等。

本文将介绍mtdblock是如何实现模拟块设备的,以及它与mtd设备之间的关系。
本文基于linux-5.10.181内核代码分析。

mtd设备节点

当我们查看/dev/mtd*时,通常情况下,我们可以看下类似如下的设备:

root@OpenWrt:~# ls /dev/mtd* -alh
crw-------    1 root     root       90,   0 Jan  1  1970 /dev/mtd0
crw-------    1 root     root       90,   1 Jan  1  1970 /dev/mtd0ro
crw-------    1 root     root       90,   2 Jan  1  1970 /dev/mtd1
crw-------    1 root     root       90,   3 Jan  1  1970 /dev/mtd1ro
crw-------    1 root     root       90,   4 Jan  1  1970 /dev/mtd2
crw-------    1 root     root       90,   5 Jan  1  1970 /dev/mtd2ro
brw-------    1 root     root       31,   0 Jan  1  1970 /dev/mtdblock0
brw-------    1 root     root       31,   4 Jan  1  1970 /dev/mtdblock1
brw-------    1 root     root       31,   8 Jan  1  1970 /dev/mtdblock2

实际上/dev/mtd0/dev/mtd0ro,/dev/mtdblock0代表的是同一个MTD分区,但是/dev/mtd0/dev/mtd0ro都是字符设备,其中/dev/mtd0ro是只读字符设备,/dev/mtdblock0是块设备。
常见的mtd-utilsnand_write等工具只能操作/dev/mtdX字符设备,因为只有字符设备才支持ioctl操作。

核心数据结构

mtdblock_tr

mtdblock_tr变量不仅定义了mtdblock相关ops,还定义了mtdblock

  • 名字(mtdblock)
  • 主设备号(MTD_BLOCK_MAJOR为31)
  • 块大小(固定为512字节)
static struct mtd_blktrans_ops mtdblock_tr = {
	.name		= "mtdblock",
	.major		= MTD_BLOCK_MAJOR,
#ifdef CONFIG_FIT_PARTITION
	.part_bits	= 2,
#else
	.part_bits	= 0,
#endif
	.blksize 	= 512,
	.open		= mtdblock_open,
	.flush		= mtdblock_flush,
	.release	= mtdblock_release,
	.readsect	= mtdblock_readsect,
	.writesect	= mtdblock_writesect,
	.add_mtd	= mtdblock_add_mtd,
	.remove_dev	= mtdblock_remove_dev,
	.owner		= THIS_MODULE,
};

mtdblock_openmtdblock_flush等函数都是标准的。其中mtdblock_tr .readsectmtdblock_tr.writesect是mtdblock的读写函数指针,最终对mtdblock的读写也是调用的这两个函数。

mtd_blktrans_dev

mtd_blktrans_dev 是一个抽象的设备,可以把它称为转换设备,它用于记录将 MTD 设备转换为块设备的一些基本信息。

struct mtd_blktrans_dev {
	struct mtd_blktrans_ops *tr;
	struct list_head list;
	struct mtd_info *mtd;//指向mtd设备
	struct mutex lock;
	int devnum;
	bool bg_stop;
	unsigned long size;//块转换设备的大小(以字节为单位)
	int readonly;//块转换设备是否是只读的,如果是则为1,否则为0
	int open;//块转换设备open 引用计数器
	struct kref ref;
	struct gendisk *disk;//指向磁盘设备或分区
	struct attribute_group *disk_attributes;
	struct request_queue *rq;
	struct list_head rq_list;
	struct blk_mq_tag_set *tag_set;
	spinlock_t queue_lock;
	void *priv;
	fmode_t file_mode;
};

mtdblk_dev

mtdblock模拟块设备用到的缓存区就是mtdblk_dev->cache_data,cache_size是缓存区的大小,通常等于MTD设备的一个擦除块大小,cache_offset是缓存区偏移量,cache_state是缓存区的状态标志,当状态为STATE_DIRTY 就需要调用flush把缓存区的数据写到Flash。

struct mtdblk_dev {
	struct mtd_blktrans_dev mbd;
	int count;
	struct mutex cache_mutex;
	unsigned char *cache_data;
	unsigned long cache_offset;
	unsigned int cache_size;
	enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state;//缓存状态
};

mtd_info

mtd_info 不做过多介绍,它代表一个mtd设备或者分区,重要的是它是字符设备

struct mtd_info {
	u_char type;
	uint32_t flags;
	uint64_t size;	 // Total size of the MTD
	uint32_t erasesize;
	/* "Minor" (smallest) erase size supported by the whole device */
	uint32_t erasesize_minor;
	......
	}

mtdblock注册过程

mtdblock的注册流程大致如下图所示,核心成员有四个mtdblock_tr,mtdblk_dev,mtd_blktrans_dev,mtd_info搞清楚了这四个成员之间的关系,基本上就通晓了mtdblock和mtd之前的关系。

在这里插入图片描述

下面一步步剖析上图的过程:

1> register_mtd_blktrans

首先mtdblock驱动的入口函数调用了register_mtd_blktrans(&mtdblock_tr)
register_mtd_blktrans主要执行如下操作:

  • 注册mtd_notifier
  • 注册块设备(主设备号31,name:/dev/mtdblock)
  • 初始化mtdblock_tr->devs链表头
  • 将mtdblock_tr添加到blktrans_majors
  • 遍历mtd设备Table,依次注册mtdblk_dev

2> mtdblock_add_mtd

上一步的最后遍历mtd设备table注册mtdblk_dev对应的源码如下:

int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
	mtd_for_each_device(mtd)
		if (mtd->type != MTD_ABSENT)
			tr->add_mtd(tr, mtd);

tr->add_mtdmtdblock_tr->add_mtd,也就是mtdblock_add_mtd,此函数的作用是建立mtdmtd_blktrans_dev 之间的联系。

static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
{
	struct mtdblk_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL);

	if (!dev)
		return;

	dev->mbd.mtd = mtd;
	dev->mbd.devnum = mtd->index;

	dev->mbd.size = mtd->size >> 9;
	dev->mbd.tr = tr;

	if (!(mtd->flags & MTD_WRITEABLE))
		dev->mbd.readonly = 1;

	if (add_mtd_blktrans_dev(&dev->mbd))
		kfree(dev);
}
  • 申请mtdblk_dev
  • 将mtdblk_dev下属成员mtd_blktrans_dev 与mtd设备关联起来
  • mtd_blktrans_dev的子设备号就是mtd的index,这也是为什么/dev/mtd0对应/dev/mtdblock0的原因
  • mtd_blktrans_dev大小设置为mtd->size/512,也就是按照512字节每个扇区计算大小
  • 如果mtd设备是只读的,mtd_blktrans_dev同样要设置只读标记
  • 调用add_mtd_blktrans_dev注册mtd_blktrans_dev

3> add_mtd_blktrans_dev

add_mtd_blktrans_dev()是注册mtdblock最关键的函数,它的作用是申请并注册块设备,并建立块设备和mtd_blktrans_dev之间的联系。
其中块设备申请与注册部分的源码如下:

int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
**	/* Create gendisk */
	gd = alloc_disk(1 << tr->part_bits);

	if (!gd)
		goto error2;

	new->disk = gd; //建立块设备和mtd_blktrans_dev 的关联
	gd->private_data = new;
	gd->major = tr->major;
	gd->first_minor = (new->devnum) << tr->part_bits;
	gd->fops = &mtd_block_ops;

	snprintf(gd->disk_name, sizeof(gd->disk_name),
		 "%s%d", tr->name, new->devnum);

	set_capacity(gd, ((u64)new->size * tr->blksize) >> 9);//设置块设备容量,单位是扇区,默认扇区大小是512

	/* Create the request queue */
	spin_lock_init(&new->queue_lock);
	INIT_LIST_HEAD(&new->rq_list);

	new->tag_set = kzalloc(sizeof(*new->tag_set), GFP_KERNEL);
	if (!new->tag_set)
		goto error3;

	new->rq = blk_mq_init_sq_queue(new->tag_set, &mtd_mq_ops, 2,
				BLK_MQ_F_SHOULD_MERGE | BLK_MQ_F_BLOCKING);//初始化块设备软件队列
	if (IS_ERR(new->rq)) {
		ret = PTR_ERR(new->rq);
		new->rq = NULL;
		goto error4;
	}

	if (tr->flush)
		blk_queue_write_cache(new->rq, true, false);//启用写缓存,禁止强制单元访问

	new->rq->queuedata = new;
	
	//设置逻辑块大小,为了减少存储开销和提高读写性能,可能会将逻辑块大小设置得比较大,如4KB或8KB。
	//而在一些需要频繁访问小文件或小数据结构的应用场景中,则可以将逻辑块大小调整到更小的值(如256字节或128字节)。
	blk_queue_logical_block_size(new->rq, tr->blksize);
	
	//指示块设备为非旋转介质,即不是机械硬盘,不需要IO调度
	blk_queue_flag_set(QUEUE_FLAG_NONROT, new->rq);
	
	//允许块设备驱动程序根据需要添加随机数种子,以帮助分散IO请求并提高系统性能
	blk_queue_flag_clear(QUEUE_FLAG_ADD_RANDOM, new->rq);

	if (tr->discard) {
		blk_queue_flag_set(QUEUE_FLAG_DISCARD, new->rq);//队列支持 TRIM 和 DISCARD 命令,可以在需要时向底层存储介质发出删除数据块的指令
		blk_queue_max_discard_sectors(new->rq, UINT_MAX);//设置队列支持的最大 DISCARD 命令扇区数目
	}

	gd->queue = new->rq;//

	if (new->readonly)
		set_disk_ro(gd, 1);

mtdblock读写流程

mtdblock由于是模拟的块设备,它的读写流程就是块设备读写流程,由于块设备读写比较复杂,这里不再详细介绍。
这里主要介绍块设备的读写请求到达mtdblock设备层之后的处理流程。

前面有提到注册mtd_blktrans_dev 设备时会初始化一个块设备的请求队列(new->rq)。

static const struct blk_mq_ops mtd_mq_ops = {
	.queue_rq	= mtd_queue_rq,
};

new->rq = blk_mq_init_sq_queue(new->tag_set, &mtd_mq_ops, 2,
				BLK_MQ_F_SHOULD_MERGE | BLK_MQ_F_BLOCKING);//初始化块设备软件队列

这里面有个非常重要的结构体——mtd_mq_ops ,此数据结构用于块层与块设备层进行通信,总结就是上层的块请求最终都是调用这个mtd_mq_ops ->queue_rq进行处理。
mtd_queue_rq处理流程大致如下(并非完整流程,完整流程请参考源码)

mtd_queue_rq()
	mtd_blktrans_work()
		do_blktrans_request()
			if REQ_OP_FLUSH
				mtdblock_flush
			case REQ_OP_DISCARD
				tr->discard
			case REQ_OP_READ
				mtdblock_readsect
			case REQ_OP_WRITE
				mtdblock_writesect

由于是模拟的块设备,实际上最多只支持REQ_OP_FLUSH,REQ_OP_DISCARD,REQ_OP_READ,REQ_OP_WRITE这4中块设备IO请求。

mtdblock_readsect

mtdblock_readsect() 大致流程如下:

mtdblock_readsect() 
	do_cached_read()
		mtd_read() 
			mtd_read_oob() 
				mtd_read_oob_std() 
					sst25l_read()//后面调用芯片厂商自己实现的读取FLASH接口

mtdblock_writesect

mtdblock_writesect() 大致流程如下:

mtdblock_writesect()
	do_cached_write()
		mtd_write() 
			mtd_write_oob() 
				mtd_write_oob_std() 
					sst25l_write()//后面调用芯片厂商自己实现的读取FLASH接口

总结

  • mtdX 和 mtdblockX实际上是同一个设备,mtdX是字符设备,mtdblockX是块设备
  • mtdblockX存在的目的主要是为了挂载存在Flash里面的文件系统(例如yaffs2,jffs2)
  • mtdblock设备的读写最终也是调用mtd设备的操作函数集

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/658066.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LIN总线协议-调度表

文章目录 一、调度表只有一个调度表时&#xff0c;采用循环执行三个调度表存在时&#xff0c;顺序执行调度表发生中断 二、总结 一、调度表 调度表规定了总线上帧的传输次序&#xff08;调度Header&#xff09;以及各帧在总线上的传输时间。 调度表位于主机节点&#xff0c;主…

算法刷题-字符串-左旋转字符串

反转个字符串还有这么多用处&#xff1f; 题目&#xff1a;剑指Offer58-II.左旋转字符串 力扣题目链接 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如&#xff0c;输入字符串"abcdefg"和数字2…

C++算法:单源最短路径Dijkstra

文章目录 前言一、Dijkstra算法思想二、算法实现1、建立图2、代码实现 总结 前言 如果你有一份北京地图&#xff0c;想从中关村走到三元桥&#xff0c;那么怎样能找出实现这一目的的最短路径呢?一种可能的方法就是将这两点之间所有的路线都找出来&#xff0c;然后求出每条路线…

openSUSE项目近日宣布openSUSE Leap 15.5的发布和全面供应

openSUSE项目近日宣布openSUSE Leap 15.5的发布和全面供应&#xff0c;该版本是openSUSE变体的最新稳定版本&#xff0c;针对那些希望为其个人电脑提供基于SUSE Linux Enterprise 15的经过良好测试的操作系统的用户。 openSUSE Leap 15.5是在openSUSE Leap 15.4的一年后推出的&…

Vue中如何进行音频可视化与音频频谱展示

Vue中如何进行音频可视化与音频频谱展示 随着音频应用程序的不断发展&#xff0c;音频可视化和音频频谱展示成为了重要的功能。在Vue应用程序中实现音频可视化和音频频谱展示可以帮助用户更好地了解音频文件的内容和特征。本文将介绍如何在Vue应用程序中实现音频可视化和音频频…

Opensearch基本介绍

OpenSearch 是一个社区驱动的开源搜索和分析套件&#xff0c;开发人员使用该套件来摄取、搜索、可视化和分析数据。 OpenSearch 由数据存储和搜索引擎 (OpenSearch)、可视化和用户界面 (OpenSearch Dashboards) 以及服务器端数据收集器 (Data Prepper) 组成。 用户可以使用一系…

把数字中国,建立在行业感知的底座上

5月23日&#xff0c;国家互联网信息办公室发布了《数字中国发展报告&#xff08;2022年&#xff09;》。报告显示&#xff0c;2022年中国数字经济规模达到50.2万亿元&#xff0c;占国内生产总值比重提升至41.5%&#xff0c;总量居世界第二。如今数字中国最主要的发展挑战&#…

MIFARE - 1

2一般说明 飞利浦根据ISO/IEC 14443A开发了用于非接触式智能卡的MIFAREMF1 IC S50。通信层&#xff08;MIFARERF接口&#xff09;符合ISO/IEC 14443A标准的第2部分和第3部分。安全层采用经过现场验证的CRYPTO1流密码&#xff0c;用于MIFAREClassic系列的安全数据交换。 MIFARE…

GPT中的temperature参数不是用在对话的而是用在调用OPEN API过程中的

前言 自从吴恩达OPENAI《ChatGPT 提示工程》放出后,各个层面反响热列。很多人看到了temperature这个参数,都以为在对话中或者说对话的末尾放上一个temperature=0-2的值就可以达到让GPT极大的发挥出自我创造能力、甚至写文章天马行空。 笔者这边觉得有义务指出这种用法是完全…

OpenAI ChatGPT 使用示例(程序员)

1.编程应用 1.1. 生成例子代码(Coding Generation) ChatGPT帮助我们生产我们需要的例子代码。而且准确率很高。即使你不懂某一种语言也没关系&#xff0c;一定程度上较低了程序员的的门槛。 我有三组数据&#xff0c;第一组是星期一到星期五&#xff0c;第二组是这一天的具体…

第七十八天学习记录:高等数学:微分方程(宋浩板书)

微分方程&#xff08;Differential equation&#xff09;是描述自然现象中变量之间关系的数学语言。它是以函数、导数、微分等数学概念为基础的方程&#xff0c;揭示了自然现象中变量之间的内在联系。微分方程在物理学、工程学、生物学、经济学、统计学等各领域都有广泛的应用。…

C++线程库(2)

C线程库&#xff08;2&#xff09; 线程同步互斥锁条件变量与互斥锁的搭配使用举例1举例2举例3 线程同步 在C线程库&#xff08;1&#xff09;的博客中说了互斥量只能解决多个线程访问共享资源的问题&#xff0c;但是很明显没有次序感&#xff0c;而线程安全就是不同线程访问资…

最短路径算法-迪杰斯特拉(Dijkstra)算法(记录最短路径和距离)

原理&#xff1a; Dijkstra算法是解决**单源最短路径**问题的**贪心算法** 它先求出长度最短的一条路径&#xff0c;再参照该最短路径求出长度次短的一条路径 直到求出从源点到其他各个顶点的最短路径。 首先假定源点为u&#xff0c;顶点集合V被划分为两部分&#xff1a;集合…

chatgpt赋能python:Python字符串去除多余空格

Python字符串去除多余空格 随着Python在各个领域的应用越来越广泛&#xff0c;很多工程师都会遇到字符串去除多余空格的需求。而Python提供了简单的方法来解决这个问题&#xff0c;本文将详细介绍这些方法。 介绍 在Python中&#xff0c;字符串是很常见的数据类型&#xff0…

Linux环境下的工具(yum,gdb,vim)

一&#xff0c;yum yum其实是linux环境下的一种应用商店&#xff0c;主要用centos等版本。它也有三板斧&#xff1a;yum list,yum remove,yum install。当然不是说他只有这三个命令&#xff0c;还有yum search等等。在这直说以上三个。 yum list其实是查看你所能安装的软件包…

puppet 入门详解 超详细!!!

目录 一、puppet概述 二、Puppet的工作模式是什么&#xff1f; 三、Puppet的适用场景是什么&#xff1f; 四、原理 &#xff08;一&#xff09;工作模型 &#xff08;二&#xff09;工作流程 &#xff08;三&#xff09;使用模型 1、单机使用模型 2、master/agent 模型 &…

Vue中如何进行自动化部署与持续集成(CI/CD)

Vue中如何进行自动化部署与持续集成&#xff08;CI/CD&#xff09; 随着云计算和容器技术的广泛应用&#xff0c;自动化部署和持续集成&#xff08;CI/CD&#xff09;已经成为现代软件开发过程中必不可少的环节。Vue作为一款流行的前端框架&#xff0c;也可以使用自动化部署和…

解决:闹钟设置的自定义歌曲响铃时不会播放仅震动【Apple Music】【iOS】

文章目录 1、问题描述2、解决策略3、Q&A4、感受5、Tips 1、问题描述 自带铃声和震动脑瓜子嗡嗡的&#xff0c;幸好有apple music&#xff0c;在闹钟中可以轻松地选择你放入资料库中的任意一首音乐作为铃声。 奇怪的是&#xff0c;闹钟响起&#xff0c;仅震动&#xff0c;没…

chatgpt赋能python:Python怎么过滤非数字

Python怎么过滤非数字 在实际编程过程中&#xff0c;我们常常遇到要对一些数据进行处理&#xff0c;其中经常需要过滤掉非数字的数据&#xff0c;以保证程序能够正常运行。在Python中&#xff0c;若要过滤非数字&#xff0c;可以采用如下几种方法。 方法一&#xff1a;使用正…

chatgpt赋能python:Python中如何输入以0开头的数字?

Python中如何输入以0开头的数字&#xff1f; 在Python编程中&#xff0c;可能会遇到需要输入以0开头的数字的情况。然而&#xff0c;当我们尝试在Python shell或代码中输入以0开头的数字时&#xff0c;我们会发现Python会自动将其转换为八进制格式。 为什么Python会将以0开头…