在了解了uboot的启动流程(uboot启动流程分析)之后,我们详细得来看uboot的环境变量在内存的组织方式和命令行是如何被执行的。
环境变量
我们知道在uboot的命令行输入pri命令可以打印出uboot的环境变量信息,这些环境变量是如何存储的?已及是怎么设置的?在内存中的组织方式是怎么样的?
是我们这篇要研究明白的。
环境变量是如何存储的?在uboot 第一次烧入进开发板时,初始那些默认的环境变量是整合进uboot 程序里面的(存放在default_environment变量中)。
我们在命令行使用save命令后,会将环境变量保存进我们划定环境变量的存储区,这个存储区位于Flash中。(可以指定为Nor Flash或者是Nand Flash)
以s3c2440芯片的uboot为例,我们在配置文件中指定了内存区域的划分:
MTDPARTS_DEFAULT这个宏就指定了我们要划分的区域,我们在烧写uboot的使用就是按照这个划分进行烧写的。
实际上MTDPARTS_DEFAULT并没有真正指定区域的划分,只是定义了每个区域的名称,方便我们进行操作。而这里的划分,和我们用于实际划分Flash的宏定义是一样的,为了方便说明,我们直接用mtdparts来代替实际的各种宏定义。
Flash中紧接着在uboot区域后面的128K的存储空间被我们拿来存储环境变量。
如下图就是我们的jz2440开发板上的Nand Flash存储空间划分:
上面我们了解到了环境变量存储的位置。在uboot中通过一个env_t类型来管理环境变量的在内存的表示(由env_t这个数据结构来管理全部的环境变量所占的存储空间)。该类型定义如下:
typedef struct environment_s {
uint32_t crc;
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
unsigned char flags;
#endif
unsigned char data[ENV_SIZE];
} env_t;
#ifdef ENV_IS_EMBEDDED
extern env_t environment;
#endif
- crc 和flags 组成ENV_HEADER,环境变量的头部信息;
- 而余下的环境变量信息全部存储在data空间中;
环境变量的存储空间由两部分组成头部(CRC、flags)和负载(data)。负载包含了所有环境变量数据,而头部由CRC校验码和一个flags参数组成。这两个部分会一起被写入到Flash中。
在env_init()函数中会将默认的环境变量(default_environment)的data存储在全局变量gd中:
env_t *env_ptr = &environment; ... gd->env_addr = (ulong)env_ptr->data; ...
在我们调用save命令时,会使用相关的write函数将环境变量写到Flash中:
int saveenv(void)
{
...
puts("Erasing Nand...n");
if (nand_erase_opts(&nand_info[0], &nand_erase_options))
return 1;
puts("Writing to Nand... ");
if (writeenv(CONFIG_ENV_OFFSET, (u_char *)&env_new)) {
puts("FAILED!n");
return 1;
}
...
}
如上面对Nand Flash的操作中,先调用nand_erase_opts擦除一个block,然后再调用writeenv将环境变量写入到这个block中。这也就是为什么我们的环境变量存储区刚好为128K,其正好是我们使用的Nand Flash芯片的block大小。不同的芯片,其block的大小不同,环境变量的区域也不一定相同。
使用默认的环境变量或者在uboot刚烧录进开发板时,这时候一般情况我们的Flash中还没有写入环境变量的相关信息。这时就需要使用默认的环境变量,也就是default_environment,这个变量定义在uboot内部,位于uboot的数据段中:
gd->env_addr = (ulong)&default_environment[0]; gd->env_valid = 1;
default_environment的定义如下方,在编译时期会根据宏开关,对default_environment数组进行填充:
const uchar default_environment[] = {
#ifdef CONFIG_BOOTARGS
"bootargs=" CONFIG_BOOTARGS " "
#endif
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND " "
#endif
#ifdef CONFIG_RAMBOOTCOMMAND
"ramboot=" CONFIG_RAMBOOTCOMMAND " "
#endif
#ifdef CONFIG_NFSBOOTCOMMAND
"nfsboot=" CONFIG_NFSBOOTCOMMAND " "
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
"bootdelay=" MK_STR(CONFIG_BOOTDELAY) " "
#endif
#if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
"baudrate=" MK_STR(CONFIG_BAUDRATE) " "
#endif
#ifdef CONFIG_LOADS_ECHO
"loads_echo=" MK_STR(CONFIG_LOADS_ECHO) " "
#endif
#ifdef CONFIG_ETHADDR
"ethaddr=" MK_STR(CONFIG_ETHADDR) " "
#endif
#ifdef CONFIG_ETH1ADDR
"eth1addr=" MK_STR(CONFIG_ETH1ADDR) " "
#endif
#ifdef CONFIG_ETH2ADDR
"eth2addr=" MK_STR(CONFIG_ETH2ADDR) " "
#endif
#ifdef CONFIG_ETH3ADDR
"eth3addr=" MK_STR(CONFIG_ETH3ADDR) " "
#endif
#ifdef CONFIG_ETH4ADDR
"eth4addr=" MK_STR(CONFIG_ETH4ADDR) " "
#endif
#ifdef CONFIG_ETH5ADDR
"eth5addr=" MK_STR(CONFIG_ETH5ADDR) " "
#endif
#ifdef CONFIG_IPADDR
"ipaddr=" MK_STR(CONFIG_IPADDR) " "
#endif
#ifdef CONFIG_SERVERIP
"serverip=" MK_STR(CONFIG_SERVERIP) " "
#endif
#ifdef CONFIG_SYS_AUTOLOAD
"autoload=" CONFIG_SYS_AUTOLOAD " "
#endif
#ifdef CONFIG_PREBOOT
"preboot=" CONFIG_PREBOOT " "
#endif
#ifdef CONFIG_ROOTPATH
"rootpath=" CONFIG_ROOTPATH " "
#endif
#ifdef CONFIG_GATEWAYIP
"gatewayip=" MK_STR(CONFIG_GATEWAYIP) " "
#endif
#ifdef CONFIG_NETMASK
"netmask=" MK_STR(CONFIG_NETMASK) " "
#endif
#ifdef CONFIG_HOSTNAME
"hostname=" MK_STR(CONFIG_HOSTNAME) " "
#endif
#ifdef CONFIG_BOOTFILE
"bootfile=" CONFIG_BOOTFILE " "
#endif
#ifdef CONFIG_LOADADDR
"loadaddr=" MK_STR(CONFIG_LOADADDR) " "
#endif
#ifdef CONFIG_CLOCKS_IN_MHZ
"clocks_in_mhz=1 "
#endif
#if defined(CONFIG_PCI_BOOTDELAY) && (CONFIG_PCI_BOOTDELAY > 0)
"pcidelay=" MK_STR(CONFIG_PCI_BOOTDELAY) " "
#endif
#ifdef CONFIG_EXTRA_ENV_SETTINGS
CONFIG_EXTRA_ENV_SETTINGS
#endif
" "
};
环境变量的设置流程
其设置的流程图如下:
环境变量的设置分为两个阶段进行设置,在第一阶段也就是board_init_f阶段,会调用env_init()函数对环境变量进行初始化设置。这个阶段只能完成存储在Nor Flash的环境变量的读取操作。而Nand Flash的环境变量,由于此时Nand Flash还没有初始化(一般在第二阶段进行Nand Flash的初始化),因此读不出存储于Flash中的环境变量。
在uboot启动流程的第二阶段,也就是在board_init_r函数内,会调用env_relocate()函数,对Nand Flash该函数才真正完成了初始化的工作。env_relocate会调用himport_r函数将gd->env_addr也就是env_t中的data 填充到env_htab这个环境变量的哈希表中。
env_htab:是uboot用于存储环境变量的一张表格,其定义如下:
struct hsearch_data { struct _ENTRY *table; unsigned int size; unsigned int filled; }; typedef struct _ENTRY { int used; ENTRY entry; } _ENTRY; typedef struct entry { const char *key; char *data; } ENTRY;
对于env_htab设置的思想如下:
- 首先会尝试从Flash(存储区)里读取出环境变量的信息(env_t),根据CRC校验,如果校验成功就会用存储区中的env_t设置env_htab;
- 如果校验失败就会使用uboot里面自带的default_environment来设置env_htab;
这么做很巧妙,如果我们第一次将uboot 烧录到开发板,那么正常情况下Flash中的校验码是不正确的,这时就会使用默认的default_environment设置我们的uboot环境变量。而当后面我们调用save将环境写入到Flash后,下次重启开发板时,重新读取Flash,这时CRC 校验就会是正确的,那么就会使用保存在Flash的env_t。
而我们后续对环境变量的修改都会被写入到Flash中,这样就实现了动态修改环境变量的功能。
在board.c 的第二阶段(board_init_r函数)最后会调用main_loop() 进入一个主循环。
这个主循环主要的工作有下面两点:
- 进入引导程序的倒计时,若在倒计时内没有按下任何键,则会自动引导启动内核;
- 如果按下了按键,则会进入uboot界面,输出命令行提示符,等待用户输入命令,然后根据命令进行一些列操作;
详细的流程如上图所示,main_loop()会调用abortboot()函数,在abortboot()中实现倒计时的功能,若在倒计时期间,有按键按下:
- 有按键按下,函数返回值为“1”,这时就会进入左边的分支。调用readline()等待用户输入,用户输入的命令会被run_command()函数进行解析,然后调用相应的接口进行执行。
- 无按键按下,函数返回值为“0”,这时就会进入右边的分支。调用getenv(“bootm”),获取启动内核的相关含操作,然后调用run_command运行bootm命令,由bootm启动内核;
如上图所示,在配置文件中我们定义了bootm的启动命令,宏CONFIG_BOOTCOMMAND;其中CONFIG_BOOTARGS 是传递给内核的参数,ttySAC0表示,使用的输出串口,root=/dev/mtdblock3 表示根文件系统所在的块。



