一、uboot的环境变量基础
1.1、环境变量的作用
(1)让我们可以不用修改uboot的源代码,而是通过修改环境变量就可以影响uboot运行时的一些特性。譬如说修改bootdelay环境变量就可以更改系统开机自动启动时倒计时的秒数。
1.2、环境变量的优先级
环境变量的优先级高于程序中的全局变量,因此是可以覆盖我们程序中的全局变量的,通过这样的设计使得修改环境变量可以影响我们程序的运行。如果环境变量为空则使用程序中全局变量的值;如果环境变量不为空则优先使用环境变量对应的值。
譬如machid(机器码)。uboot中在x210_sd.h中以硬编码的形式定义了一个机器码2456。如果要修改uboot中配置的机器码,可以直接去修改x210_sd.h中的机器码,但是每次修改源代码后都需要重新配置编译烧录,非常麻烦;
比较简单的方法就是使用环境变量machid。我们在uboot命令行底下输入set machid 0x999类似这样然后保存,SD卡就有了machid环境变量,重启uboot后uboot会优先使用machid对应的环境变量,这就是优先级问题。
1.3、环境变量在uboot中工作方式
(1)环境变量模板,在uboot/common/env_common.c中default_environment,这是一个字符数组,大小为CFG_ENV_SIZE(0x4000 = 16kb),里面内容就是很多个环境变量连续分布组成的,每个环境变量最末端以’\0’结束。
(2)SD卡中环境变量分区,即uboot的env分区。存储时其实是把DDR中的环境变量整体的写入SD卡中分区里。当我们使用saveenv命令时其实所有的环境变量都被保存了一遍,而不是只保存更改了的。
(3)DDR中环境变量,有两份,一份是环境变量模板default_environment字符数组,default_environment是一个全局变量,存于data段中,重定位uboot时就被重定位到DDR中一个内存地址处了;在uboot的第二阶段调用了env_relocate之后,在堆区申请了一份内存,用env_ptr指向这个内存,并且将环境变量模板memcpy到这里,所以DDR中会有两份环境变量。
env_ptr = (env_t *)malloc (CFG_ENV_SIZE);给env_ptr申请一片16KB的内存,即堆区
调用env_relocate_spec
void env_relocate_spec_movinand(void)
{
#if !defined(ENV_IS_EMBEDDED)
#if defined(CONFIG_CMD_MOVINAND)
uint *magic = (uint*)(PHYS_SDRAM_1);//0x3000 0000
if ((0x24564236 != magic[0]) || (0x20764316 != magic[1])) {
movi_read_env(virt_to_phys((ulong)env_ptr));
//由于我save保存了环境变量,下次上电会走这
}
if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
return use_default();//初次上电,SD中没有env,则走这里
#endif /* CONFIG_CMD_MOVINAND */
#endif /* ! ENV_IS_EMBEDDED */
}
gd->env_addr = (ulong)&(env_ptr->data);
总结:刚烧录的系统中环境变量分区是空白的,uboot第一次运行时内存中有一份环境变量模板(default_environment数组),是uboot代码自带的;在第二阶段调用env_relocate时将这份环境变量模板default_environment复制(memcpy)到env_ptr指向的内存(堆区),之后使用的也是堆区这份。
直到我们在命令行底下执行saveenv命令,saveenv实际调用了movi_write,将内存中env_ptr指向的这一份环境变量复制到SD卡env分区中;
然后第二次上电,SD卡中就有环境变量了,在第二阶段调用env_relocate时,通过if else语句判断,得知SD卡中有环境变量,接下来就会执行movi_read函数将SD卡中的环境变量加载到DDR中的这份堆区。
修改代码,真的走了下面的函数体,而非上面。
我们在saveenv时DDR中的环境变量会被更新到SD卡中的环境变量中,就可以被保存下来,下次开机会在环境变量relocate时会将SD卡中的环境变量会被加载到DDR中去。
二、环境变量相关命令源码解析
2.1、printenv
U_BOOT_CMD(
printenv, CFG_MAXARGS, 1, do_printenv,
"printenv- print environment variables\n",
"\n - print values of all environment variables\n"
"printenv name ...\n"
" - print value of environment variable 'name'\n"
);
与printfenv绑定的函数是do_printenv,在uboot\common\cmd_nvedit.c(85~136行)
(1)这个命令有2种使用方法。第一种打印所有的环境变量:print
;第二种是选择性的打印出环境变量:print name1 name2…
(2)do_printenv函数首先区分参数个数argc=1还是不等于1的情况,
若argc=1那么就循环打印所有的环境变量出来;
若argc不等于1,则后面的参数就是要打印的环境变量,给哪个就打印哪个。
//env_common.c(185~206行)
uchar env_get_char_memory (int index)
{
if (gd->env_valid) {//代表重定位了,使用SD卡复制到内存的环境变量
return ( *((uchar *)(gd->env_addr + index)) );
//实际uboot执行走了这一条路
} else {//代表还未重定位,使用默认环境变量
return ( default_environment[index] );
}
}
uchar env_get_char (int index)
{
uchar c;
/* if relocated to RAM */
if (gd->flags & GD_FLG_RELOC)
c = env_get_char_memory(index); //实际uboot执行走了这一条路
else
c = env_get_char_init(index); //说明我们内存中没有环境变量
return (c);
}
//uboot\common\cmd_nvedit.c(85~136行)
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 */
//env_get_char函数是获取内存中环境变量的值,i是相对首地址的偏移量
for (i=0; env_get_char(i) != '\0'; i=nxt+1) {//i指向每个环境变量的第一个元素
for (nxt=i; env_get_char(nxt) != '\0'; ++nxt)//nxt等于每个环境变量的长度
;
for (k=i; k<nxt; ++k) //把每个环境变量都打印出来,一次循环结束只打印一个环境变量
putc(env_get_char(k));
putc ('\n');
if (ctrlc()) { //printenv命令执行过程是打印所有环境变量,期间支持crtl+c打断
puts ("\n ** Abort\n");
return 1;
}
}
printf("\nEnvironment size: %d/%ld bytes\n",
i, (ulong)ENV_SIZE);//ENV_SIZE = 0x4000 - 4
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);//将printenv后的参数和每个环境变量进行比较,然后打印
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;
}
(3)argc=1时用双重for循环来依次处理所有的环境变量的打印。第一重for循环就是处理所有环境变量,有多少个环境变量就循环多少次。第二重for循环就是单独打印出一个环境变量中的所有字符
(4)要看懂这个函数,首先要明白整个环境变量在内存中如何存储的问题。即以字符数组default_environment为格式存储
,default_environment复制给SD卡env分区,SD卡env分区复制给env_ptr指针指向的内存,他们三者的格式都是一样的。
(5)关键点:要明白环境变量在内存中存储的方式;
2.2、setenv
与setenv绑定的函数是do_setenv,do_setenv实际工作调用_do_setenv
int _do_setenv (int flag, int argc, char *argv[])
{
int i, len, oldval;
int console = -1;
uchar *env, *nxt = NULL;
char *name;
bd_t *bd = gd->bd;
uchar *env_data = env_get_addr(0);//获取内存中环境变量数组的首地址
if (!env_data) /* need copy in RAM */
return 1; //说明内存中没有环境变量
name = argv[1]; //参数1
if (strchr(name, '=')) { //提示设置时不要加等号 譬如set a=1,正确使用应该是 set a 1
printf ("## Error: illegal character '=' in variable name \"%s\"\n", name);
return 1;
}
/*
* search if variable with this name already exists
*/
oldval = -1;
for (env=env_data; *env; env=nxt+1) {//搜索此参数名称的环境变量是否已经存在
for (nxt=env; *nxt; ++nxt)
;
if ((oldval = envmatch((uchar *)name, env-env_data)) >= 0)
//oldval>=0,说明存在,并且此时env这个指针指向那个找到的环境变量的存储区域
break;
}
/*
* Delete any existing definition
*/
if (oldval >= 0) {
//如果原来就有先清空环境变量的存储区域再覆盖
}
//如果原来没有则在最后创建一个环境变量。
return 0;
}
1)setenv的思路就是:先去DDR中的环境变量处寻找原来有没有这个环境变量,如果原来就有则需要覆盖原来的环境变量,如果原来没有则在最后新增一个环境变量即可。
(2)本来setenv做完上面的就完了,但是还要考虑一些附加问题。
问题一:环境变量太多导致DDR中的字符数组溢出的解决方法。
问题二:有些环境变量如baudrate、ipaddr等,在gd中有对应的全局变量。这种环境变量在set更新的时候要同时去更新对应的全局变量,否则就会出现在本次运行中环境变量和全局变量不一致的情况。
2.3、saveenv
与saveenv绑定的函数是do_saveenv,实现环境变量的保存操作主要是saveenv函数,在uboot/common/cmd_nvedit.c中
(1)从uboot实际执行saveenv命令的输出,以及do_saveenv函数的内容,可以分析出:uboot实际使用的是env_auto.c文件中的相关内容,并且可以从x210_sd.h中的配置宏(#define CFG_ENV_IS_IN_AUTO)进一步确认。在env_auto.c中是使用宏定义的方式去条件编译了各种常见的flash芯片(如movinand、norflash、nand等)。主要体现在文件里的saveenv函数中通过读取 INF_REG 从而知道我们的启动介质,然后调用这种启动介质对应的操作函数来操作。
int saveenv(void)
{
#if defined(CONFIG_S5PC100) || defined(CONFIG_S5PC110) || defined(CONFIG_S5P6442)
if (INF_REG3_REG == 2)
saveenv_nand();
else if (INF_REG3_REG == 3)//INF_REG3_REG是开发板中的一个寄存器,判断我们从哪里启动的
saveenv_movinand();
else if (INF_REG3_REG == 1)
saveenv_onenand();
else if (INF_REG3_REG == 4)
saveenv_nor();
#elif defined(CONFIG_SMDK6440)
if (INF_REG3_REG == 3)
saveenv_nand();
else if (INF_REG3_REG == 4 || INF_REG3_REG == 5 || INF_REG3_REG == 6)
saveenv_nand_adv();
else if (INF_REG3_REG == 0 || INF_REG3_REG == 1 || INF_REG3_REG == 7)
saveenv_movinand();
#else // others
if (INF_REG3_REG == 2 || INF_REG3_REG == 3)
saveenv_nand();
else if (INF_REG3_REG == 4 || INF_REG3_REG == 5 || INF_REG3_REG == 6)
saveenv_nand_adv();
else if (INF_REG3_REG == 0 || INF_REG3_REG == 7)
saveenv_movinand();
else if (INF_REG3_REG == 1)
saveenv_onenand();
#endif
else
printf("Unknown boot device\n");
return 0;
}
(2)INF_REG寄存器地址:E010F000+0C=E010_F00C,数据手册中含义是用户自定义数据。我们在start.S中判断启动介质后将#BOOT_MMCSD(就是3,定义在x210_sd.h)写入了INF_REG这个寄存器,所以saveenv函数中读出的肯定是3,所以实际执行的函数是:saveenv_movinand
saveenv_movinand函数体:
int saveenv_movinand(void)
{
#if defined(CONFIG_CMD_MOVINAND)
movi_write_env(virt_to_phys((ulong)env_ptr));
//virt_to_phys函数将虚地址转化为实地址
puts("done\n");
return 1;
#else
return 0;
#endif /* CONFIG_CMD_MOVINAND */
}
(3)真正执行保存环境变量操作的是:cpu/s5pc11x/movi.c中的movi_write_env函数,这个函数肯定是写sd卡的,将DDR中的环境变量集合(其实就是env_ptr指向的内存区域,大小16kb = 32个扇区)写入iNand中的env分区中。
(4)raw_area_control是uboot中规划iNnad/SD卡的原始分区表,这个里面记录了我们对iNand的分区,env分区也在这里,下标是2,movi_read函数里面就是调用驱动部分的写SD卡/iNand的底层函数。
2.4、uboot内部获取环境变量
getenv和getenv_r不是一个环境变量,是uboot内部的一个函数,用于获取环境变量的值,在uboot/common/cmd_nvedit.c中。
2.4.1、getenv
(1)是不可重入的。
char *getenv (char *name)
{
int i, nxt;
WATCHDOG_RESET();
for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
int val;
for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {
if (nxt >= CFG_ENV_SIZE) {
return (NULL);
}
}
if ((val=envmatch((uchar *)name, i)) < 0)
continue;
return ((char *)env_get_addr(val));
}
return (NULL);
}
(2)实现方式就是去遍历数组,挨个拿出所有的环境变量比对name,找到相等的直接返回这个环境变量的首地址。
2.4.2、getenv_r
(1)可重入版本。
int getenv_r (char *name, char *buf, unsigned len)
{
int i, nxt;
for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
int val, n;
for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {
if (nxt >= CFG_ENV_SIZE) {
return (-1);
}
}
if ((val=envmatch((uchar *)name, i)) < 0)
continue;
/* found; copy out */
n = 0;
while ((len > n++) && (*buf++ = env_get_char(val++)) != '\0')
;
if (len == n)
*buf = '\0';
return (n);
}
return (-1);
}
(2)getenv函数是直接返回这个找到的环境变量在DDR中环境变量处的地址,而getenv_r函数的做法是找到了DDR中环境变量地址后,将这个环境变量复制一份到提供的buf中,而不动原来DDR中环境变量。
所以差别就是:getenv中返回的地址只能读不能随便乱写,而getenv_r中返回的环境变量是在自己提供的buf中,是可以随便改写加工的。文章来源:https://www.toymoban.com/news/detail-773686.html
三、总结
(1)功能是一样的,但是可重入版本会比较安全一些,建议使用。
(2)有关于环境变量的所有操作,主要在于理解环境变量在DDR中的存储方法,环境变量和gd全局变量的关联和优先级,理环境变量在存储介质中的存储方式(专用raw分区)。
(3)环境变量到底在内存的哪里?以下是我的分析过程。文章来源地址https://www.toymoban.com/news/detail-773686.html
fastboot只烧录了uboot、kernel、rootfs到SD卡,并没有烧录环境变量到env分区
上电,uboot启动,此时SD卡中没有环境变量可以使用
1.uboot第二阶段start_armboot函数中调用env_init
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 1;
2.后续start_armboot函数中调用env_relocate
3.env_relocate中
3.1env_ptr = (env_t *)malloc (CFG_ENV_SIZE);给env_ptr申请一片16KB的内存,即堆区
3.2调用env_relocate_spec
3.3gd->env_addr = (ulong)&(env_ptr->data);//将这片内存作为环境变量的区域使用
4.env_relocate_spec调用env_relocate_spec_movinand
5.env_relocate_spec_movinand中
5.1uint *magic = (uint*)(PHYS_SDRAM_1);//PHYS_SDRAM_1 = 0x3000 0000
5.2
5.2.1如果SD中有环境变量,则加载,加载方法是调用movi_read_env(virt_to_phys((ulong)env_ptr));//virt_to_phys是虚地址转换成实地址
5.2.2如果没有则调用use_default,将uboot中封装的一套默认环境变量default_environment复制到堆区,之后使用堆区的那一份。
假如5.2.1生效
6.movi_read_env调用movi_read
6.1movi_read(raw_area_control.image[2].start_blk,
raw_area_control.image[2].used_blk, addr);//start_blk是env分区的起始扇区号,used_blk是env分区的大小(多少个扇区),addr:将SD卡内的env分区重定位到这里
到了这里,关于uboot的环境变量相关源码分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!