u-boot的環境變量 用來存儲一些經常使用的參數變量,uboot 希望將環境變量存儲在靜態存儲器中(如 nand?nor?eeprom?mmc )。
其中有一些也是大家經常使用,有一些是使用人員自己定義的,更改這些名字會出現錯誤,下面的表中我們列出了一些常用的環境變量:
?????bootdelay????執行自動啟動的等候秒數 ?????baudrate?????串口控制臺的波特率 ?????netmask?????以太網接口的掩碼 ?????ethaddr???????以太網卡的網卡物理地址 ?????bootfile????????缺省的下載文件 ?????bootargs?????傳遞給內核的啟動參數 ?????bootcmd?????自動啟動時執行的命令 ?????serverip???????服務器端的 ip 地址 ?????ipaddr?????????本地 ip? 地址 ?????stdin???????????標準輸入設備 ?????stdout????????標準輸出設備 ?????stderr?????????標準出錯設備
上面這些是uboot 默認存在的 環境變量,uboot本身會使用這些環境變量來進行配置。我們可以自己定義一些環境變量來供我們自己 uboot 驅動來使用。
Uboot環境變量的設計邏輯是在啟動過程中將 env 從靜態存儲器中讀出放到 RAM 中,之后在 uboot 下對 env 的操作(如 printenv?editenv?setenv )都是對 RAM 中 env 的操作,只有在執行 saveenv 時才會將 RAM 中的 env 重新寫入靜態存儲器中。
這種設計邏輯可以加快對env 的讀寫速度。
基于這種設計邏輯,2014.4 版本 uboot 實現了 saveenv 這個保存 env 到靜態存儲器的命令,而沒有實現讀取 env 到 RAM 的命令。
那我們就來看一下uboot 中 env 的數據結構?初始化?操作如何實現的。
一?env 數據結構
在include/environment.h 中定義了 env_t ,如下:
[cpp] view plaincopyprint?
#ifdef?CONFIG_SYS_REDUNDAND_ENVIRONMENT ??#?define?ENV_HEADER_SIZE????(sizeof(uint32_t)?+?1) ??#?define?ACTIVE_FLAG???1 ??#?define?OBSOLETE_FLAG?0 ??#else ??#?define?ENV_HEADER_SIZE????(sizeof(uint32_t)) ??#endif ??#define?ENV_SIZE?(CONFIG_ENV_SIZE?-?ENV_HEADER_SIZE) ??typedef ?struct ?environment_s?{??????uint32_t????crc;?????????? #ifdef?CONFIG_SYS_REDUNDAND_ENVIRONMENT ??????unsigned?char ???flags;???????? #endif ??????unsigned?char ???data[ENV_SIZE];??? }?env_t;??
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
# define ENV_HEADER_SIZE (sizeof(uint32_t) + 1)
# define ACTIVE_FLAG 1
# define OBSOLETE_FLAG 0
#else
# define ENV_HEADER_SIZE (sizeof(uint32_t))
#endif
#define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE)
typedef struct environment_s {uint32_t crc; /* CRC32 over data bytes */
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENTunsigned char flags; /* active/obsolete flags */
#endifunsigned char data[ENV_SIZE]; /* Environment data */
} env_t;
CONFIG_ENV_SIZE是我們需要在配置文件中配置的環境變量的總長度。
這里我們使用的nand 作為靜態存儲器, nand 的一個 block 是 128K ,因此選用一個 block 來存儲 env , CONFIG_ENV_SIZE 為 128K 。
Env_t結構體頭 4 個 bytes 是對 data 的 crc 校驗碼,沒有定義 CONFIG_SYS_REDUNDAND_ENVIRONMENT,所以后面緊跟data 數組,數組大小是 ENV_SIZE.
ENV_SIZE是 CONFIG_ENV_SIZE 減掉 ENV_HEADER_SIZE ,也就是 4bytes ,
所以env_t 這個結構體就包含了整個我們規定的長度為 CONFIG_ENV_SIZE 的存儲區域。
頭4bytes 是 crc 校驗碼,后面剩余的空間全部用來存儲環境變量。
需要說明的一點,crc 校驗碼是 uboot 中在 saveenv 時計算出來,然后寫入 nand ,所以在第一次啟動 uboot 時 crc 校驗會出錯,
因為 uboot 從 nand 上讀入的一個 block 數據是隨機的,沒有意義的,執行 saveenv 后重啟 uboot , crc 校驗就正確了。
data?字段保存實際的環境變量。 u-boot?? 的? env?? 按? name=value”\0” 的方式存儲,在所有 env? 的最后以 ”\0\0” 表示整個? env?? 的結束。
新的 name=value? 對總是被添加到? env?? 數據塊的末尾,當刪除一個 ?name=value? 對時,后面的環境變量將前移,對一個已經存在的環境變量的修改實際上先刪除再插入。 ? u-boot?把 env_t?? 的數據指針 還 保存在了另外一個地方, 這就 ? 是?gd_t?? 結構(不同平臺有不同的? gd_t?? 結構?) , 這里以 ARM? 為例僅列出和? env?? 相關的部分 ?
[cpp] view plaincopyprint?
typedef ?struct ?global_data???{??? ?????…??? ?????unsigned?long ?env_off;??????????? ?????unsigned?long ?env_addr;?????????? ?????unsigned?long ?env_valid?????????? ?????…??? }?gd_t;???
typedef struct global_data?
{?…?unsigned long env_off; ? ? ? ?/* Relocation Offset */?unsigned long env_addr; ? ? ? /* Address of Environment struct ??? */?unsigned long env_valid ? ? ? /* Checksum of Environment valid */?…?
} gd_t;?
二?env 的初始化
uboot中 env 的整個架構可以分為 3 層:
(1)?命令層,如saveenv , setenv?editenv 這些命令的實現,還有如啟動時調用的 env_relocate 函數。
(2)?中間封裝層,利用不同靜態存儲器特性封裝出命令層需要使用的一些通用函數,如env_init,env_relocate_spec,saveenv 這些函數。實現文件在 common/env_xxx.c
(3)?驅動層,實現不同靜態存儲器的讀寫擦等操作,這些是uboot 下不同子系統都必須的。
按照執行流順序,首先分析一下uboot 啟動的 env 初始化過程。
首先在board_init_f 中調用 init_sequence 的 env_init ,這個函數是不同存儲器實現的函數, nand 中的實現如下:
[cpp] view plaincopyprint?
<span?style="font-size:14px;" >? ? ? ? ? ? ? ? ? ? ? ?? int ?env_init(void )??{?? ????gd->env_addr????=?(ulong)&default_environment[0];?? ????gd->env_valid???=?1;?? ????return ?0;?? }</span>??
<span style="font-size:14px;">/** This is called before nand_init() so we can't read NAND to* validate env data.** Mark it OK for now. env_relocate() in env_common.c will call our* relocate function which does the real validation.** When using a NAND boot image (like sequoia_nand), the environment* can be embedded or attached to the U-Boot image in NAND flash.* This way the SPL loads not only the U-Boot image from NAND but* also the environment.*/
int env_init(void)
{gd->env_addr = (ulong)&default_environment[0];gd->env_valid = 1;return 0;
}</span>
從注釋就基本可以看出這個函數的作用,因為env_init 要早于靜態存儲器的初始化,所以無法進行 env 的讀寫,這里將 gd 中的 env 相關變量進行配置,
默認設置 env 為 valid 。方便后面 env_relocate 函數進行真正的 env 從 nand 到 ram 的 relocate 。
繼續執行,在board_init_r 中,如下:
[cpp] view plaincopyprint?
?? ????if ?(should_load_env())?? ????????env_relocate();?? ????else ?? ????????set_default_env(NULL);??
/* initialize environment */if (should_load_env())env_relocate();elseset_default_env(NULL);
這是在所有存儲器初始化完成后執行的。
首先調用should_load_env ,如下:
[cpp] view plaincopyprint?
? ? ? ? ? ? ? ? ? ? ?? static ?int ?should_load_env(void )??{?? #ifdef?CONFIG_OF_CONTROL ??????return ?fdtdec_get_config_int(gd->fdt_blob,?"load-environment" ,?1);?? #elif?defined?CONFIG_DELAY_ENVIRONMENT ??????return ?0;?? #else ??????return ?1;?? #endif ??}??
/** Tell if it's OK to load the environment early in boot.** If CONFIG_OF_CONFIG is defined, we'll check with the FDT to see* if this is OK (defaulting to saying it's not OK).** NOTE: Loading the environment early can be a bad idea if security is* important, since no verification is done on the environment.** @return 0 if environment should not be loaded, !=0 if it is ok to load*/
static int should_load_env(void)
{
#ifdef CONFIG_OF_CONTROLreturn fdtdec_get_config_int(gd->fdt_blob, "load-environment", 1);
#elif defined CONFIG_DELAY_ENVIRONMENTreturn 0;
#elsereturn 1;
#endif
}
從注釋可以看出,CONFIG_OF_CONTROL 沒有定義,鑒于考慮安全性問題,如果我們想要推遲 env 的 load ,可以定義 CONFIG_DELAY_ENVIRONMENT, 這里返回 0 ,就調用 set_default_env 使用默認的 env ,默認 env 是在配置文件中 CONFIG_EXTRA_ENV_SETTINGS設置的。
我們可以在之后的某個地方在調用env_relocate 來 load?env 。這里我們選擇在這里直接 load?env 。所以沒有定義 CONFIG_DELAY_ENVIRONMENT, 返回 1 。調用 env_relocate 。
在common/env_common.c 中:
[cpp] view plaincopyprint?
void ?env_relocate(void )??{?? #if?defined(CONFIG_NEEDS_MANUAL_RELOC) ??????env_reloc();?? ????env_htab.change_ok?+=?gd->reloc_off;?? #endif ??????if ?(gd->env_valid?==?0)?{?? #if?defined(CONFIG_ENV_IS_NOWHERE)?||?defined(CONFIG_SPL_BUILD) ???????????? ????????set_default_env(NULL);?? #else ??????????bootstage_error(BOOTSTAGE_ID_NET_CHECKSUM);?? ????????set_default_env("!bad?CRC" );?? #endif ??????}?else ?{?? ????????env_relocate_spec();?? ????}?? }??
void env_relocate(void)
{
#if defined(CONFIG_NEEDS_MANUAL_RELOC)env_reloc();env_htab.change_ok += gd->reloc_off;
#endifif (gd->env_valid == 0) {
#if defined(CONFIG_ENV_IS_NOWHERE) || defined(CONFIG_SPL_BUILD)/* Environment not changable */set_default_env(NULL);
#elsebootstage_error(BOOTSTAGE_ID_NET_CHECKSUM);set_default_env("!bad CRC");
#endif} else {env_relocate_spec();}
}
[cpp] view plaincopyprint?
??
</pre><p style="font-size: 14px;">Gd->env_valid<span style="font-family: 宋體;">在之前的</span><span style="font-family: Verdana;">env_init</span><span style="font-family: 宋體;">中設置為</span><span style="font-family: Verdana;">1</span><span style="font-family: 宋體;">,所以這里調用</span><span style="font-family: Verdana;">env_relocate_spec</span><span style="font-family: 宋體;">,</span></p><p style="font-size: 14px;"><span style="font-family: 宋體;">這個函數也是不同存儲器的中間封裝層提供的函數,對于</span><span style="font-family: Verdana;">nand</span><span style="font-family: 宋體;">在</span><span style="font-family: Verdana;">common/env_nand.c</span><span style="font-family: 宋體;">中,如下:</span></p><div class="dp-highlighter bg_cpp"><div class="bar"><div class="tools"><strong>[cpp]</strong> <a target=_blank title="view plain" class="ViewSource" href="http://blog.csdn.net/skyflying2012/article/details/39005705#">view plain</a><a target=_blank title="copy" class="CopyToClipboard" href="http://blog.csdn.net/skyflying2012/article/details/39005705#">copy</a><a target=_blank title="print" class="PrintSource" href="http://blog.csdn.net/skyflying2012/article/details/39005705#">print</a><a target=_blank title="?" class="About" href="http://blog.csdn.net/skyflying2012/article/details/39005705#">?</a><a target=_blank title="在CODE上查看代碼片" style="text-indent: 0px;" href="https://code.csdn.net/snippets/462607" target="_blank"><img width="12" height="12" style="left: 2px; top: 1px; position: relative;" alt="在CODE上查看代碼片" src="https://code.csdn.net/assets/CODE_ico.png" /></a><a target=_blank title="派生到我的代碼片" style="text-indent: 0px;" href="https://code.csdn.net/snippets/462607/fork" target="_blank"><img width="12" height="12" style="left: 2px; top: 2px; position: relative;" alt="派生到我的代碼片" src="https://code.csdn.net/assets/ico_fork.svg" /></a></div></div><ol class="dp-cpp"><li class="alt"><span><span class="keyword">void</span><span>?env_relocate_spec(</span><span class="keyword">void</span><span>)??</span></span></li><li><span>{??</span></li><li class="alt"><span>???<span class="datatypes">int</span><span>?ret;??</span></span></li><li><span>????ALLOC_CACHE_ALIGN_BUFFER(<span class="datatypes">char</span><span>,?buf,?CONFIG_ENV_SIZE);??</span></span></li><li class="alt"><span>????ret?=?readenv(CONFIG_ENV_OFFSET,?(u_char?*)buf);??</span></li><li><span>????<span class="keyword">if</span><span>?(ret)?{??</span></span></li><li class="alt"><span>????????set_default_env(<span class="string">"!readenv()?failed"</span><span>);??</span></span></li><li><span>????????<span class="keyword">return</span><span>;??</span></span></li><li class="alt"><span>????}??</span></li><li><span>????env_import(buf,?1);??</span></li><li class="alt"><span>}???</span></li></ol></div><pre class="cpp" style="font-size: 14px; display: none;" name="code" code_snippet_id="462607" snippet_file_name="blog_20140902_8_4316261">void env_relocate_spec(void)
{int ret;ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf);if (ret) {set_default_env("!readenv() failed");return;}env_import(buf, 1);
}
首先定義一個長度為CONFIG_ENV_SIZE 的 buf ,然后調用 readenv ,
CONFIG_ENV_OFFSET 是在配置文件中定義的 env 在 nand 中偏移位置。我們這里定義的是在 4M 的位置。
Readenv也在 env_nand.c 中,如下:
[cpp] view plaincopyprint?
int ?readenv(size_t ?offset,?u_char?*buf)??{?? ????size_t ?end?=?offset?+?CONFIG_ENV_RANGE;?? ????size_t ?amount_loaded?=?0;?? ????size_t ?blocksize,?len;?? ????u_char?*char_ptr;?? ????blocksize?=?nand_info[0].erasesize;?? ????if ?(!blocksize)?? ????????return ?1;?? ????len?=?min(blocksize,?CONFIG_ENV_SIZE);?? ????while ?(amount_loaded?<?CONFIG_ENV_SIZE?&&?offset?<?end)?{?? ????????if ?(nand_block_isbad(&nand_info[0],?offset))?{?? ????????????offset?+=?blocksize;?? ????????}?else ?{?? ????????????char_ptr?=?&buf[amount_loaded];?? ????????????if ?(nand_read_skip_bad(&nand_info[0],?offset,?? ???????????????????????????&len,?NULL,?? ???????????????????????????nand_info[0].size,?char_ptr))?? ????????????????return ?1;?? ????????????offset?+=?blocksize;?? ????????????amount_loaded?+=?len;?? ????????}?? ????}?? ?? ????if ?(amount_loaded?!=?CONFIG_ENV_SIZE)?? ????????return ?1;?? ?? ????return ?0;?? }??
int readenv(size_t offset, u_char *buf)
{size_t end = offset + CONFIG_ENV_RANGE;size_t amount_loaded = 0;size_t blocksize, len;u_char *char_ptr;blocksize = nand_info[0].erasesize;if (!blocksize)return 1;len = min(blocksize, CONFIG_ENV_SIZE);while (amount_loaded < CONFIG_ENV_SIZE && offset < end) {if (nand_block_isbad(&nand_info[0], offset)) {offset += blocksize;} else {char_ptr = &buf[amount_loaded];if (nand_read_skip_bad(&nand_info[0], offset,&len, NULL,nand_info[0].size, char_ptr))return 1;offset += blocksize;amount_loaded += len;}}if (amount_loaded != CONFIG_ENV_SIZE)return 1;return 0;
}
Readenv函數利用 nand_info[0] 對 nand 進行讀操作,讀出指定位置,指定長度的數據到 buf 中。 Nand_info[0]是一個全局變量,來表征第一個 nand?device ,這里在 nand_init 時會初始化這個變量。 Nand_init必須在 env_relocate 之前。
回到env_relocate_spec 中, buf 讀回后調用 env_import ,如下:
[cpp] view plaincopyprint?
? ? ? ?? int ?env_import(const ?char ?*buf,?int ?check)??{?? ????env_t?*ep?=?(env_t?*)buf;?? ?? ????if ?(check)?{?? ????????uint32_t?crc;?? ?? ????????memcpy(&crc,?&ep->crc,?sizeof (crc));?? ?? ????????if ?(crc32(0,?ep->data,?ENV_SIZE)?!=?crc)?{?? ????????????set_default_env("!bad?CRC" );?? ????????????return ?0;?? ????????}?? ????}?? ?? ????if ?(himport_r(&env_htab,?(char ?*)ep->data,?ENV_SIZE,?'\0' ,?0,?? ????????????0,?NULL))?{?? ????????gd->flags?|=?GD_FLG_ENV_READY;?? ????????return ?1;?? ????}?? ?? ????error("Cannot?import?environment:?errno?=?%d\n" ,?errno);?? ?? ????set_default_env("!import?failed" );?? ?? ????return ?0;?? }??
/** Check if CRC is valid and (if yes) import the environment.* Note that "buf" may or may not be aligned.*/
int env_import(const char *buf, int check)
{env_t *ep = (env_t *)buf;if (check) {uint32_t crc;memcpy(&crc, &ep->crc, sizeof(crc));if (crc32(0, ep->data, ENV_SIZE) != crc) {set_default_env("!bad CRC");return 0;}}if (himport_r(&env_htab, (char *)ep->data, ENV_SIZE, '\0', 0,0, NULL)) {gd->flags |= GD_FLG_ENV_READY;return 1;}error("Cannot import environment: errno = %d\n", errno);set_default_env("!import failed");return 0;
}
首先將buf 強制轉換為 env_t 類型,然后對 data 進行 crc 校驗,跟 buf 中原有的 crc 對比,不一致則使用默認 env 。
最后調用himport_r ,該函數將給出的 data 按照‘ \0 ’分割填入 env_htab 的哈希表中。
之后對于env 的操作,如 printenv?setenv?editenv ,都是對該哈希表的操作。
Env_relocate執行完成, env 的初始化就完成了。
三?env 的操作實現
Uboot對 env 的操作命令實現在 common/cmd_nvedit.c 中。
對于setenv?printenv?editenv 這 3 個命令,看其實現代碼,都是對 relocate 到 RAM 中的 env_htab 的操作,這里就不再詳細分析了,重點來看一下 savenv 實現。
[cpp] view plaincopyprint?
static ?int ?do_env_save(cmd_tbl_t?*cmdtp,?int ?flag,?int ?argc,?????????????????char ?*?const ?argv[])?? {?? ????printf("Saving?Environment?to?%s...\n" ,?env_name_spec);?? ?? ????return ?saveenv()???1?:?0;?? }?? ?? U_BOOT_CMD(?? ????saveenv,?1,?0,??do_env_save,?? ????"save?environment?variables?to?persistent?storage" ,?? ????"" ?? );??
static int do_env_save(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[])
{printf("Saving Environment to %s...\n", env_name_spec);return saveenv() ? 1 : 0;
}U_BOOT_CMD(saveenv, 1, 0, do_env_save,"save environment variables to persistent storage",""
);
在do_env_save 調用 saveenv, 這個函數是不同存儲器實現的封裝層函數。對于 nand ,在 common/env_nand.c 中,如下:
[cpp] view plaincopyprint?
int ?saveenv(void )??{?? ????int ?ret?=?0;?? ????ALLOC_CACHE_ALIGN_BUFFER(env_t,?env_new,?1);?? ????ssize_t?len;?? ????char ????*res;?? ????int ?env_idx?=?0;?? ????static ?const ?struct ?env_location?location[]?=?{?? ????????{?? ????????????.name?=?"NAND" ,?? ????????????.erase_opts?=?{?? ????????????????.length?=?CONFIG_ENV_RANGE,?? ????????????????.offset?=?CONFIG_ENV_OFFSET,?? ????????????},?? ????????},?? #ifdef?CONFIG_ENV_OFFSET_REDUND ??????????{?? ????????????.name?=?"redundant?NAND" ,?? ????????????.erase_opts?=?{?? ????????????????.length?=?CONFIG_ENV_RANGE,?? ????????????????.offset?=?CONFIG_ENV_OFFSET_REDUND,?? ????????????},?? ????????},?? #endif ??????};?? ?? ????if ?(CONFIG_ENV_RANGE?<?CONFIG_ENV_SIZE)?? ????????return ?1;?? ?? ????res?=?(char ?*)&env_new->data;?? ????len?=?hexport_r(&env_htab,?'\0' ,?0,?&res,?ENV_SIZE,?0,?NULL);?? ????if ?(len?<?0)?{?? ????????error("Cannot?export?environment:?errno?=?%d\n" ,?errno);?? ????????return ?1;?? ????}?? ????env_new->crc???=?crc32(0,?env_new->data,?ENV_SIZE);?? #ifdef?CONFIG_ENV_OFFSET_REDUND ??????env_new->flags?=?++env_flags;??? ????env_idx?=?(gd->env_valid?==?1);?? #endif ???? ????ret?=?erase_and_write_env(&location[env_idx],?(u_char?*)env_new);?? #ifdef?CONFIG_ENV_OFFSET_REDUND ??????if ?(!ret)?{?? ?????????? ????????gd->env_valid?=?gd->env_valid?==?2???1?:?2;?? ????????return ?ret;?? ????}?? ?? ????env_idx?=?(env_idx?+?1)?&?1;?? ????ret?=?erase_and_write_env(&location[env_idx],?(u_char?*)env_new);?? ????if ?(!ret)?? ????????printf("Warning:?primary?env?write?failed," ?? ????????????????"?redundancy?is?lost!\n" );?? #endif ???? ????return ?ret;?? }??
int saveenv(void)
{int ret = 0;ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1);ssize_t len;char *res;int env_idx = 0;static const struct env_location location[] = {{.name = "NAND",.erase_opts = {.length = CONFIG_ENV_RANGE,.offset = CONFIG_ENV_OFFSET,},},
#ifdef CONFIG_ENV_OFFSET_REDUND{.name = "redundant NAND",.erase_opts = {.length = CONFIG_ENV_RANGE,.offset = CONFIG_ENV_OFFSET_REDUND,},},
#endif};if (CONFIG_ENV_RANGE < CONFIG_ENV_SIZE)return 1;res = (char *)&env_new->data;len = hexport_r(&env_htab, '\0', 0, &res, ENV_SIZE, 0, NULL);if (len < 0) {error("Cannot export environment: errno = %d\n", errno);return 1;}env_new->crc = crc32(0, env_new->data, ENV_SIZE);
#ifdef CONFIG_ENV_OFFSET_REDUNDenv_new->flags = ++env_flags; /* increase the serial */env_idx = (gd->env_valid == 1);
#endifret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
#ifdef CONFIG_ENV_OFFSET_REDUNDif (!ret) {/* preset other copy for next write */gd->env_valid = gd->env_valid == 2 ? 1 : 2;return ret;}env_idx = (env_idx + 1) & 1;ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);if (!ret)printf("Warning: primary env write failed,"" redundancy is lost!\n");
#endifreturn ret;
}
定義env_t 類型的變量 env_new ,準備來存儲 env 。
利用函數hexport_r 對 env_htab 操作,讀取 env 內容到 env_new->data ,
校驗data ,獲取校驗碼 env_new->crc 。
最后調用erase_and_write_env 將 env_new 先擦后寫入由 location 定義的偏移量和長度的 nand 區域中。
這樣就完成了env寫入nand的操作。
在savenv readenv函數以及printenv setenv的實現函數中涉及到的函數himport_r hexport_r hdelete_r hmatch_r都是對env_htab哈希表的一些基本操作函數。
這些函數都封裝在uboot的lib/hashtable.c中,這里就不仔細分析這些函數了。
總結
以上是生活随笔 為你收集整理的uboot环境变量实现分析 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。