uboot的环境变量

本文详细介绍了UBoot中的环境变量管理,包括环境变量的优先级、存储位置和工作方式。重点解析了printenv、setenv和saveenv命令的源码实现,并探讨了环境变量在DDR和SD卡之间的迁移过程。此外,还讨论了getenv和getenv_r函数的区别,强调了可重入函数在多任务环境中的重要性。


前言

uboot学习:个人觉得边看代码边总结会比较好。不纠结于细节,要想弄清楚uboot的目的,看代码的枝干就行。根据朱有鹏课程整理。

uboot文章连载:
1.uboot启动过程
2.uboot命令体系
3.uboot的环境变量
4.uboot的驱动
5.uboot启动Linux内核

Linux文章连载


1.概述

1.1、环境变量的优先级
(1)命令传参>环境变量>uboot源码中的设置。

1.2、环境变量在uboot中工作方式
(1)默认环境变量,在uboot/common/env_common.c中default_environment,本质是一个字符数组,大小为CFG_ENV_SIZE(16kb),每个环境变量最末端以’\0’结束。
(2)SD卡中环境变量分区,在uboot的raw分区中。当saveenv时其实整个环境变量都被保存了一遍,而不是只保存更改了的。
(3)DDR中环境变量,在default_environment中,实质是字符数组。在uboot中其实是一个全局变量,链接时在数据段,重定位时default_environment就被重定位到DDR中一个内存地址处了。
总结:刚烧录的系统中环境变量分区是空白的,uboot第一次运行时加载的是uboot代码中自带的一份环境变量。我们在saveenv时DDR中的环境变量会被更新到SD卡中,下次开机会在环境变量relocate时SD卡中的环境变量会被加载到DDR中去。
default_environment中的内容虽然被uboot源代码初始化为一定的值(这个值就是我们的默认环境变量),但是在uboot启动的第二阶段,env_relocate时代码会去判断SD卡中的env分区的crc是否通过。如果crc校验通过说明SD卡中有正确的环境变量存储,则relocate函数会从SD卡中读取环境变量来覆盖default_environment字符数组,从而每次开机可以保持上一次更改过的环境变量。

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

2.1、printenv
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;
}

do_printenv 说明printenv命令有两种使用方式:传参与不传参。
不传参时,执行for循环嵌套:
在这里插入图片描述
在env_auto.c的env_init函数中,将代码中自带的环境变量指针给了gd->env_addr。
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 1;

2.2、setenv
(1)命令定义和对应的函数在uboot/common/cmd_nvedit.c中。
(2)do_setenv:代码看uboot
第1步:遍历DDR中环境变量的数组,找到原来就有的那个环境变量对应的地址。
第2步:擦除原来的环境变量(如果原来的值长度大于新写入的值,则采用覆盖的办法擦不干净原来的值,只有先将值擦除(就是将原来的值写成0)再写入)
第3步:写入新的环境变量,
(3)本来setenv做完上面的就完了,但是还要考虑一些附加的问题。
问题一:环境变量太多超出DDR中的字符数组,溢出的解决方法。
问题二:有些环境变量如baudrate、ipaddr等,在gd中有对应的全局变量。这种环境变量在set更新的时候要同时去更新对应的全局变量,否则就会出现在本次运行中环境变量和全局变量不一致的情况。

2.3、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的底层函数了。)

3.uboot内部获取环境变量

3.1、getenv
(1)应该是不可重入的。不能在uboot的shell界面使用,因为这是某些相应会调用的内部函数。
(2)实现方式就是去遍历default_environment数组,挨个拿出所有的环境变量比对name,找到相等的直接返回这个环境变量的首地址即可。
3.2、getenv_r
(1)可重入版本。(和本节内容无关,可以了解,也可以略过)
可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。
简介:
可重入函数也可以这样理解,重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是purecode(纯代码)可重入,可以允许有多个该函数的副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。如果确实需要访问全局变量(包括static),一定要注意实施互斥手段。可重入函数在并行运行环境中非常重要,但是一般要为访问全局变量付出一些性能代价。
可重入函数注意事项:
编写可重入函数时,若使用全局变量,则应通过**关中断、信号量(即P、V操作)**等手段对其加以保护。
若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个线程调用此函数时,很有可能使有关全局变量变为不可知状态。
可重入函数示例
假设Exam是int型全局变量,函数Square_Exam返回Exam平方值。那么如下函数不具有可重入性。

