ARM uboot 源码分析8 - uboot的环境变量

news2024/12/23 8:35:25

一、uboot 的环境变量基础

1、环境变量的作用

(1) 让我们可以不用修改 uboot 的源代码,而是通过修改环境变量,来影响 uboot 运行时的一些数据和特性。譬如说,通过修改 bootdelay 环境变量,就可以更改系统开机自动启动时倒数的秒数。


2、环境变量的优先级

(1) uboot 代码当中有一个值,环境变量中也有一个值。uboot 程序实际运行时规则是:如果环境变量为空,则使用代码中的值;如果环境变量不为空,则优先使用环境变量对应的值。

(2) 譬如 machid(机器码)。uboot 中在 x210_sd.h 中定义了一个机器码 2456,写死在程序中的,不能更改。如果要修改 uboot 中配置的机器码,可以修改 x210_sd.h 中的机器码,但是修改源代码后需要重新编译烧录,很麻烦;

在这里插入图片描述

在这里插入图片描述

比较简单的方法就是,使用环境变量 machidset machid 0x998 类似这样,有了 machid 环境变量后,系统启动时会优先使用 machid 对应的环境变量,这就是优先级问题。


3、环境变量在 uboot 中工作方式

(1) 默认环境变量,在 uboot/common/env_common.c 中 default_environment,这东西本质是一个字符数组,大小为 CFG_ENV_SIZE(16 kByte),里面内容就是很多个环境变量连续分布组成的,每个环境变量最末端以 ‘\0’ 结束。

在这里插入图片描述


(2) SD 卡中的环境变量分区,在 uboot 的 raw 分区中。SD 卡中其实就是给了个分区,专门用来存储而已。存储时其实是把 DDR 中的环境变量整体的写入 SD 卡中分区里。所以当我们 saveenv 时,其实整个所有的环境变量都被保存了一遍,而不是只保存更改了的。

(3) DDR 中环境变量,在 default_environment 中,实质是字符数组。在 uboot 中其实是一个全局变量,链接时在数据段;重定位时,default_environment 就被重定位到 DDR 中一个内存地址处了。这个地址处,这个全局字符数组,就是我们 uboot 运行时的 DDR 中的环境变量了。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


总结:

  • 刚烧录的系统中,环境变量分区是空白的,uboot 第一次运行时,加载的是 uboot 代码中自带的一份环境变量,叫默认环境变量 default_environment
  • 我们在 saveenv 时,DDR 中的环境变量会被更新到 SD 卡中的环境变量中,就可以被保存下来,下次开机会在环境变量 relocate 时,SD 卡中的环境变量会被加载到 DDR 中去。
  • default_environment 中的内容虽然被 uboot 源代码初始化为一定的值(这个值就是我们的默认环境变量),但是在 uboot 启动的第二阶段,env_relocate 时代码会去判断 SD 卡中的 env 分区的 crc 是否通过。如果 crc 校验通过,说明 SD 卡中有正确的环境变量存储,则 relocate 函数会从 SD 卡中读取环境变量来覆盖 default_environment 字符数组,从而每次开机可以保持上一次更改过的环境变量。

二、环境变量相关命令源码解析1

1、printenv

(1) 找到 printenv 命令所对应的函数。通过 printenv 的 help 可以看出,这个命令有 2 种使用方法。第一种直接使用不加参数,则打印所有的环境变量;第二种是 printenv name ,则只打印出 name 这个环境变量的值。

在这里插入图片描述


(2) 分析 do_printenv 函数。

