从零开始的UBOOT的学习--环境变量
参考朱有鹏UBOOT全集
1、环境变量的作用
让我们可以不用修改UBOOT的源代码,而是通过修改环境变量来影响UBOOT运行的一些数据和特性,比如通过修改bootdelay环境变量就可以更改系统开机自动启动的倒数的秒数。
2、环境变量的优先级
(1)uboot代码中当中有一个值,环境变量中也有一个值。
其实UBOOT代码中的值是存放在内存当中的,环境变量的值是存放在硬盘当中的,所以就算是掉电的话,也不会把环境变量的值改变。
(2)比如machid机器码,UBOOT中在x210_sd.h中定义了一个机器码2456,写死在程序中的不能进行修改,但是如果需要修改这个UBOOT中的机器码的话,可以有两种方式:
1、更改源代码,修改机器码的全局变量
2、或者是直接修改环境变量的值,下次掉电启动的时候就可以生效。
设置环境变量的方法。
set machid 0x998类似这样,有了machid环境变量后,系统启动后会优先使用machid对应的环境变量,这就是优先级的问题。
3、环境变量的存储方式
(1)默认环境变量,在UBOOT/common/env_common.c中的default_environment,这东西本质上是一个字符数组,大小为CFG_ENV_SIZE(16KB)里面内容很多个环境变量连续分布组成的。每个环境变量都是以最末端'\0'结束。
(2)SD卡中的环境变量分区,在UBOOT的raw分区中,SD卡中其实就是给了一个分区,什么是分区,(分区好像是很牛逼的名词,但是实际上并不是很牛逼的名词,就是划定一块区域地址给你放置环境变量)
(3)DDR中的环境变量,在default_environment实际是字符数组,在UBOOT中其实就是一个全局变量的数组。
总结:刚烧录的系统中环境变量分区是空白的,UBOOT的第一次运行时加载的是UBOOT中DDR中自带的环境变量,叫做默认环境变量,我们在saveenv时DDR中的环境变量会被更新到SD卡中的环境变量中,就可以被保存下来,下次开机会在环境变量重定位的时候会把SD卡中的环境变量会被加载到DDR中去。
为什么我们每次执行都会从UBOOT的环境变量中读取数据,而不是直接从全局数组中读取数值?
虽然这个字符数组内容会被UBOOT源代码初始化为一定的值,(这个值就是我们默认的环境变量)但是在UBOOT启动的第二阶段,env_relocate时代码会去判断SD卡中的ENV分区的CRC是否通过。如果CRC校验通过说明SD卡中有正确的环境变量存储。
则relocate函数会从SD卡中读取环境变量来覆盖这个字符数组。从而每次开机都可以保持上一次开机更改过的环境变量的值。
环境变量在内存中的存储方法:代码小节
uchar default_environment[CFG_ENV_SIZE] = {
#else
uchar default_environment[] = {
#endif
#ifdef CONFIG_BOOTARGS
"bootargs=" CONFIG_BOOTARGS "\0"
#endif
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif
#if 0 /* for fast booting */
"verify=" MK_STR(no) "\0"
#endif
#ifdef CONFIG_MTDPARTITION
"mtdpart=" CONFIG_MTDPARTITION "\0"
#endif
#ifdef CONFIG_RAMBOOTCOMMAND
"ramboot=" CONFIG_RAMBOOTCOMMAND "\0"
#endif
#ifdef CONFIG_NFSBOOTCOMMAND
"nfsboot=" CONFIG_NFSBOOTCOMMAND "\0"
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
"bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0"
#endif
#if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
"baudrate=" MK_STR(CONFIG_BAUDRATE) "\0"
#endif
#ifdef CONFIG_LOADS_ECHO
"loads_echo=" MK_STR(CONFIG_LOADS_ECHO) "\0"
#endif
#ifdef CONFIG_ETHADDR
"ethaddr=" MK_STR(CONFIG_ETHADDR) "\0"
#endif
#ifdef CONFIG_ETH1ADDR
"eth1addr=" MK_STR(CONFIG_ETH1ADDR) "\0"
#endif
4、环境变量的有关命令和函数小结
(1)输出所有的环境变量:printenv
注册这个printenv这个函数
里面主要是调用这个函数do_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"
);
参数1:cmd_tbl_t 结构体的参数
这个是命令结构体的参数:
首先第一个是命令的名字:
第二个是最大允许的参数的个数
第三个是是否接受可重复进行命令
第四个是命令执行体
第五个或者第六个是执行的命令的帮助信息。
struct cmd_tbl_s {
char *name; /* Command Name */
int maxargs; /* maximum number of arguments */
int repeatable; /* autorepeat allowed? */
/* Implementation function */
int (*cmd)(struct cmd_tbl_s *, int, int, char *[]);
char *usage; /* Usage message (short) */
#ifdef CFG_LONGHELP
char *help; /* Help message (long) */
#endif
};
flag:这个还不知道是什么参数
argc:就是输入参数的个数,包含命令,也就是说命令也算一个参数
argv:就是命令输入的内容信息。
int do_printenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
int i, j, k, nxt;
int rcode = 0;
if (argc == 1)
{
//以'\0'为单位,把每一个环境变量的值作为分界线,不断的循环遍历
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');
}
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;
//当env_get_char这个是得出字符数组中的字符,
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;
}
(2)setenv,设置环境变量的函数,
(1)命令定义和对应函数在uboot/common/cmd_nvedit.c中对应的函数为do_setenv
(2)setenv的思路就是:先去DDR中的环境变量处寻找原来没有这个环境变量,如果原来有的话,就需要覆盖原来的环境变量,如果原来没有的话则在最后新增一个环境变量即可。
步奏:
1、遍历DDR中环境变量的数组,找到原来就有的那个环境变量对应的地址,168-174行。
2、檫除原来的环境变量,都是用'\0'表示。
3、写入新的环境变量
本来setenv已经完成了,但是还是需要考虑一些事情。
环境变量如果太大的话,会超出DDR中的字符数组,溢出的解决方法。
有些环境变量如baurate/ipaddr等,在gd中有对应的全局变量,这种环境变量在set更新的时候要同时去更新对应的全局变量,否则就会出现在本次运行中环境变量和全局变量不一致的情况。
(3)saveenv,保存环境变量的参数
在UBOOT/common/cmd_nvedit.c中对应函数为do_saveenv
从UBOOT实际执行saveenv命令的输出,和x210_sd.h中的配置,可以分析出,我们实际使用的env_auto.c中的相关的内容,没有一种芯片叫做auto的,env_auto.c中是使用宏定义的方式去条件编译了各种常见的flash芯片(movinand/norflash,nand等),然后在程序中读取INF_REG,从而知道我们的启动介质。
设置保存环境变量的参数
实际调用的函数就是saveenv()
int do_saveenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
extern char * env_name_spec;
printf ("Saving Environment to %s...\n", env_name_spec);
return (saveenv() ? 1 : 0);
}
//实际进行保存的函数:saveenv_movinand()
因为我们这个开发板是用INAND启动的,所以,具体的底层的驱动函数就先不说了。
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)
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;
}
(4)UBOOT获取内部的环境变量,这个不是命令,这是是中间的那些函数的中间函数。
2.9.5.1、getenv
(1)应该是不可重入的。
(2)实现方式就是去遍历default_environment数组,挨个拿出所有的环境变量比对name,找到相等的直接返回这个环境变量的首地址即可。
2.9.5.2、getenv_r
(1)可重入版本。(可自行搜索补充可重入函数的概念)
(2)getenv函数是直接返回这个找到的环境变量在DDR中环境变量处的地址,而getenv_r函数的做法是找到了DDR中环境变量地址后,将这个环境变量复制一份到提供的buf中,而不动原来DDR中环境变量。
所以差别就是:getenv中返回的地址只能读不能随便乱写,而getenv_r中返回的环境变量是在自己提供的buf中,是可以随便改写加工的。
通过名字,可以通过访问到env的名字,还有存储空间。
不断的循环遍历那个字符数组
并把得到的值存储到buf这个内存当中。
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);
}
总结:
(1)功能是一样的,但是可重入版本会比较安全一些,建议使用。
(2)有关于环境变量的所有操作,主要理解了环境变量在DDR中的存储方法,理解了环境变量和gd全局变量的关联和优先级,理解了环境变量在存储介质中的存储方式(专用raw分区),整个环境变量相关的都清楚了。