unsigned int example(int para)
{
    unsigned int temp;
    Exam = para; // (**)
    temp = Square_Exam();
    return temp;
}

此函数若被多个进程调用的话,其结果可能是未知的,因为当(**)语句刚执行完后,另外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此函数时,将使Exam赋予另一个不同的para值,所以当控制重新回到“temp = Square_Exam( )”后,计算出的temp很可能不是预想中的结果。此函数应如下改进。

unsigned int example( int para )
{
unsigned int temp;
[申请信号量操作] //(1)
Exam = para;
temp = Square_Exam( );
[释放信号量操作]
return temp;
}

若申请不到“信号量”,说明另外的进程正处于给Exam赋值并计算其平方过程中(即正在使用此信号),本进程必须等待其释放信号后,才可继续执行。若申请到信号,则可继续执行,但其它进程必须等待本进程释放信号量后,才能再使用本信号。
保证函数的可重入性的方法:在写函数时候尽量使用局部变量(例如寄存器、堆栈中的变量),对于要使用的全局变量要加以保护(如采取关中断、信号量等方法),这样构成的函数就一定是一个可重入的函数。
VxWorks中采取的可重入的技术有:
动态堆栈变量(各子函数有自己独立的堆栈空间)
受保护的全局变量和静态变量
任务变量 [2]
可重入函数与线程安全的关系
可重入与线程安全两个概念都关系到函数处理资源的方式。但是,他们有重大区别:
• 可重入概念会影响函数的外部接口,而线程安全只关心函数的实现。
o 大多数情况下,要将不可重入函数改为可重入的,需要修改函数接口,使得所有的数据都通过函数的调用者提供。
o 要将非线程安全的函数改为线程安全的,则只需要修改函数的实现部分。一般通过加入同步机制以保护共享的资源,使之不会被几个线程同时访问。
• 操作系统背景与CPU调度策略:
o 可重入是在单线程操作系统背景下,重入的函数或者子程序,按照后进先出的线性序依次执行完毕。
o 多线程执行的函数或子程序,各个线程的执行时机是由操作系统调度,不可预期的,但是该函数的每个执行线程都会不时的获得CPU的时间片,不断向前推进执行进度。
• 可重入函数未必是线程安全的;线程安全函数未必是可重入的。
o 例如,一个函数打开某个文件并读入数据。这个函数是可重入的,因为它的多个实例同时执行不会造成冲突;但它不是线程安全的,因为在它读入文件时可能有别的线程正在修改该文件,为了线程安全必须对文件加“同步锁”。
o 另一个例子,函数在它的函数体内部访问共享资源使用了加锁、解锁操作,所以它是线程安全的,但是却不可重入。因为若该函数一个实例运行到已经执行加锁但未执行解锁时被停下来,系统又启动该函数的另外一个实例,则新的实例在加锁处将转入等待。如果该函数是一个中断处理服务,在中断处理时又发生新的中断将导致资源死锁。fprintf函数就是线程安全但不可重入。
可重入函数不可重入
在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数被设计成为不可重入的函数的话,那么不同任务调用这个函数时可能修改其他任务用到的数据,从而导致不可预料的后果。那么什么是可重入函数呢?所谓可重入函数是指一个可以被多个任务调用的函数(过程),任务在调用时不必担心数据是否会出错。不可重入函数在实时系统设计中被视为不安全函数。 [1]
满足下列条件的函数多数是不可重入的:
1函数体内使用了静态的数据结构;
2函数体内调用了malloc()或者free()函数;
3函数体内调用了标准I/O函数。
下面举例加以说明。

A. 可重入函数

void strcpy(char *lpszDest, char *lpszSrc) {
while(*lpszDest++=*lpszSrc++);
*dest=0;
}

B. 不可重入函数1

char cTemp;//全局变量
void SwapChar1(char *lpcX, char *lpcY) {
cTemp=*lpcX;
*lpcX=*lpcY;
lpcY=cTemp;//访问了全局变量
}

C. 不可重入函数2

void SwapChar2(char *lpcX,char *lpcY) {
static char cTemp;//静态局部变量
cTemp=*lpcX;
*lpcX=*lpcY;
lpcY=cTemp;//使用了静态局部变量
}

(2)getenv函数是直接返回这个找到的环境变量在DDR中环境变量处的地址,而getenv_r函数的做法是找到了DDR中环境变量地址后,将这个环境变量复制一份到提供的buf中,而不动原来DDR中环境变量。
所以差别就是:getenv中返回的地址只能读不能随便乱写,而getenv_r中返回的环境变量是在自己提供的buf中,是可以随便改写加工的。