int do_printenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	int i, j, k, nxt;
	int rcode = 0;

	if (argc == 1) {		/* Print all env variables	*/
		for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
			for (nxt=i; env_get_char(nxt) != '\0'; ++nxt)
				;
			for (k=i; k<nxt; ++k)
				putc(env_get_char(k));
			putc  ('\n');

			if (ctrlc()) {
				puts ("\n ** Abort\n");
				return 1;
			}
		}

		printf("\nEnvironment size: %d/%ld bytes\n",
			i, (ulong)ENV_SIZE);

		return 0;
	}

	for (i=1; i<argc; ++i) {	/* print single env variables	*/
		char *name = argv[i];

		k = -1;

		for (j=0; env_get_char(j) != '\0'; j=nxt+1) {

			for (nxt=j; env_get_char(nxt) != '\0'; ++nxt)
				;
			k = envmatch((uchar *)name, j);
			if (k < 0) {
				continue;
			}
			puts (name);
			putc ('=');
			while (k < nxt)
				putc(env_get_char(k++));
			putc ('\n');
			break;
		}
		if (k < 0) {
			printf ("## Error: \"%s\" not defined\n", name);
			rcode ++;
		}
	}
	return rcode;
}

在这里插入图片描述

在这里插入图片描述

不论 SD 卡中是否有环境变量,以及是否发生环境变量覆盖,环境变量的首地址总是 default_environment 字符数组的首地址。


(3) do_printenv 函数首先区分 argc=1 还是不等于 1 的情况,若 argc=1 ,那么就循环打印所有的环境变量出来;如果 argc 不等于 1,则后面的参数就是要打印的环境变量,给哪个就打印哪个。

(4) argc=1 时,用双重 for 循环来依次处理所有的环境变量的打印。第一重 for 循环就是处理各个环境变量。所以有多少个环境变量,则第一重就执行循环多少圈。

在这里插入图片描述

在这里插入图片描述


(5) 这个函数要看懂,首先要明白整个环境变量在内存中如何存储的问题。

(6) 关键点:第一,要明白环境变量在内存中存储的方式;第二,要 C 语言处理字符串的功底要好。


三、环境变量相关命令源码解析 2

在这里插入图片描述

1、setenv

(1) 命令定义和对应的函数在 uboot/common/cmd_nvedit.c 中,对应的函数为 do_setenv

在这里插入图片描述


(2) setenv 的思路就是:先去 DDR 中的环境变量处寻找原来有没有这个环境变量,如果原来就有,则需要覆盖原来的环境变量,如果原来没有则在最后新增一个环境变量即可。

第1步:遍历 DDR 中环境变量的数组,找到原来就有的那个环境变量对应的地址。168-174 行。
在这里插入图片描述

第2步:擦除原来的环境变量,259-265 行。
第3步:写入新的环境变量,266-273 行。


(3)本来 setenv 做完上面的就完了,但是还要考虑一些附加的问题。

问题一:环境变量太多,超出 DDR 中的字符数组,溢出的解决方法。
问题二:有些环境变量如 baudrate、ipaddr 等,在 gd 中有对应的全局变量。这种环境变量在 set 更新的时候,要同时去更新对应的全局变量,否则就会出现在本次运行中,环境变量和全局变量的值不一致的情况。

在这里插入图片描述


四、环境变量相关命令源码解析 2

1、saveenv

(1) 在 uboot/common/cmd_nvedit.c 中,对应函数为 do_saveenv

在这里插入图片描述


