ext2_allocate_branch()解析

本文深入解析了ext2文件系统中ext2_allocate_branch()函数的实现细节,解释了其在为数据块建立映射路径中的重要作用,并通过举例说明了函数的具体流程与应用背景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        在之前的多篇博客中我们都比较详细地阐述了ext2_get_block()路径中各个函数的实现原理,今天我们来关注下ext2_allocate_branch()的实现细节,这个函数是非常重要的,而且理解起来也没那么简单,虽然说函数的功能一句话就可能说完:为数据块建立映射路径。

        要理解这个函数,我们首先得理解ext2文件系统的逻辑块至物理块的映射方法(这个在之前已经作了比较仔细的描述)。同样,在分析这个函数的时候,我们也通过举例来说明,这样理解起来会更具体。

        让我们假设这样一种场景,假如目前文件的大小是2KB,但我们现在seek到了268KB位置处,写入2个数据块。这样是可以的,即产生了所谓的文件空洞。至于为什么seek到这样一个特殊的位置,也是有代表性的,下面我会说明。

        回想ext2文件系统的逻辑块至物理块的映射关系我们知道,269KB此处需要采用2级间接映射,而且正好就从这个地放起开始需要采用二级映射的方式(如果文件系统块大小为1KB的话,计算很简单,直接索引方式可管理12KB数据,一级索引方式可管理256KB数据)。在这种场景下进入该函数,让我们看看会发生点什么。

static int ext2_alloc_branch(struct inode *inode,
			int indirect_blks, int *blks, ext2_fsblk_t goal,
			int *offsets, Indirect *branch)
{
	int blocksize = inode->i_sb->s_blocksize;
	int i, n = 0;
	int err = 0;
	struct buffer_head *bh;
	int num;
	ext2_fsblk_t new_blocks[4];
	ext2_fsblk_t current_block;

	/* 首先分配直接块和间接块*/
	num = ext2_alloc_blocks(inode, goal, indirect_blks,
				*blks, new_blocks, &err);
	if (err)
		return err;

	branch[0].key = cpu_to_le32(new_blocks[0]);
	/*
	 * metadata blocks and data blocks are allocated.
	 */
	for (n = 1; n <= indirect_blks;  n++) {
		/*
		 * Get buffer_head for parent block, zero it out
		 * and set the pointer to new one, then send
		 * parent to disk.
		 */
		bh = sb_getblk(inode->i_sb, new_blocks[n-1]);
		branch[n].bh = bh;
		lock_buffer(bh);
		memset(bh->b_data, 0, blocksize);
		branch[n].p = (__le32 *) bh->b_data + offsets[n];
		branch[n].key = cpu_to_le32(new_blocks[n]);
		*branch[n].p = branch[n].key;
		//假如到了最后一级的映射间接块,那需要在间接块中存储直接块的物理块号
		if ( n == indirect_blks) {
			current_block = new_blocks[n];
			/*
			 * End of chain, update the last new metablock of
			 * the chain to point to the new allocated
			 * data blocks numbers
			 */
			for (i=1; i < num; i++)
				*(branch[n].p + i) = cpu_to_le32(++current_block);
		}
		set_buffer_uptodate(bh);
		unlock_buffer(bh);
		mark_buffer_dirty_inode(bh, inode);
		/* We used to sync bh here if IS_SYNC(inode).
		 * But we now rely upon generic_write_sync()
		 * and b_inode_buffers.  But not for directories.
		 */
		if (S_ISDIR(inode->i_mode) && IS_DIRSYNC(inode))
			sync_dirty_buffer(bh);
	}
	*blks = num;
	return err;
}
我们首先不妨看看这个函数的都代表何意义:

inode:自然代表了该文件;

indirect_blocks:需要分配多少个间接块,本例中需要采用二级映射,而且该映射路径上的所有间接块均未分配,因此,需要分配2个间接块;

blks:需要分配的数据块数量,本例中也为2;

goal:调用者计算的建议最佳分配块号,这个的计算已经在之前的博客中说明过;

offsets[]:保存了每一级映射路径上的偏移;

branch[]:保存了映射关系的数据结构。

让我们看看函数主要流程:

1. 首先自然要分配间接块和数据块,在函数ext2_allocate_blocks()中完成,这个我们等到下一篇博客中再详述;

2.然后需要建立映射关系,其实就是将映射路径上的映射关系进行填充,主要过程在for循环中完成,这也是我们关注的重点。

再回到我们前面举的例子上来,现在我们间接块和数据块都分配好了,假如都分配成功,但此时映射尚未建立,如下图所示:


这就是我们目前所处的状态,间接块和数据块也已分配,但这种连接尚未建立起来。文件在i_data[13](即二级索引的起始之处)断链了,接下来我们要做的是就是创建这种连接关系。

让我们看看for循环的执行流程:

首先在for循环尚未开始的地方,branch[0].key = new_blocks[0],new_blocks[]数组中存储了ext2_allocate_blocks()分配的间接块和直接块的索引块号(new_blocks[]只有4项,如果分配的块数过多,它如何能记下,其实它的使用有点讲究,因为最多需要三个间接块,所以做多使用前三项来保存间接块号,而该数组并不记录所有的数据块号,只记录数据块的起始块号,因为它好像保证分配的数据块连续),因此,new_blocks[0]中保存的就是第一个间接块的物理块号,经过这一步,第一个链接就此建立起来,如下图:

 

至此,第一个链接就建立起来了,接下来进入for循环,建立剩余的链接,建立链接的过程相对简单:

