英山縣住房和城鄉(xiāng)建設局網(wǎng)站搜索推廣開戶
一、uboot 的環(huán)境變量基礎
1、環(huán)境變量的作用
(1) 讓我們可以不用修改 uboot 的源代碼,而是通過修改環(huán)境變量,來影響 uboot 運行時的一些數(shù)據(jù)和特性。譬如說,通過修改 bootdelay 環(huán)境變量,就可以更改系統(tǒng)開機自動啟動時倒數(shù)的秒數(shù)。
2、環(huán)境變量的優(yōu)先級
(1) uboot 代碼當中有一個值,環(huán)境變量中也有一個值。uboot 程序?qū)嶋H運行時規(guī)則是:如果環(huán)境變量為空,則使用代碼中的值;如果環(huán)境變量不為空,則優(yōu)先使用環(huán)境變量對應的值。
(2) 譬如 machid(機器碼)。uboot 中在 x210_sd.h 中定義了一個機器碼 2456,寫死在程序中的,不能更改。如果要修改 uboot 中配置的機器碼,可以修改 x210_sd.h 中的機器碼,但是修改源代碼后需要重新編譯燒錄,很麻煩;
比較簡單的方法就是,使用環(huán)境變量 machid。set machid 0x998 類似這樣,有了 machid 環(huán)境變量后,系統(tǒng)啟動時會優(yōu)先使用 machid 對應的環(huán)境變量,這就是優(yōu)先級問題。
3、環(huán)境變量在 uboot 中工作方式
(1) 默認環(huán)境變量,在 uboot/common/env_common.c 中 default_environment,這東西本質(zhì)是一個字符數(shù)組,大小為 CFG_ENV_SIZE(16 kByte),里面內(nèi)容就是很多個環(huán)境變量連續(xù)分布組成的,每個環(huán)境變量最末端以 ‘\0’ 結(jié)束。
(2) SD 卡中的環(huán)境變量分區(qū),在 uboot 的 raw 分區(qū)中。SD 卡中其實就是給了個分區(qū),專門用來存儲而已。存儲時其實是把 DDR 中的環(huán)境變量整體的寫入 SD 卡中分區(qū)里。所以當我們 saveenv 時,其實整個所有的環(huán)境變量都被保存了一遍,而不是只保存更改了的。
(3) DDR 中環(huán)境變量,在 default_environment 中,實質(zhì)是字符數(shù)組。在 uboot 中其實是一個全局變量,鏈接時在數(shù)據(jù)段;重定位時,default_environment 就被重定位到 DDR 中一個內(nèi)存地址處了。這個地址處,這個全局字符數(shù)組,就是我們 uboot 運行時的 DDR 中的環(huán)境變量了。
總結(jié):
- 剛燒錄的系統(tǒng)中,環(huán)境變量分區(qū)是空白的,uboot 第一次運行時,加載的是 uboot 代碼中自帶的一份環(huán)境變量,叫默認環(huán)境變量 default_environment。
- 我們在 saveenv 時,DDR 中的環(huán)境變量會被更新到 SD 卡中的環(huán)境變量中,就可以被保存下來,下次開機會在環(huán)境變量 relocate 時,SD 卡中的環(huán)境變量會被加載到 DDR 中去。
- default_environment 中的內(nèi)容雖然被 uboot 源代碼初始化為一定的值(這個值就是我們的默認環(huán)境變量),但是在 uboot 啟動的第二階段,env_relocate 時代碼會去判斷 SD 卡中的 env 分區(qū)的 crc 是否通過。如果 crc 校驗通過,說明 SD 卡中有正確的環(huán)境變量存儲,則 relocate 函數(shù)會從 SD 卡中讀取環(huán)境變量來覆蓋 default_environment 字符數(shù)組,從而每次開機可以保持上一次更改過的環(huán)境變量。
二、環(huán)境變量相關命令源碼解析1
1、printenv
(1) 找到 printenv 命令所對應的函數(shù)。通過 printenv 的 help 可以看出,這個命令有 2 種使用方法。第一種直接使用不加參數(shù),則打印所有的環(huán)境變量;第二種是 printenv name ,則只打印出 name 這個環(huán)境變量的值。
(2) 分析 do_printenv 函數(shù)。
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;
}
不論 SD 卡中是否有環(huán)境變量,以及是否發(fā)生環(huán)境變量覆蓋,環(huán)境變量的首地址總是 default_environment 字符數(shù)組的首地址。
(3) do_printenv 函數(shù)首先區(qū)分 argc=1 還是不等于 1 的情況,若 argc=1 ,那么就循環(huán)打印所有的環(huán)境變量出來;如果 argc 不等于 1,則后面的參數(shù)就是要打印的環(huán)境變量,給哪個就打印哪個。
(4) argc=1 時,用雙重 for 循環(huán)來依次處理所有的環(huán)境變量的打印。第一重 for 循環(huán)就是處理各個環(huán)境變量。所以有多少個環(huán)境變量,則第一重就執(zhí)行循環(huán)多少圈。
(5) 這個函數(shù)要看懂,首先要明白整個環(huán)境變量在內(nèi)存中如何存儲的問題。
(6) 關鍵點:第一,要明白環(huán)境變量在內(nèi)存中存儲的方式;第二,要 C 語言處理字符串的功底要好。
三、環(huán)境變量相關命令源碼解析 2
1、setenv
(1) 命令定義和對應的函數(shù)在 uboot/common/cmd_nvedit.c 中,對應的函數(shù)為 do_setenv。
(2) setenv 的思路就是:先去 DDR 中的環(huán)境變量處尋找原來有沒有這個環(huán)境變量,如果原來就有,則需要覆蓋原來的環(huán)境變量,如果原來沒有則在最后新增一個環(huán)境變量即可。
第1步:遍歷 DDR 中環(huán)境變量的數(shù)組,找到原來就有的那個環(huán)境變量對應的地址。168-174 行。
第2步:擦除原來的環(huán)境變量,259-265 行。
第3步:寫入新的環(huán)境變量,266-273 行。
(3)本來 setenv 做完上面的就完了,但是還要考慮一些附加的問題。
問題一:環(huán)境變量太多,超出 DDR 中的字符數(shù)組,溢出的解決方法。
問題二:有些環(huán)境變量如 baudrate、ipaddr 等,在 gd 中有對應的全局變量。這種環(huán)境變量在 set 更新的時候,要同時去更新對應的全局變量,否則就會出現(xiàn)在本次運行中,環(huán)境變量和全局變量的值不一致的情況。
四、環(huán)境變量相關命令源碼解析 2
1、saveenv
(1) 在 uboot/common/cmd_nvedit.c 中,對應函數(shù)為 do_saveenv。
(2) 從 uboot 實際執(zhí)行 saveenv 命令的輸出,和 x210_sd.h 中的配置(#define CFG_ENV_IS_IN_AUTO)可以分析出:我們實際使用的是 env_auto.c 中相關的內(nèi)容。沒有一種芯片叫 auto 的,env_auto.c 中是使用宏定義的方式,去條件編譯了各種常見的 flash 芯片(如 movinand、norflash、nand 等)。然后在程序中讀取 INF_REG(OMpin 內(nèi)部對應的寄存器)從而知道我們的啟動介質(zhì),然后調(diào)用這種啟動介質(zhì)對應的操作函數(shù)來操作。
(3) do_saveenv 內(nèi)部調(diào)用 env_auto.c 中的 saveenv 函數(shù)來執(zhí)行實際的環(huán)境變量保存操作。
(4) 寄存器地址:E010F000 + 0C=E010_F00C,含義是用戶自定義數(shù)據(jù)。我們在 start.S 中判斷啟動介質(zhì)后,將 #BOOT_MMCSD(就是 3,定義在x210_sd.h)寫入了這個寄存器,所以這里讀出的肯定是 3,經(jīng)過判斷就是 movinand。所以實際執(zhí)行的函數(shù)是:saveenv_movinand。
(5) 真正執(zhí)行保存環(huán)境變量操作的是:cpu/s5pc11x/movi.c 中的 movi_write_env 函數(shù),這個函數(shù)肯定是寫 sd卡,將 DDR 中的環(huán)境變量數(shù)組(其實就是 default_environment 這個數(shù)組,大小 16kb,剛好 32 個扇區(qū))寫入 iNand 中的 ENV 分區(qū)中。
(6) raw_area_control 是 uboot 中規(guī)劃 iNnad/SD 卡的原始分區(qū)表,這個里面記錄了我們對 iNand 的分區(qū),env 分區(qū)也在這里,下標是2。 追到這一層就夠了,再里面就是調(diào)用驅(qū)動部分的寫 SD卡/iNand 的底層函數(shù)了。
int init_raw_area_table (block_dev_desc_t * dev_desc)
{struct mmc *host = find_mmc_device(dev_desc->dev);/* when last block does not have raw_area definition. */if (raw_area_control.magic_number != MAGIC_NUMBER_MOVI) {int i = 0;member_t *image;u32 capacity;if (host->high_capacity) {capacity = host->capacity;#ifdef CONFIG_S3C6410if(IS_SD(host))capacity -= 1024;#endif} else {capacity = host->capacity;}dev_desc->block_read(dev_desc->dev,capacity - (eFUSE_SIZE/MOVI_BLKSIZE) - 1,1, &raw_area_control);if (raw_area_control.magic_number == MAGIC_NUMBER_MOVI) {return 0;}dbg("Warning: cannot find the raw area table(%p) %08x\n",&raw_area_control, raw_area_control.magic_number);/* add magic number */raw_area_control.magic_number = MAGIC_NUMBER_MOVI;/* init raw_area will be 16MB */raw_area_control.start_blk = 16*1024*1024/MOVI_BLKSIZE;raw_area_control.total_blk = capacity;raw_area_control.next_raw_area = 0;strcpy(raw_area_control.description, "initial raw table");image = raw_area_control.image;#if defined(CONFIG_EVT1)#if defined(CONFIG_FUSED)/* image 0 should be fwbl1 */image[0].start_blk = (eFUSE_SIZE/MOVI_BLKSIZE);image[0].used_blk = MOVI_FWBL1_BLKCNT;image[0].size = FWBL1_SIZE;image[0].attribute = 0x0;strcpy(image[0].description, "fwbl1");dbg("fwbl1: %d\n", image[0].start_blk);#endif
#endif/* image 1 should be bl2 */
#if defined(CONFIG_EVT1)#if defined(CONFIG_FUSED)image[1].start_blk = image[0].start_blk + MOVI_FWBL1_BLKCNT;#elseimage[1].start_blk = (eFUSE_SIZE/MOVI_BLKSIZE);#endif
#elseimage[1].start_blk = capacity - (eFUSE_SIZE/MOVI_BLKSIZE) -MOVI_BL1_BLKCNT;
#endifimage[1].used_blk = MOVI_BL1_BLKCNT;image[1].size = SS_SIZE;image[1].attribute = 0x1;strcpy(image[1].description, "u-boot parted");dbg("bl1: %d\n", image[1].start_blk);/* image 2 should be environment */
#if defined(CONFIG_EVT1)image[2].start_blk = image[1].start_blk + MOVI_BL1_BLKCNT;
#elseimage[2].start_blk = image[1].start_blk - MOVI_ENV_BLKCNT;
#endifimage[2].used_blk = MOVI_ENV_BLKCNT;image[2].size = CFG_ENV_SIZE;image[2].attribute = 0x10;strcpy(image[2].description, "environment");dbg("env: %d\n", image[2].start_blk);/* image 3 should be bl2 */
#if defined(CONFIG_EVT1)image[3].start_blk = image[2].start_blk + MOVI_ENV_BLKCNT;
#elseimage[3].start_blk = image[2].start_blk - MOVI_BL2_BLKCNT;
#endifimage[3].used_blk = MOVI_BL2_BLKCNT;image[3].size = PART_SIZE_BL;image[3].attribute = 0x2;strcpy(image[3].description, "u-boot");dbg("bl2: %d\n", image[3].start_blk);/* image 4 should be kernel */
#if defined(CONFIG_EVT1)image[4].start_blk = image[3].start_blk + MOVI_BL2_BLKCNT;
#elseimage[4].start_blk = image[3].start_blk - MOVI_ZIMAGE_BLKCNT;
#endifimage[4].used_blk = MOVI_ZIMAGE_BLKCNT;image[4].size = PART_SIZE_KERNEL;image[4].attribute = 0x4;strcpy(image[4].description, "kernel");dbg("knl: %d\n", image[4].start_blk);/* image 5 should be RFS */
#if defined(CONFIG_EVT1)image[5].start_blk = image[4].start_blk + MOVI_ZIMAGE_BLKCNT;
#elseimage[5].start_blk = image[4].start_blk - MOVI_ROOTFS_BLKCNT;
#endifimage[5].used_blk = MOVI_ROOTFS_BLKCNT;image[5].size = PART_SIZE_ROOTFS;image[5].attribute = 0x8;strcpy(image[5].description, "rfs");dbg("rfs: %d\n", image[5].start_blk);for (i=6; i<15; i++) {raw_area_control.image[i].start_blk = 0;raw_area_control.image[i].used_blk = 0;}}
}
五、uboot 內(nèi)部獲取環(huán)境變量
1、getenv
(1) 應該是不可重入的。
(2) 實現(xiàn)方式就是,去遍歷 default_environment 數(shù)組,挨個拿出所有的環(huán)境變量比對 name,找到相等的直接返回這個環(huán)境變量的首地址即可。
2、getenv_r
(1) 可重入版本。(可自行搜索補充可重入函數(shù)的概念)
(2) getenv 函數(shù)是直接返回這個找到的環(huán)境變量在 DDR 中環(huán)境變量處的地址,而 getenv_r 函數(shù)的做法是,找到了 DDR 中環(huán)境變量地址后,將這個環(huán)境變量復制一份到提供的 buf 中,而不去動原來 DDR 中環(huán)境變量。
所以差別就是:getenv 中返回的地址,只能讀,不能隨便亂寫,而 getenv_r 中返回的環(huán)境變量是在自己提供的 buf 中,是可以隨便改寫加工的。
3、總結(jié)
(1) 功能是一樣的,但是可重入版本會比較安全一些,建議使用。
(2) 有關于環(huán)境變量的所有操作,主要理解了環(huán)境變量在 DDR 中的存儲方法,理解了環(huán)境變量和 gd 全局變量的關聯(lián)和優(yōu)先級,理解了環(huán)境變量在存儲介質(zhì)中的存儲方式(專用raw分區(qū)),整個環(huán)境變量相關的都清楚了。
源自朱有鵬老師.