(2) 从 uboot 实际执行 saveenv 命令的输出,和 x210_sd.h 中的配置(#define CFG_ENV_IS_IN_AUTO)可以分析出:我们实际使用的是 env_auto.c 中相关的内容。没有一种芯片叫 auto 的,env_auto.c 中是使用宏定义的方式,去条件编译了各种常见的 flash 芯片(如 movinand、norflash、nand 等)。然后在程序中读取 INF_REG(OMpin 内部对应的寄存器)从而知道我们的启动介质,然后调用这种启动介质对应的操作函数来操作。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


(3) do_saveenv 内部调用 env_auto.c 中的 saveenv 函数来执行实际的环境变量保存操作。

在这里插入图片描述


(4) 寄存器地址:E010F000 + 0C=E010_F00C,含义是用户自定义数据。我们在 start.S 中判断启动介质后,将 #BOOT_MMCSD(就是 3,定义在x210_sd.h)写入了这个寄存器,所以这里读出的肯定是 3,经过判断就是 movinand。所以实际执行的函数是:saveenv_movinand。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


(5) 真正执行保存环境变量操作的是:cpu/s5pc11x/movi.c 中的 movi_write_env 函数,这个函数肯定是写 sd卡,将 DDR 中的环境变量数组(其实就是 default_environment 这个数组,大小 16kb,刚好 32 个扇区)写入 iNand 中的 ENV 分区中

在这里插入图片描述

在这里插入图片描述


(6) raw_area_control 是 uboot 中规划 iNnad/SD 卡的原始分区表,这个里面记录了我们对 iNand 的分区,env 分区也在这里,下标是2。 追到这一层就够了,再里面就是调用驱动部分的写 SD卡/iNand 的底层函数了。

在这里插入图片描述

在这里插入图片描述

int init_raw_area_table (block_dev_desc_t * dev_desc)
{
	struct mmc *host = find_mmc_device(dev_desc->dev);
	
	/* when last block does not have raw_area definition. */
	if (raw_area_control.magic_number != MAGIC_NUMBER_MOVI) {
		int i = 0;
		member_t *image;
		u32 capacity;
	
		if (host->high_capacity) {
			capacity = host->capacity;
		#ifdef CONFIG_S3C6410
			if(IS_SD(host))
				capacity -= 1024;
		#endif
		} else {
			capacity = host->capacity;
		}

		dev_desc->block_read(dev_desc->dev,
			capacity - (eFUSE_SIZE/MOVI_BLKSIZE) - 1,
			1, &raw_area_control);
		if (raw_area_control.magic_number == MAGIC_NUMBER_MOVI) {
			return 0;
		}
		
		dbg("Warning: cannot find the raw area table(%p) %08x\n",
			&raw_area_control, raw_area_control.magic_number);
		/* add magic number */
		raw_area_control.magic_number = MAGIC_NUMBER_MOVI;

		/* init raw_area will be 16MB */
		raw_area_control.start_blk = 16*1024*1024/MOVI_BLKSIZE;
		raw_area_control.total_blk = capacity;
		raw_area_control.next_raw_area = 0;
		strcpy(raw_area_control.description, "initial raw table");

		image = raw_area_control.image;

#if defined(CONFIG_EVT1)
	#if defined(CONFIG_FUSED)
		/* image 0 should be fwbl1 */
		image[0].start_blk = (eFUSE_SIZE/MOVI_BLKSIZE);
		image[0].used_blk = MOVI_FWBL1_BLKCNT;
		image[0].size = FWBL1_SIZE;
		image[0].attribute = 0x0;
		strcpy(image[0].description, "fwbl1");
		dbg("fwbl1: %d\n", image[0].start_blk);
	#endif
#endif

		/* image 1 should be bl2 */
#if defined(CONFIG_EVT1)
	#if defined(CONFIG_FUSED)
		image[1].start_blk = image[0].start_blk + MOVI_FWBL1_BLKCNT;
	#else
		image[1].start_blk = (eFUSE_SIZE/MOVI_BLKSIZE);
	#endif
#else
		image[1].start_blk = capacity - (eFUSE_SIZE/MOVI_BLKSIZE) -
				MOVI_BL1_BLKCNT;
#endif
		image[1].used_blk = MOVI_BL1_BLKCNT;
		image[1].size = SS_SIZE;

		image[1].attribute = 0x1;
		
		strcpy(image[1].description, "u-boot parted");
		dbg("bl1: %d\n", image[1].start_blk);

		/* image 2 should be environment */
#if defined(CONFIG_EVT1)
		image[2].start_blk = image[1].start_blk + MOVI_BL1_BLKCNT;
#else
		image[2].start_blk = image[1].start_blk - MOVI_ENV_BLKCNT;
#endif
		image[2].used_blk = MOVI_ENV_BLKCNT;
		image[2].size = CFG_ENV_SIZE;
		image[2].attribute = 0x10;
		strcpy(image[2].description, "environment");
		dbg("env: %d\n", image[2].start_blk);

		/* image 3 should be bl2 */
#if defined(CONFIG_EVT1)
		image[3].start_blk = image[2].start_blk + MOVI_ENV_BLKCNT;
#else
		image[3].start_blk = image[2].start_blk - MOVI_BL2_BLKCNT;
#endif
		image[3].used_blk = MOVI_BL2_BLKCNT;
		image[3].size = PART_SIZE_BL;
		image[3].attribute = 0x2;
		strcpy(image[3].description, "u-boot");
		dbg("bl2: %d\n", image[3].start_blk);

		/* image 4 should be kernel */
#if defined(CONFIG_EVT1)
		image[4].start_blk = image[3].start_blk + MOVI_BL2_BLKCNT;
#else
		image[4].start_blk = image[3].start_blk - MOVI_ZIMAGE_BLKCNT;
#endif
		image[4].used_blk = MOVI_ZIMAGE_BLKCNT;
		image[4].size = PART_SIZE_KERNEL;
		image[4].attribute = 0x4;
		strcpy(image[4].description, "kernel");
		dbg("knl: %d\n", image[4].start_blk);

		/* image 5 should be RFS */
#if defined(CONFIG_EVT1)
		image[5].start_blk = image[4].start_blk + MOVI_ZIMAGE_BLKCNT;
#else
		image[5].start_blk = image[4].start_blk - MOVI_ROOTFS_BLKCNT;
#endif
		image[5].used_blk = MOVI_ROOTFS_BLKCNT;
		image[5].size = PART_SIZE_ROOTFS;
		image[5].attribute = 0x8;
		strcpy(image[5].description, "rfs");
		dbg("rfs: %d\n", image[5].start_blk);

		for (i=6; i<15; i++) {
			raw_area_control.image[i].start_blk = 0;
			raw_area_control.image[i].used_blk = 0;
		}
	}
}


五、uboot 内部获取环境变量

1、getenv

(1) 应该是不可重入的。

(2) 实现方式就是,去遍历 default_environment 数组,挨个拿出所有的环境变量比对 name,找到相等的直接返回这个环境变量的首地址即可。

在这里插入图片描述


2、getenv_r

(1) 可重入版本。(可自行搜索补充可重入函数的概念)

(2) getenv 函数是直接返回这个找到的环境变量在 DDR 中环境变量处的地址,而 getenv_r 函数的做法是,找到了 DDR 中环境变量地址后,将这个环境变量复制一份到提供的 buf 中,而不去动原来 DDR 中环境变量。

在这里插入图片描述

所以差别就是:getenv 中返回的地址,只能读,不能随便乱写,而 getenv_r 中返回的环境变量是在自己提供的 buf 中,是可以随便改写加工的。


3、总结

(1) 功能是一样的,但是可重入版本会比较安全一些,建议使用。

(2) 有关于环境变量的所有操作,主要理解了环境变量在 DDR 中的存储方法,理解了环境变量和 gd 全局变量的关联和优先级,理解了环境变量在存储介质中的存储方式(专用raw分区),整个环境变量相关的都清楚了。


源自朱有鹏老师.

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

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

相关文章

【MindSpore】安装和使用MindSpore 2.0.0版本简单实现数据变换Transforms功能

本篇文章主要是讲讲MindSpore的安装以及根据官方提供的例子实现数据变换功能。 昇思MindSpore是一款开源的AI框架&#xff0c;旨在实现易开发、高效执行、全场景覆盖三大目标。 目录1、加入MindSpore社区2、安装前准备2.1、获取安装命令2.2、安装pip2.3、确认系统环境3、安装Mi…

JavaWeb--Web概述

Web概述1 Web概述1.1 Web和JavaWeb的概念1.2 JavaWeb技术栈1.2.1 B/S架构1.2.2 静态资源1.2.3 动态资源1.2.4 数据库1.2.5 HTTP协议1.2.6 Web服务器1.3 Web核心课程安排今日目标&#xff1a; 了解JavaWeb开发的技术栈 1 Web概述 1.1 Web和JavaWeb的概念 Web是全球广域网&#…

阿里软件测试二面:adb 连接 Android 手机的两种方式,看完你就懂了

前言 随着现在移动端技术的突飞猛进&#xff0c;导致现在市场上&#xff0c;APP 应用数不胜数&#xff0c;那对于测试工程师而言&#xff0c;对于 APP 的测试&#xff0c;那基本就是一个必修课了。 今天&#xff0c;我就来给大家介绍一下&#xff0c;adb 连接 Android 手机的两…

Spring(Bean生命周期)

目录 1. 生命周期简图2. 扩展接口介绍 2.1 Aware接口2.2 BeanPostProcessor接口2.3 InitializingBean2.4 DisposableBean2.5 BeanFactoryPostProcessor接口3. spring的简化配置 3.1 项目搭建3.2 Bean的配置和值注入3.3 AOP的示例 1. 生命周期简图 2. 扩展接口介绍 2.1 Aware接…

Redis实现分页和多条件模糊查询方案

导言 Redis是一个高效的内存数据库&#xff0c;它支持包括String、List、Set、SortedSet和Hash等数据类型的存储&#xff0c;在Redis中通常根据数据的key查询其value值&#xff0c;Redis没有模糊条件查询&#xff0c;在面对一些需要分页、排序以及条件查询的场景时(如评论&…

PolarDB数据库的CSN机制

背景 对postgres数据库熟悉的同学会发现在高并发场景下在获取快照处易出现性能瓶颈&#xff0c;其原因在于PG使用全局数组在共享内存中保存所有事务的状态&#xff0c;在获取快照时需要加锁以保证数据一致性。获取快照时需要持有ProcArraryLock共享锁比遍历ProcArray数组中活跃…

[计算机网络(第八版)]第二章 物理层(学习笔记)

网络层是网络体系结构的最低层&#xff0c;不是具体的传输媒体&#xff0c;也不是连接计算机的具体物理设备 2.1 物理层的概念 物理层考虑的是怎样才能在连接各种计算机的传输媒体上传输数据比特流&#xff0c;而不是指具体的传输媒体。物理层的作用&#xff1a; 要尽可能地屏…

面试题:Android 中 Intent 采用了什么设计模式?

答案是采用了原型模式。原型模式的好处在于方便地拷贝某个实例的属性进行使用、又不会对原实例造成影响&#xff0c;其逻辑在于对 Cloneable 接口的实现。 话不多说看下 Intent 的关键源码&#xff1a; // frameworks/base/core/java/android/content/Intent.java public cla…

阅读笔记9——DenseNet

一、DenseNet DenseNet的网络结构如图1-1所示&#xff0c;其核心是Dense Block模块&#xff0c;Dense Block中的一个黑点就代表一个卷积模块&#xff08;不是一个卷积层&#xff0c;而是DenseNet提出的一个BottleNeck模块&#xff0c;后文有讲解&#xff09;&#xff0c;每条黑…

ClassPathResource遇到的坑:class path resource

读取文件--ClassPathResource前言一、使用ClassPathResource.getFile()的坑二、通过流读取文件内容总结前言 需求&#xff1a;拿到一个小程序的皮肤文件夹&#xff0c;放在resource目录下 1:根据皮肤的style.json&#xff0c;获取json内的${xxx.png}变量&#xff08;获的图片名…

「2」指针进阶——详解

&#x1f680;&#x1f680;&#x1f680;大家觉不错的话&#xff0c;就恳求大家点点关注&#xff0c;点点小爱心&#xff0c;指点指点&#x1f680;&#x1f680;&#x1f680; 目录 &#x1f430;指向函数指针数组的指针(很少用&#xff0c;了解) &#x1f430;回调函数&…

【Arduino 无刷电机控制教程】

【Arduino 无刷电机控制教程】 1. 概述2. 试验准备3. 实验原理4. Arduino 无刷电机控制 – 电路图4.1 实验组件4.2 用于 BLDC 电机控制的 Arduino 代码5. 实验验证5.1 电位计控制无刷电机速度5.2 电调校准在本教程中,我们将学习如何使用 Arduino 和 ESC 控制无刷电机。如果您想…

建议将com.alibaba:fastjson升级至1.2.83

问题 升级了gradle&#xff0c;改了文件存储位置&#xff0c;项目需要重新构建下载依赖文件&#xff0c;发现fastjson 1.2.66一直下载不下来一直卡在下载&#xff0c;就想着手动下载下试试&#xff0c;就去了mvnrepository网站找到fastjson时&#xff0c;发现了fastjson2 Note…

一文让你彻底了解Linux内核文件系统

一&#xff0c;文件系统特点 文件系统要有严格的组织形式&#xff0c;使得文件能够以块为单位进行存储。文件系统中也要有索引区&#xff0c;用来方便查找一个文件分成的多个块都存放在了什么位置。如果文件系统中有的文件是热点文件&#xff0c;近期经常被读取和写入&#xf…

数学不好,英语不行,非本专业,可以学IT吗?

很多小伙伴&#xff0c;都会问小青一些比较类似的问题。比如&#xff1a;不是计算机专业的&#xff0c;可以学编程吗&#xff1f;数学一直就不好&#xff0c;可以转行学IT吗&#xff1f;学编程开发&#xff0c;对英语的的要求会不会很高&#xff1f;01计算机不是计算机专业的&a…

C/C++开发,无可避免的内存管理(篇三)-规划好内存

一、用内存空间换效率 1.1 allocatoe类模板 在前面简述模板顺序容器时&#xff0c;就提到过&#xff0c;标准库中的 vector 类是通过预先分配额外内存以换取不不用每次添加元素都要重新分配内存和移动元素&#xff0c;而是将元素直接保存加入的预先分配的内存区域。在预先分配…

【Git】Git冲突与解决方法

目录 一、Git冲突如何产生&#xff1f; 二、解决Git冲突—手动修改冲突 【第一步】在 hot-fix 分支上增加如下代码&#xff0c;并且提交。 【第二步】在master 分支上同样的地方增加如下代码&#xff0c;并且提交。 【第三步】 我们现在在 master 分支上合并 hot-fix 分支&a…

慢雾:Discord 私信钓鱼手法分析

事件背景 5 月 16 日凌晨&#xff0c;当我在寻找家人的时候&#xff0c;从项目官网的邀请链接加入了官方的 Discord 服务器。在我加入服务器后立刻就有一个"机器人"(Captcha.bot)发来私信要我进行人机验证。这一切看起来相当的合理。我也点击了这个验证链接进行查看…

数据结构——顺序表讲解

作者&#xff1a;几冬雪来 时间&#xff1a;2023年2月25日 内容&#xff1a;数据结构顺序表内容讲解 目录 前言&#xff1a; 顺序表&#xff1a; 1.线性表&#xff1a; 2.什么是顺序表&#xff1a; 3.顺序表的概念和构成&#xff1a; 4.顺序表的书写&#xff1a; 1…

【Web逆向】万方数据平台正文的逆向分析(上篇--加密发送请求)—— 逆向protobuf

【Web逆向】万方数据平台正文的逆向分析&#xff08;上篇--加密发送请求&#xff09;—— 逆向protobuf声明一、了解protobuf协议&#xff1a;二、前期准备&#xff1a;二、目标网站&#xff1a;三、开始分析&#xff1a;我们一句句分析&#xff1a;先for循环部分&#xff1a;后…