一是環境變量初始化,二是環境變量的設定、刪除等操作。下面將分別進行討論。
這裏所使用的u-boot版本爲2015.7,硬件爲I.MX6 boundary nitrogen6q開發平臺。
一 .環境變量初始化
1.讀取環境變量
環境變量的初始化在board_init_f階段完成,其由在common/barod_r.c中定義的靜態函數initr_env來實現:
static int initr_env(void)
{
/* initialize environment */
if (should_load_env())
env_relocate();
else
set_default_env(((void *)0));
/* Initialize from environment */
load_addr = getenv_ulong("loadaddr", 16, load_addr);
return 0;
}
should_load_env是board_r.c中的靜態函數,經過編譯預處理,直接返回1。接着執行env_relocate(),該函數在common/env_common.c中實現,編譯預處理後爲:
void env_relocate(void)
{
if (gd->env_valid == 0) {
bootstage_error(BOOTSTAGE_ID_NET_CHECKSUM);
set_default_env("!bad CRC");
} else {
env_relocate_spec();
}
}
gd->env_valid在board_f階段中的函數env_init調用中被賦值爲1,這裏將接着執行env_relocate_spec。env_relocate_spec針對不同的環境變量存儲設備有多處實現,這裏使用CONFIG_ENV_IS_IN_SPI_FLASH宏,所以使用common/Env_sf.c中的定義(參見common/Makefile),由於沒有定義CONFIG_ENV_OFFSET_REDUND,則env_relocate_spec實現爲:
void env_relocate_spec(void)
{
int ret;
char *buf = NULL;
buf = (char *)malloc(CONFIG_ENV_SIZE);
env_flash = spi_flash_probe(CONFIG_ENV_SPI_BUS, CONFIG_ENV_SPI_CS,
CONFIG_ENV_SPI_MAX_HZ, CONFIG_ENV_SPI_MODE);
if (!env_flash) {
set_default_env("!spi_flash_probe() failed");
if (buf)
free(buf);
return;
}
ret = spi_flash_read(env_flash,
CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE, buf);
if (ret) {
set_default_env("!spi_flash_read() failed");
goto out;
}
ret = env_import(buf, 1);
if (ret)
gd->env_valid = 1;
out:
spi_flash_free(env_flash);
if (buf)
free(buf);
env_flash = NULL;
}
如果從spi flash中讀取環境變量成功,則調用函數env_import,該函數將對讀取到的環境變量進行CRC校驗,如校驗失敗,會執行set_default_env。否則接着調用himport_r執行hash表的初始化。set_default_env函數用來配置默認的的環境變量。它在common/env_common.c中實現:
void set_default_env(constchar*s)
{
int flags = 0;
if (sizeof(default_environment) > ENV_SIZE) {
puts("*** Error - default environment is too large\n\n");
return;
}
if (s) {
if (*s == '!') {
printf("*** Warning - %s, "
"using default environment\n\n",
s + 1);
} else {
flags = H_INTERACTIVE;
puts(s);
}
} else {
puts("Using default environment\n\n");
}
if (himport_r(&env_htab, (char *)default_environment,
sizeof(default_environment), '\0', flags, 0,
0, NULL) == 0)
error("Environment import failed: errno = %d\n", errno);
gd->flags |= GD_FLG_ENV_READY;
}
default_environment爲include/Env_default.h中定義的變量。它是一個常量字符串數組。在default_environment定義時賦值時,賦值的常量字符串又包含了nitrogen6x.h中的宏定義CONFIG_EXTRA_ENV_SETTINGS,該宏定義爲代表附加環境變量的常量字符串。也即是default_environment值爲Env_default.h加上nitrogen6x.h文件中定義的環境變量值。另外注意,set_default_env函數中,gd->flags最後被賦值爲gd->flags |= GD_FLG_ENV_READY,而從SPI Flash中讀取的環境變量,在使用後續函數env_import時,該函數在返回前也會進行同樣的賦值操作。該標誌代表環境變量已準備好,可以對其進行打印、編輯操作。
環境變量讀取的執行總流程圖示如下:
無論是使用默認的環境變量,還是使用從spi flash讀取到的有效環境變量配置,完成環境變量的讀取後,都將調用himport_r將獲取到的環境變量導入到hash表中。
2. 導入環境變量到hash表中
u-boot中使用三個結構體描述了hash表,它們在include/search.h文件中定義:
struct hsearch_data;
struct _ENTRY;
struct entry,即ENTRY;
下圖描述了這些結構體的定義和它們之間的關係:
struct _ENTRY代表一個hash表項,其內部包含的struct entry(ENTRY)爲hash表中具體的內容數據。struct hsearch_data用來管理整個hash表。struct _ENTRY結構體中的成員used也是作爲hash表的管理之用。
讀入的環境變量會導入到hash表中,以方便環境變量的查找,插入,編輯等操作。
在下面的描述中,使用了hash表鍵值和key兩個名稱,hash表鍵值代表上圖中的hash表項索引index(整數),它還會存儲在struct _ENTRY成員變量used中,當其爲空時,表示該hash表中該索引表項未被佔用;key代表是的是上圖中ENTRY結構體中的成員變量字串指針key。hash表鍵值根據key通過hash算法來生成。key在u-boot中實際代表是環境變量名(name)。ENTRY結構體中的成員變量字符串指針data則代表相應環境變量的值(value)。
環境變量的hash表導入是通過函數himport_r來實現。該函數包含代碼較多,爲了便於分析,這裏刪除了略去了出錯檢查和繁雜的字符串操作代碼,只保留其主要架構:
int himport_r(struct hsearch_data *htab,
const char *env, size_t size, const char sep, int flag,
int crlf_is_lf, int nvars, char * const vars[])
{
char *data, *sp, *dp, *name, *value;
char *localvars[nvars];
int i;
if (htab == NULL)
...設定錯誤標誌,返回0;
if ((data = malloc(size)) == NULL)
...設定錯誤標誌,返回0;
memcpy(data, env, size);
dp = data;
/* make a local copy of the list of variables */
if (nvars)
memcpy(localvars, vars, sizeof(vars[0]) * nvars);
if ((flag & H_NOCLEAR) == 0) {
/* Destroy old hash table if one exists */
if (htab->table)
hdestroy_r(htab);
}
/*如果還未創建,則下面創建它*/
if (!htab->table) {
int nent = CONFIG_ENV_MIN_ENTRIES + size / 8;
if (nent > CONFIG_ENV_MAX_ENTRIES)
nent = CONFIG_ENV_MAX_ENTRIES;
/*創建hash表--分配存儲空間*/
if (hcreate_r(nent, htab) == 0) {
...釋放data存儲空間,然後返回0;
}
}
...
if(crlf_is_lf){
crlf_is_lf回車標誌,這裏傳入的參數crlf_is_lf爲0,不做處理
如果有些環境變量過長,字符串中間包括'\r'分行,那麼這裏處理它,去掉回車,並將'\r'前後的字符串
串聯成一個字符串。
如:"bootdelay=" "\r" "3" "\0"處理後爲"bootdelay=" "3" "\0"
在do_env_import(執行env import命令時帶-r選項)中包含了crlf_is_lf的情況。
}
/* Parse environment; allow for '\0' and 'sep' as separators */
do {
ENTRY e, *rv;
/*...解析環境變量,解析結構存入name和value中"*/
/* enter into hash table */
e.key = name;
e.data = value;
hsearch_r(e, ENTER, &rv, htab, flag);
} while ((dp < data + size) && *dp); /* size check needed for text */
/* without '\0' termination */
free(data);
/* process variables which were not considered */
...
return 1; /* everything OK */
}
該函數首先爲環境變量分配內存空間。如果標誌參數flag中包含H_NOCLEAR---該標誌代表強制清除hash表,那麼就調用hdestroy_r函數。如果hash表爲空(!htab->table)那麼將創建hash表。語句CONFIG_ENV_MIN_ENTRIES + size / 8
用來根據環境變量包含的字節數,以及CONFIG_ENV_MIN_ENTRIES宏定義的最小表項數,來計算hash表包含的表項數。這裏的8,使用的是估算法,假設每條環境變量佔用8個字節,即key=value(不含等號)。源代碼中對此有詳細註解。然後調用hcreate_r創建hash表----初始化htab->size爲上面的表項數,分配hash表的內存空間。
if(crlf_is_lf)代碼段處理環境變量中包含的'\r'。上面程序中有詳細註解。
do {
...
} while ((dp < data + size) && *dp);
do-while代碼段解析環境變量,然後將解析後的所有環境變量,逐條填入hash表中。程序首先解析環境變量,每條環境變量的設定用"\0"分割,環境變量名和其設定值用"="號分隔,程序最後將解析後的環境變量名存入name,值存入value中,如"baudrate=" "115200" "\0",解析後name的值爲"baudrate",value的內容爲"115200"。
e.key = name;
e.data = value;
hsearch_r(e, ENTER, &rv, htab, flag);
上述代碼將環境變量名字符串name賦值給hash表項中的key,環境變量值賦值給hash表項中的data,然後將e代表的hash表項插入到hash表中。下面將會分析具體的插入操作實現。
3.環境變量hash表的插入、編輯等操作
下面的討論涉及到hash表鍵值生成算法,關於這方面的內容,可參見數據結構的相關書籍和網絡上的相關文章。
u-boot中涉及到的hash表操作主要包括環境變量初始化和環境變量編輯。前者被上面的函數himport_r調用,後者則被u-boot一些環境變量編輯命令所調用。
這些操作主要通過函數hsearch_r來實現。刪除操作則通過hdelete_r函數實現。
和通用的hash表操作不同,u-boot中hash表的操作中附加了權限檢查和可執行的回調函數。前者是針對結構體ENTRY中的成員flags的檢查,後者則是對ENTRY中的成員callback的調用。
下面主要分析hash表項初始化(插入表項,和編輯操作中的插入相同)以及編輯(插入、修改,刪除等)操作的實現函數hsearch_r。我們將分段討論該函數。int hsearch_r(ENTRY item, ACTION action, ENTRY ** retval, struct hsearch_data *htab, int flag)
{
unsigned int hval;
unsigned int count;
unsigned int len = strlen(item.key);
unsigned int idx;
unsigned int first_deleted = 0;
int ret;
/* Compute an value for the given string. Perhaps use a better method. */
hval = len;
count = len;
while (count-- > 0) {
hval <<= 4;
hval += item.key[count];
}
/*
* First hash function:
* simply take the modul but prevent zero.
*/
hval %= htab->size;
if (hval == 0)
++hval;
/*--------------------------以上爲代碼段1--------------------------------------------*/
/* The first index tried. */
idx = hval;
if (htab->table[idx].used) {
/*
* Further action might be required according to the
* action value.
*/
unsigned hval2;
if (htab->table[idx].used == -1 && !first_deleted) /*下面的函數_hdelete中會把used填充爲-1*/
first_deleted = idx;
/*_compare_and_overwrite_entry中執行了change_ok權限檢查, 以及執行回調call_back函數*/
ret = _compare_and_overwrite_entry(item, action, retval, htab, flag, hval, idx);
if (ret != -1)
return ret;
/*
* Second hash function:
* as suggested in [Knuth]
*/
hval2 = 1 + hval % (htab->size - 2); /*如如hash表鍵值出現重複*/
do {
/*
* Because SIZE is prime this guarantees to
* step through all available indices.
*/
if (idx <= hval2)
idx = htab->size + idx - hval2;
else
idx -= hval2;
/*
* If we visited all entries leave the loop
* unsuccessfully.
*/
if (idx == hval)
break;
/* If entry is found use it. */
ret = _compare_and_overwrite_entry(item, action, retval, htab, flag, hval, idx);
if (ret != -1) /*沒有錯誤,直接返回*/
return ret;
}
while (htab->table[idx].used); /*直至生成或找到沒有重複的鍵值*/
}
/*--------------------------以上爲代碼段2--------------------------------------------*/
/* An empty bucket has been found. */
if (action == ENTER) { /*這裏是填充操作,即初始化*/
/*
* If table is full and another entry should be
* entered return with error.
*/
if (htab->filled == htab->size) {
...設置錯誤標誌,且返回0;
}
/*
* Create new entry;
* create copies of item.key and item.data
*/
if (first_deleted)
idx = first_deleted;
htab->table[idx].used = hval;
htab->table[idx].entry.key = strdup(item.key);
htab->table[idx].entry.data = strdup(item.data);
if (!htab->table[idx].entry.key || !htab->table[idx].entry.data) {
...設置錯誤標誌 ENOMEM,且返回0;
}
++htab->filled;
/* This is a new entry, so look up a possible callback */
env_callback_init(&htab->table[idx].entry); /*回調函數初始化*/
/* Also look for flags */
env_flags_init(&htab->table[idx].entry); /*flag標誌初始化*/
/*hash表項初始化時,其中涉及的環境變量表現會執行其相關的命令,如stdout=serial,vga
那麼就會執行類似u-boot中的setenv stdout serial,vga命令,且進行權限檢查。
當然這些回調函數是用戶定義的。如權限檢查置位且回調函數不爲空,權限檢查和回調函數返回失敗,
則刪除相應的hash表項,且返回標誌*/
/* check for permission */
if (htab->change_ok != NULL && htab->change_ok(
&htab->table[idx].entry, item.data, env_op_create, flag)) {
_hdelete(item.key, htab, &htab->table[idx].entry, idx);
...設置錯誤標誌EPERM ,且返回0;
}
/* If there is a callback, call it */
if (htab->table[idx].entry.callback &&
htab->table[idx].entry.callback(item.key, item.data,
env_op_create, flag)) {
_hdelete(item.key, htab, &htab->table[idx].entry, idx);
...設置錯誤標誌 EINVAL,且返回0;
}
/* return new entry */
*retval = &htab->table[idx].entry;
/*--------------------------以上爲代碼段3--------------------------------------------*/
return 1;
}
...設置錯誤標誌 ESRCH,且返回0;
}
代碼段1:hash表鍵值生成算法其中的代碼
while (count-- > 0) { /*這裏使用的是估算法,假設每條環境變量佔8個字節,hval爲32位(32/4)*/
hval <<= 4;
hval += item.key[count];
}
是hash鍵值算法的核心。將item.key---環境變量name中的字符移位並累加,然後模hash表項數,即爲該環境變量在hash表項中的數組索引---hash表鍵值。注意這裏使用的還是估算法,即假設每條環境變量字符串佔用8個字節,注意上述代碼中hval爲32位數,移位操作8次就會發生循環。if (hval == 0)
++hval;
保留了表項數組索引0的空間,在上面himport_r調用的hcreate_r分配hash存儲空間時,多分配了一個表項空間,即該處的保留空間。代碼段2:hash表鍵值重複衝突處理和編輯操作
上述代碼中
if (htab->table[idx].used == -1 && !first_deleted) /*下面的函數_hdelete中會把used填充爲-1*/
first_deleted = idx;
別處使用的函數_hdelete中會把used填充爲-1,變量first_deleted記錄第一次被刪除的鍵值。下面的代碼段3將會使用此處被賦值的first_deleted。函數_compare_and_overwrite_entry主要執行三項操作:
c.1)檢查以idx爲索引的hash表項中的key(環境變量名字符串)是否和輸入參數item.key一致,不一致返回-1
c.2)調用change_ok執行權限檢查,不通過返回0。
c.3)如果相應表項的成員callback 不爲空,則執行該回調函數。函數執行失敗返回0。
回調函數在hash表項初始化時或被填充(見下面的代碼段3)。
c.4)執行hash表項修改操作,即修改hash表項中的data爲新值。htab->table[idx].used的包含的值及其含義說明如下:
0 -- 未使用;
-1 -- 曾被刪除
index -- 鍵值索引
當htab->table[idx].used爲0時,則代表還未被使用和填充,將跳過代碼段2,執行代碼段3。
當htab->table[idx].used不爲空,表示如下三種情況:
a)代碼段生成的hash表鍵值出現重複
即以idx爲索引的表項已被其他環境變量佔用,由於環境變量名是唯一的,那麼_compare_and_overwrite_entry執行的hash表項中key(代表被佔用的環境變量名)和輸入參數item.key一致性檢查將出錯返回-1。然後調整hash表的鍵值生成算法,執行while循環,直至找到一個未重複的hash表鍵值,注意while將循環裏重複上述檢查過程。
另外還有一種情況,即此時hash表項中key爲空,即還未被初始化,那麼_compare_and_overwrite_entry也將返回-1。
這裏重點說明的是hash表項初始化中鍵值生成時重複問題,但鍵值重複的處理又不僅限於此。環境變量編輯時所涉及的hash表操作也會遇到該問題。但後者只不過重複還原前者的鍵值生成操作流程,以保證鍵值映射的一致性。
b)已被使用,這裏是編輯操作
_compare_and_overwrite_entry也會檢查根據代碼段1鍵值算法映射的鍵值是否重複,如果不重複,那麼執行上述的_compare_and_overwrite_entry中的c.2和c.3操作。c.2和c.3操作如出錯均會返回0,接着執行c.4完成表項內容修改操作(修改環境變量的值value),出錯也將返回0。所以上面的操作出錯都會返回0,而非-1,則表示編輯操作完成,
hsearch_r函數直接返回。
如果_compare_and_overwrite_entry返回-1, 表示該hash表項初始化時,在hash表生成時的鍵值處理中,已有重複鍵值。所以,針對已被初始化的hash表,這裏也要處理對這種重複鍵值進行重定位。即執行上述的a)。因爲第一個被刪除的hash表項即爲有效的鍵值。
c.)相應的hash表項曾被刪除,這裏再次被使用
如果已被刪除,那麼只有針對hash表項的再次初始化纔有意義,編輯操作執行_compare_and_overwrite_entry中的c.1返回-1,接着將執行a)。其實,此時代碼再次執行a)要麼無法找到有效的表項索引。即使能找到,下面的代碼段3也只會使用第一次的曾被刪除的表項索引值,該值記錄在first_deleted中。
從上面的分析中可以看出,函數_compare_and_overwrite_entry的返回值爲-1時,將hash表項初始化時和編輯操作時的鍵值重複檢查混同處理,導致程序執行流程複雜化。所以該函數的首字符爲下劃線,表示可疑版本(可改進)。
代碼段3:
這裏主要執行的是hash表項的初始化,在u-boot中使用新增環境命令時也將執行該段代碼。其他環境變量的編輯命令一般在代碼段2都被正確執行後直接返回。而在使用u-boot命令setenv xxx新增一個環境變量時,代碼段2也無法正確執行,不能返回到上層函數。
無論是整個hash表項的初始化,還是上述新增環境變量時的插入新的hash表項,它們執行的操作都是相同的,即都是插入新表項操作。程序執行到代碼段3時,上面的程序已經生成了有效的hash表鍵值,並賦值給變量idx。
htab->filled代表已使用的hash表項數,如其值大於hash->size,即hash表項數,那麼設置錯誤標誌,並直接返回0。
如果first_deleted不爲空,則其在代碼2中曾被賦值,表示該表項曾經被刪除過,其first_deleted代表第一個被刪除的表項對應的鍵值。
htab->table[idx].used = hval;
htab->table[idx].entry.key = strdup(item.key);
htab->table[idx].entry.data = strdup(item.data);
if (!htab->table[idx].entry.key || !htab->table[idx].entry.data) {
...設置錯誤標誌 ENOMEM,且返回0;
}
++htab->filled;
上面的代碼完成hash表項內容的填充。strdup函數會分配內存空間,注意entry.key和entry.data都是指針變量。if語句執行鍵值一致性檢查。填充無錯誤則變量htab->filled遞增1,該變量上面使用過,代表已使用的hash表項數。接着執行函數env_callback_init和env_flags_init,前者是hash表項中回調函數的初始化,後者是hash表項中flags的初始化,flags用來標誌訪問權限。這兩個函數的實現比較複雜,會另做一節對它們分析。接着的代碼:if (htab->change_ok != NULL && htab->change_ok(
&htab->table[idx].entry, item.data, env_op_create, flag)) {
_hdelete(item.key, htab, &htab->table[idx].entry, idx);
...設置錯誤標誌EPERM ,且返回0;
}
htab->change_ok函數在其定義時賦值爲env_flags_validate,這裏不爲空,則將調用該函數執行操作權限檢查。代碼:
if (htab->table[idx].entry.callback &&
htab->table[idx].entry.callback(item.key, item.data,
env_op_create, flag)) {
_hdelete(item.key, htab, &htab->table[idx].entry, idx);
...設置錯誤標誌 EINVAL,且返回0;
}
執行在env_callback_init中初始化的回調函數。如權限檢查和回調函數的執行出現錯誤,則直接刪除該hash表項。這裏,可以看到,環境變量在其初始化的hash表填充時,就會調用表項中設置的回調函數。例如,有如下的存儲在spi flash中的環境變量:
const unsigned char default_environment[] = {
"bootdelay=" "3" "\0"
"baudrate=" "115200" "\0"
"stdout=" "serial,vga" "\0"
"ethprime=" "FEC" "\0"
"preboot=" "" "\0"
"loadaddr=" "0x12000000" "\0"
"\0"
};
以第二行的環境變量"stdout=" "serial,vga"爲例,當其從spi flash加載到內存空間中,並填充到hash表中時,如果該項環境變量的相關賦值(創建)操作被允許(權限檢查通過),且有相應的回調函數,那麼此處就會執行該函數。另外代碼段2中的_compare_and_overwrite_entry中也將執行權限檢查和回調函數。如,在u-boot中執行:=>setenv stdout serial,hdmi
那麼該命令會調用do_env_set函數,它又調用上述的hsearch_r並執行代碼段2。所以,在"console_init_r分析"一節中,曾經提到過該命令是立即生效的。
3.env_flags_init和env_callback_init
env_flags_init初始化操作權限,env_callback_init初始化回調函數。
所謂的權限是針對每一個hash表項而言的,而每一個hash表項對應一條環境變量,權限即環境變量的創建,修改,刪除等權限。回調函數是執行這些環境變量的操作後附加的一些操作。
如上面的例子中,執行setenv stdout serial,hdmi時,會首先檢查環境變量stdout的修改操作是否被允許,如果允許將環境變量的值修改爲serial,hdmi,然後調用回調函數,重設console。
下面來逐一分析這兩個函數的具體實現。
3.1 env_flags_init
權限操作集中在文件env_flags.c中:
void env_flags_init(ENTRY *var_entry)
{
const char *var_name = var_entry->key;
char flags[ENV_FLAGS_ATTR_MAX_LEN + 1] = "";
int ret = 1;
if (first_call) {
flags_list = getenv(ENV_FLAGS_VAR);
first_call = 0;
}
/* look in the ".flags" and static for a reference to this variable */
ret = env_flags_lookup(flags_list, var_name, flags);
/* if any flags were found, set the binary form to the entry */
if (!ret && strlen(flags))
var_entry->flags = env_parse_flags_to_bin(flags);
}
first_call爲靜態變量,其定義時初始化爲1。上述代碼首先從環境變量中獲取flags的值(ENV_FLAGS_VAR),然後調用env_flags_lookup:static inline int env_flags_lookup(const char *flags_list, const char *name,
char *flags)
{
int ret = 1;
if (!flags)
/* bad parameter */
return -1;
/* try the env first */
if (flags_list)
ret = env_attr_lookup(flags_list, name, flags);
if (ret != 0)
/* if not found in the env, look in the static list */
ret = env_attr_lookup(ENV_FLAGS_LIST_STATIC, name, flags);
return ret;
}
一般地,環境變量中不含ENV_FLAGS_VAR,那麼此處的flags_list值爲空,則執行env_attr_lookup(ENV_FLAGS_LIST_STATIC, name, flags);ENV_FLAGS_LIST_STATIC定義爲:
#define ENV_FLAGS_LIST_STATIC \
"ipaddr:i," \
"gatewayip:i," \
"netmask:i," \
"serverip:i," \
"serial#:so,"
每條環境變量的操作權限用逗號分隔,變量名和權限位使用冒號分隔。如上面ipaddr是環境變量名,i是權限描述。權限描述又分爲權限類型和權限值描述。
類型即是該環境變量對應值的類型,其字符的定義實現及其含義如下:
字符s代表string類型數據
字符d代表decimal類型數據
字符x代表hyexadecimal類型數據
字符b代表boolean 類型數據
字符i代表ip address 類型數據
權限值及其含義如下:
字符a表示any,可進行任何操作,它爲權限的默認值
字符r表示read-only,只讀
字符o表示write-once,可一次寫
字符c表示change-default,可改變爲默認值
上述權限值對應的u-boot操作包含在env_flags_varaccess_mask變量中:
static const int env_flags_varaccess_mask[] = {
0,
ENV_FLAGS_VARACCESS_PREVENT_DELETE |
ENV_FLAGS_VARACCESS_PREVENT_CREATE |
ENV_FLAGS_VARACCESS_PREVENT_OVERWR,
ENV_FLAGS_VARACCESS_PREVENT_DELETE |
ENV_FLAGS_VARACCESS_PREVENT_OVERWR,
ENV_FLAGS_VARACCESS_PREVENT_DELETE |
ENV_FLAGS_VARACCESS_PREVENT_NONDEF_OVERWR
};
env_flags_init函數會調用函數env_parse_flags_to_bin,將權限值字符,映射爲上面env_flags_varaccess_mask變量中的二進制值,即
字符a最終映射爲0
字符r映射爲:ENV_FLAGS_VARACCESS_PREVENT_DELETE |
ENV_FLAGS_VARACCESS_PREVENT_CREATE |
ENV_FLAGS_VARACCESS_PREVENT_OVERWR,
字符o映射爲:
ENV_FLAGS_VARACCESS_PREVENT_DELETE |
ENV_FLAGS_VARACCESS_PREVENT_OVERWR,
字符c映射爲:
ENV_FLAGS_VARACCESS_PREVENT_DELETE |
ENV_FLAGS_VARACCESS_PREVENT_NONDEF_OVERWR
從上面的ENV_FLAGS_VARACCESS_XX宏定義名可以看出其代表的操作權限。
env_flags_init最後會將映射後的二進制權限位值賦值給hash表項的成員flags。struct hsearch_data的成員變量change_ok,它是一個函數指針,定義時被初始化爲env_flags_validate。環境變量操作的權限檢查是通過該函數來執行。
int env_flags_validate(const ENTRY *item, const char *newval, enum env_op op, int flag)
{
const char *name;
const char *oldval = NULL;
if (op != env_op_create)
oldval = item->data;
name = item->key;
/* Default value for NULL to protect string-manipulating functions */
newval = newval ? : "";
/* validate the value to match the variable type */
if (op != env_op_delete) {
enum env_flags_vartype type = (enum env_flags_vartype)
(ENV_FLAGS_VARTYPE_BIN_MASK & item->flags);
if (_env_flags_validate_type(newval, type) < 0) {
...
return -1;
}
}
/* check for access permission */
#ifndef CONFIG_ENV_ACCESS_IGNORE_FORCE
if (flag & H_FORCE)
return 0;
#endif
switch (op) {
case env_op_delete:
if (item->flags & ENV_FLAGS_VARACCESS_PREVENT_DELETE) {
return 1;
}
break;
case env_op_overwrite:
if (item->flags & ENV_FLAGS_VARACCESS_PREVENT_OVERWR) {
return 1;
} else if (item->flags &
ENV_FLAGS_VARACCESS_PREVENT_NONDEF_OVERWR) {
const char *defval = getenv_default(name);
if (defval == NULL)
defval = "";
if (strcmp(oldval, defval) != 0) {
return 1;
}
}
break;
case env_op_create:
if (item->flags & ENV_FLAGS_VARACCESS_PREVENT_CREATE) {
return 1;
}
break;
}
return 0;
}
如果定義了CONFIG_ENV_ACCESS_IGNORE_FORCE,則所有的訪問權限將被忽略。針對不同的操作,傳給env_flags_validate的參數op值就不同。
在上面的函數hsearch_r中,代碼段2中調用_compare_and_overwrite_entry,然後調用change_ok,傳入的op值是env_op_overwrite,而代碼段3中,調用change_ok ,傳入的op值爲env_op_create。這樣env_flags_validate函數將根據不同的op操作,來執行相應的權限檢查。如當操作是創建hash表項時,那麼,會檢查ENV_FLAGS_VARACCESS_PREVENT_CREATE標誌。
另外,如果相應的環境變量hash表項沒有定義flags,則其值爲0,允許所有的權限。即針對環境變量的操作,默認的權限爲全部使能。
3.2 env_callback_init
void env_callback_init(ENTRY *var_entry)
{
const char *var_name = var_entry->key;
char callback_name[256] = "";
struct env_clbk_tbl *clbkp;
int ret = 1;
if (first_call) {
callback_list = getenv(ENV_CALLBACK_VAR);
first_call = 0;
}
/* look in the ".callbacks" var for a reference to this variable */
if (callback_list != NULL)
ret = env_attr_lookup(callback_list, var_name, callback_name);
/* only if not found there, look in the static list */
if (ret)
ret = env_attr_lookup(ENV_CALLBACK_LIST_STATIC, var_name,
callback_name);
/* if an association was found, set the callback pointer */
if (!ret && strlen(callback_name)) {
clbkp = find_env_callback(callback_name);
if (clbkp != NULL)
var_entry->callback = clbkp->callback;
}
}
代碼首先從環境變量中獲取回調函數的值(ENV_CALLBACK_VAR),然後調用env_attr_lookup,這裏使用參數ENV_CALLBACK_LIST_STATIC,該宏定義如下:#define ENV_CALLBACK_LIST_STATIC ENV_DOT_ESCAPE ENV_CALLBACK_VAR ":callbacks," \
ENV_DOT_ESCAPE ENV_FLAGS_VAR ":flags," \
"baudrate:baudrate," \
NET_CALLBACKS \
"loadaddr:loadaddr," \
SILENT_CALLBACK \
SPLASHIMAGE_CALLBACK \
"stdin:console,stdout:console,stderr:console," \
CONFIG_ENV_CALLBACK_LIST_STATIC
每條環境變量操作的回調函數用逗號分隔,變量名和回調函數索引字符串使用冒號分隔。如上面stdin是環境變量名,冒號後面的console是回調函數索引字符串。之所以稱爲"回調函數索引字符串",因爲這裏只是字符串,必須使用映射機制,將回調函數索引字符串和所對應的回調函數關聯起來。然後使用回調函數索引字符串,查找到相應的回調函數。結構體struct env_clbk_tbl維持着這樣一個回調函數索引字符串和回調函數的映射表:
struct env_clbk_tbl {
const char *name; /* Callback name */
int (*callback)(const char *name, const char *value, enum env_op op,
int flags);
};
利用宏U_BOOT_ENV_CALLBACK填充該結構體,將上述的回調函數索引字符串和回調函數關聯起來,如:U_BOOT_ENV_CALLBACK(console, on_console);
將回調函數索引字符串"console"和on_console回調函數相關聯,那麼上述環境變量stdin操作最後的回調函數就是on_console。
在u-boot中,使用U_BOOT_ENV_CALLBACK定義並初始化的struct env_clbk_tbl變量都存放在名爲_u_boot_list_2_env_clbk_2_xx的符號段中。
U_BOOT_ENV_CALLBACK(console, on_console)預編譯後爲:
struct env_clbk_tbl _u_boot_list_2_env_clbk_2_console __attribute__((aligned(4))) __attribute__((unused, section(".u_boot_list_2_""env_clbk""_2_""console"))) = {"console", on_console};
注意上述section中的符號段名。所有利用U_BOOT_ENV_CALLBACK 定義的的環境變量回調函數都將放在這樣一個以_u_boot_list_2_env_clbk_2開頭的符號段中。在u-boot生成的符號表文件system.map中可查看到這些段:
17868084 D _u_boot_list_2_env_clbk_2_bootfile
1786808c D _u_boot_list_2_env_clbk_2_callbacks
17868094 D _u_boot_list_2_env_clbk_2_console
1786809c D _u_boot_list_2_env_clbk_2_ethaddr
178680a4 D _u_boot_list_2_env_clbk_2_flags
178680ac D _u_boot_list_2_env_clbk_2_gatewayip
178680b4 D _u_boot_list_2_env_clbk_2_ipaddr
178680bc D _u_boot_list_2_env_clbk_2_loadaddr
178680c4 D _u_boot_list_2_env_clbk_2_netmask
env_callback_init函數中接着執行的find_env_callback就是在_u_boot_list_2_env_clbk_2_xx段中查找環境變量相應的符號段,該符號也即上述struct env_clbk_tbl變量的地址。find_env_callback函數對其中name進行覈對後,返回有效的回調函數指針然後函數env_callback_init將其賦值給hash表項的成員callback。這樣就完成了環境變量hash表項的回調函數初始化。