for (n = 1; n <= indirect_blks;  n++) {
		/*
		 * Get buffer_head for parent block, zero it out
		 * and set the pointer to new one, then send
		 * parent to disk.
		 */
		bh = sb_getblk(inode->i_sb, new_blocks[n-1]);
		branch[n].bh = bh;
		lock_buffer(bh);
		memset(bh->b_data, 0, blocksize);
		branch[n].p = (__le32 *) bh->b_data + offsets[n];
		branch[n].key = cpu_to_le32(new_blocks[n]);
		*branch[n].p = branch[n].key;
		//如果到了最后一个间接块,那么需要在该块中记录数据块的块号,分配的数据块是连续的
		if ( n == indirect_blks) {
			current_block = new_blocks[n];
			/*
			 * End of chain, update the last new metablock of
			 * the chain to point to the new allocated
			 * data blocks numbers
			 */
			for (i=1; i < num; i++)
				*(branch[n].p + i) = cpu_to_le32(++current_block);//分配的数据块连续
		}
}		
建立链接也很简单,对于每个间接块,从磁盘读出其内容,然后将在相应的偏移处记录下一个间接块的物理块号

branch[n].p = (__le32 *)bh->b_data + offsets[n] //将指针指向间接块的相应偏移处

branch[n].key = cpu_to_le32(new_blocks[n])//设置key,即下一级映射的间接块物理块号

*branch[n].p = branch[n].key//将内容写入间接块的相应位置

最后,在间接块映射完成以后,需要将数据块的物理块号记录在一级间接块中。这就是for循环里面的if判断的作用,因为分配的数据块是连续的,所以只需要知道起始数据块号和块数即可。

至此,所有的链接都已经完成了,形成的效果如下所示::


至此,整个建立连接的过程就已经完成了。理解这个过程非常重要,还好,我们已经把它搞透彻了。


<think>嗯,用户需要了解lv_obj_allocate_spec_attr函数的定义和用法。这看起来是一个LVGL图形库的技术问题,说明用户可能正在开发嵌入式GUI应用。根据引用[2],这个函数是用来为LVGL对象分配特殊属性内存的。它与lv_obj_set_user_data的区别在于,后者只是关联指针,而前者是真正在对象内部开辟存储空间。用户特别要求查看函数定义和用法,说明可能是第一次使用这个函数。用户提供的引用中[4]有个简单示例展示了函数调用,但缺少详细参数说明。准备这样解答:先给出函数原型定义(基于C语法),然后解释参数含义。在用法部分重点说明三个要点:仅分配一次、通过spec_attr访问数据、对象销毁时自动释放内存。还要特别提醒用户,如果所需空间小于已分配空间则不会重新分配。考虑到用户可能想扩展自定义控件,准备补充一个在自定义对象中使用该函数的场景。示例代码展示如何创建special_attr结构体类型并进行内存分配,最后访问成员变量。这个场景应该能覆盖大多数用户需求。最后还需要强调这是面向对象扩展的功能,普通对象不需要使用。用户可能误用这个底层API,所以补充这个使用建议。技术细节上准备指出要强制转换指针类型,这是代码中容易出错的地方。</think> 以下是关于 `lv_obj_allocate_spec_attr()` 函数的详细说明,参考站内引用[2][^2]和[4][^4]: --- ### 函数定义 ```c void lv_obj_allocate_spec_attr(lv_obj_t * obj); ``` **作用**: 为LVGL对象分配特殊属性内存空间(存储在 `spec_attr` 成员中),用于扩展对象类的私有数据[^2]。 **参数**: - `obj`:目标对象的指针(`lv_obj_t` 类型) --- ### 核心用途 1. **扩展对象数据** 当创建自定义组件(如继承 `lv_btn` 的增强按钮)需存储额外数据时,通过此函数分配固定大小的内存块,存储专属数据结构(如配置参数、状态变量等)[^2]。 2. **替代 `lv_obj_set_user_data()` 的场景** - `set_user_data()`:仅关联一个外部指针,适合轻量级数据[^2] - `allocate_spec_attr()`:**直接内嵌数据到对象**,避免额外内存管理,提高数据访问效率[^2] --- ### 使用步骤 ```c // 1. 定义自定义数据结构 typedef struct { int counter; bool is_active; } my_spec_attr_t; // 2. 在对象初始化时分配内存 lv_obj_t * custom_obj = lv_obj_create(parent); lv_obj_allocate_spec_attr(custom_obj); // 分配特殊属性空间 // 3. 关联数据到对象的 spec_attr lv_obj_spec_attr_t * spec_attr = custom_obj->spec_attr; spec_attr->user_data = &(my_spec_attr_t){ .counter = 0, .is_active = true }; // 4. 访问数据(需类型转换) my_spec_attr_t * data = (my_spec_attr_t *)spec_attr->user_data; data->counter++; // 操作自定义数据 ``` --- ### 关键注意事项 1. **内存管理** - 内存由LVGL自动分配和释放(对象删除时回收) - 若 `spec_attr` 已分配,重复调用此函数无效[^4] 2. **使用限制** - 仅适用于需要**扩展内置对象功能**的高级场景 - 普通数据存储建议优先用 `lv_obj_set_user_data()`[^2] 3. **数据访问** 通过 `obj->spec_attr->user_data` 访问自定义数据,需自行管理类型安全 --- ### 典型应用场景 ✅ 自定义控件开发(如带状态记录的进度条) ✅ 对象需要存储结构化专属配置 ❌ 临时存储简单数据(此时应使用 `lv_obj_set_user_data()`) --- ### 示例补充 在LVGL官方组件中广泛使用此模式,例如: ```c // lv_btn 内部使用 spec_attr 存储状态标志 lv_obj_allocate_spec_attr(btn); btn->spec_attr->state_flags |= LV_BTN_STATE_PRESSED; ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值