<think>好的,用户想了解U-Boot环境变量的配置和使用示例。首先,我需要回忆一下U-Boot的基本知识,环境变量在U-Boot中的作用,以及如何操作它们。根据用户提供的引用内容,环境变量存储在flash中,默认值来自源代码,用户可以通过命令进行设置和保存。 首先,我应该解释什么是U-Boot环境变量,它们的作用是什么。然后,详细说明如何查看、设置、保存环境变量,并提供一些常见的使用示例,比如设置网络参数或启动命令。需要确保步骤清晰,命令正确,并且符合用户的实际操作需求。 用户可能想知道具体的命令语法,比如printenv、setenv、saveenv的使用方法。还要注意环境变量的优先级,当变量为空时使用默认值,否则使用用户设置的值。可能需要提到一些常见变量,如serverip、ipaddr、ethaddr、bootcmd和bootargs,这些在引用中都有涉及。 另外,用户可能需要示例来更好地理解,比如设置serverip的步骤,或者配置启动参数。需要确保示例准确,并且命令格式正确。同时,要注意用户可能的错误,比如忘记保存环境变量,导致重启后配置丢失,所以需要强调saveenv的重要性。 还要考虑用户可能遇到的问题,比如如何恢复默认设置,或者处理环境变量存储损坏的情况。可以简要提到这些情况,但主要焦点还是放在配置和使用示例上。 最后,根据用户的要求生成相关问题,这些问题应该与U-Boot环境变量的高级配置、存储位置、调试方法等相关,帮助用户进一步学习。</think>### U-Boot 环境变量的配置与使用详解 U-Boot 环境变量是嵌入式系统启动过程中用于存储配置参数的关键机制,其优先级规则为:若变量值为空则使用代码默认值,若已设置则优先使用用户定义值[^1]。以下从基础操作到典型场景逐步说明: #### 一、环境变量基础操作 1. **查看所有环境变量** ```bash => printenv baudrate=115200 bootcmd=run distro_bootcmd serverip=192.168.1.100 # 未设置时使用默认值 ``` 通过该命令可验证当前生效的变量值[^3]。 2. **设置/修改变量** ```bash => setenv serverip 192.168.1.100 => setenv bootdelay 3 # 修改启动等待时间 ``` 变量修改后仅在内存中生效,需执行保存操作才能持久化[^1]。 3. **保存到存储设备** ```bash => saveenv Saving Environment to SPI Flash... OK ``` 该操作将当前环境变量写入NOR Flash或eMMC等存储介质。 #### 二、典型配置场景示例 1. **网络参数配置** ```bash => setenv ipaddr 192.168.1.50 # 设备IP => setenv serverip 192.168.1.100 # TFTP服务器IP => setenv netmask 255.255.255.0 => saveenv ``` 这些参数直接影响`tftp`命令下载内核镜像等网络操作[^2]。 2. **MAC地址设置** ```bash => setenv ethaddr 00:0a:35:12:34:56 => saveenv ``` 注意:MAC地址首次设置后不可修改,需清除环境变量分区才能重置[^2]。 3. **自定义启动流程** ```bash => setenv bootcmd 'tftp 0x82000000 zImage; bootm 0x82000000' => setenv bootargs console=ttyS0,115200 root=/dev/mmcblk0p2 => saveenv ``` `bootcmd`定义自动执行的启动命令,`bootargs`向内核传递参数[^2]。 #### 三、高级操作技巧 1. **多环境备份机制** 修改`envsize`和`envnumber`实现多份环境备份: ```bash => setenv envsize 0x20000 # 单个环境区大小 => setenv envnumber 2 # 双备份配置 => saveenv ``` 2. **环境变量恢复** ```bash => env default -f # 恢复所有默认值 => saveenv # 清除自定义设置 ``` 3. **脚本化配置** 通过`;`分隔多个命令实现批量配置: ```bash => setenv init_cmd 'setenv ipaddr 192.168.1.10; setenv serverip 192.168.1.100; saveenv' => run init_cmd ``` #### 四、调试与验证 1. **变量值有效性检查** ```bash => env info env_valid = valid env_ready = true env_use_default = false ``` 该命令显示环境变量存储状态[^3]。 2. **Android系统读取验证** 通过`envtool`工具验证变量是否传递到系统层: ```bash # envtool r factory_test ubootenv init successful.(0) on ``` 这确认了U-Boot环境变量与Android系统的集成效果[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值