一、环境变量基础知识
1、环境变量的作用
环境变量的最大作用就是在我们不需要改变源代码的情况下,改变程序的执行情况。比如我们的bootdelay时间,通过修改对应的环境变量的值,就可以改变uboot开机延时的时间,而不需要修改源代码,然后编译,烧录。省去了很多的麻烦。
uboot中两份环境变量,一份环境变量是程序中的,一份是存储在SD卡的环境变量分区中的。譬如我们的环境变量machid(机器码),uboot在x210_sd.h中定义了一个机器码2456,写死在程序中,不能更改的。如果想要改变uboot中配置的机器码,可以修改x210-sd.h中对应的机器码,但是比较麻烦。所以比较简单的方法,就是使用环境变量machid来修改uboot中的机器码。
2、环境变量的工作方式
在DDR中环境变量就是default_environment数组,在重定位的时候,它会被定位到DDR中的一个内存地址处,这个地址处,这个全局数组就是我们uboot我们uboot运行时的DDR中的环境变量。
但是我们SD卡中,也有一份环境变量,而且每次开机启动的时候,环境变量的数值,都是上一次关机时,保存在SD卡中的环境变量,怎么就是程序中的default_environment数组呢?原因是这样的,就是SD卡中那份环境变量重定位时,也会被加载到这个地址(就是我们default_environment数组的基地址,所以default_environment数组中的环境变量被SD卡中的环境变量覆盖了),所以我们看到的实际DDR中的环境变量,就是SD卡中的环境变量。
二、环境变量在uboot是如何存储的
1、default_environment数组
uchar default_environment[CFG_ENV_SIZE] = {
#ifdef CONFIG_BOOTARGS
"bootargs=" CONFIG_BOOTARGS "\0"
#endif
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif
.........
......
"\0"
};
所有的把所有的环境变量当作字符串存储在数组中,比如bootargs的存储,即使这样的"bootargs="+"root=/dev/mtdblock4 rootfstype=yaffs2 init=/init console=ttySAC2,115200"+'\0';然后后面接着其他的环境变量,不同的环境变量之间只用'\0'作为分隔符。了解了环境变量在uboot中存储方式,我们就知道如何查找一个一个环境变量了。
三、环境变量相关的命令源码解析
1、printenv命令:/uboot/common/cmd_nvedit.c中
(1)该命令对应的函数是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;
}
函数根据输入参数的个数,分为两个功能,但是两个分支的主要架构就是三个for循环结构体,而且作用都是相同的。第一、二个for循环用来寻找一个环境变量,第三个for循环处理这个找出来的环境变量。
"bootargs=" "root=/dev/mtdblock4 rootfstype=yaffs2 init=/init console=ttySAC2,115200""\0"
"bootcmd=" "bootp; " \
"setenv bootargs root=/dev/nfs nfsroot=${serverip}:${rootpath} " \
"ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}::off; " \
"bootm""\0"
...
这里第一、二个for循环,就是为了找到bootargs这个环境变量(它的第一个字节在哪,最后一个字节在哪),然后第三个环境变量就是来处理这个环境变量,进行环境变量的打印输出。
这里第一、二个for循环的结果就是这样了的,表i和nxt的位置标记好,然后在使用一个循环来编译i和nxt之间的所有信息,然后打印出来即可。
2、setenv:/uboot/common/cmd_nvedit.c中
int setenv (char *varname, char *varvalue)
{
char *argv[4] = { "setenv", varname, varvalue, NULL };
if (varvalue == NULL)
return _do_setenv (0, 2, argv);
else
return _do_setenv (0, 3, argv);
}
该命令有两种功能,一种是删除环境变量,一种是修改环境变量的值,如果传递进来的环境变量没有数值,就会删除这个环境变量,否则更改这个环境变量的数值为传递进来的数值。
3、saveenv命令:/uboot/common/cmd_nvedit.c中
(1)saveenv命令对应的函数do_saveenv,在该函数中调用了saveenv函数,这个函数有很多,为了找到正确的函数定义,我们必须从x210_sd.h中找到#define CFG_ENV_IS_IN_AUTO这个宏的定义,通过可能函数中,每个函数是否被编译链接,在文件的头部我们会找到#if defined(CFG_ENV_IS_IN_AUTO) /* Environment is in Non-Volatile Device */,会根据是否定义了相关的宏来判断是否将该函数参与编译链接,因为我们在x210_ssd.h中定义了CFG_ENV_IS_IN_AUTO,所以我们确定了函数是在env_auto.c文件中定义的。因为只有这个文件才会参与编译链接的过程。(auto函数中使用了宏定义的方法去条件编译各种常见的Flash芯片可以说是一个自动程序,它会根据我们的宏定义,判断我们Flash是哪个,我们的启动方式是什么,然后执行对应的函数)。
(2)这个saveenv函数中,程序会读取我们在start.S根据OMPin判断的启动方式,然后存储在INF_REG3_REG寄存器中,这里就读取该寄存器的数值,然后判断启动方式。INF_REG3_REG是一个用户自定义的寄存器。
(3)根据寄存器的数值,我们执行了saveenv_movinand()函数,将环境变量保存 ,使用函数movi_write_env(virt_to_phys((ulong)env_ptr));
- virt_to_phys:将虚拟地址转换成物理地址,我们知道在uboot中只有0xC0000000-0xCFFFFFFF映射到了0x3000000--0x3FFFFFFF的地址处。
- env_ptr:env_t *env_ptr = (env_t *)(&environment[0])是一个全局变量指针,指向的是DDR环境变量,大小为16KB,写入iNand的env分区。
(4)在movi_write_env调用了movi_write_env函数,该函数中使用了raw_area_control,raw_area_control是uboot规划iNand/SD卡的原始分区表,这里记录了我们对iNand/SD卡的分区,env分区也在这里,下标是2。
4、uboot获取环境变量的两个函数char *getenv (char *name)和int getenv_r (char *name, char *buf, unsigned len)
(1)两个函数的不同在于,getenv是不可重入的,getenv_r是可重入的,简单来说,我们从输入的参数就可以看出来,getenv传递的参数只有命令的名字,输出参数之后一个指针,这个指针指向就是这个命令所对应的实际地址,就是uboot中保存这个环境变量的地址。所以我们修改了这个传递回来的数值,就代表修改了uboot中的环境变量。但是第二个函数,是通过输出一个接收数组来接收的,也就说copy了一份代码到buf中,这样就可以在buf中随意处理这段字符,而不会修改uboot中的环境变量。但是两者的实现原理都是一样的。所以使用后者较为安全。